Compare commits
103 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9ae9782fe | ||
|
|
016dba4ca9 | ||
|
|
39c7ef305f | ||
|
|
849c1dc848 | ||
|
|
61414014fe | ||
|
|
578a915884 | ||
|
|
eacafb8a63 | ||
|
|
4446760f74 | ||
|
|
6da2a083f9 | ||
|
|
8837c8f822 | ||
|
|
bac301ed66 | ||
|
|
061db3906d | ||
|
|
fd7df5c952 | ||
|
|
a270019147 | ||
|
|
55e0209901 | ||
|
|
2b255fbbed | ||
|
|
8a2345a0fb | ||
|
|
bfa9f535aa | ||
|
|
f757623ad8 | ||
|
|
3c7465e268 | ||
|
|
108665fc4f | ||
|
|
ed519c9138 | ||
|
|
2dd2e2c57e | ||
|
|
6c3a976222 | ||
|
|
80cc26bd95 | ||
|
|
970fb84fd8 | ||
|
|
20cbcf6931 | ||
|
|
8fcde2a579 | ||
|
|
b32d1f8ad3 | ||
|
|
03513e0cb1 | ||
|
|
e041a2b197 | ||
|
|
d7d625be2a | ||
|
|
4121266678 | ||
|
|
22971a6be4 | ||
|
|
efbf8d7e0d | ||
|
|
397396ea4a | ||
|
|
e59b077c21 | ||
|
|
4bc39f3084 | ||
|
|
21c3570786 | ||
|
|
2f85c1fb18 | ||
|
|
1e27a4c2df | ||
|
|
456f575637 | ||
|
|
51546c9e64 | ||
|
|
83b4b70ef4 | ||
|
|
a5120d4f6f | ||
|
|
c95941e14f | ||
|
|
0dd531149d | ||
|
|
67da1b5219 | ||
|
|
919bd16437 | ||
|
|
ecead109ab | ||
|
|
765294c263 | ||
|
|
d6b5351207 | ||
|
|
a2009bcc6b | ||
|
|
12709a8a0a | ||
|
|
c055baefd2 | ||
|
|
56522599b5 | ||
|
|
664f53b75d | ||
|
|
87200d9f10 | ||
|
|
5c3d0b6520 | ||
|
|
bd49979f4a | ||
|
|
7e606cdd9f | ||
|
|
8b4b7fa794 | ||
|
|
05345ddf8b | ||
|
|
66adb470ad | ||
|
|
e15c8fd146 | ||
|
|
0f09b98a39 | ||
|
|
b4d6f4e24d | ||
|
|
3217fa625b | ||
|
|
e719ff8a47 | ||
|
|
9fcf528d45 | ||
|
|
1ddbf5a158 | ||
|
|
64bf4574b0 | ||
|
|
5649d26077 | ||
|
|
92f923effe | ||
|
|
0d46d548b9 | ||
|
|
062df3f0c3 | ||
|
|
789fb53b8e | ||
|
|
351db5a18f | ||
|
|
aabbd271c8 | ||
|
|
aae8e0171e | ||
|
|
45827a2458 | ||
|
|
726030296f | ||
|
|
6659ab3881 | ||
|
|
c6a103609e | ||
|
|
c6b3f035e5 | ||
|
|
2b0a7e378e | ||
|
|
b75ce909c8 | ||
|
|
229c3f5dab | ||
|
|
ec73094506 | ||
|
|
c7650c9326 | ||
|
|
d94c6d4e72 | ||
|
|
3cc8760733 | ||
|
|
a2f6973495 | ||
|
|
f8648fa651 | ||
|
|
177aa038df | ||
|
|
e0a14ec881 | ||
|
|
9366512f2f | ||
|
|
ea38b8041a | ||
|
|
f1870daf0d | ||
|
|
9722441aad | ||
|
|
9d014087f4 | ||
|
|
83b4038b85 | ||
|
|
1e0a448feb |
66
README.md
66
README.md
@@ -20,8 +20,10 @@ turn your phone or raspi into a portable file server with resumable uploads/down
|
|||||||
|
|
||||||
* top
|
* top
|
||||||
* [quickstart](#quickstart)
|
* [quickstart](#quickstart)
|
||||||
|
* [on debian](#on-debian)
|
||||||
* [notes](#notes)
|
* [notes](#notes)
|
||||||
* [status](#status)
|
* [status](#status)
|
||||||
|
* [testimonials](#testimonials)
|
||||||
* [bugs](#bugs)
|
* [bugs](#bugs)
|
||||||
* [general bugs](#general-bugs)
|
* [general bugs](#general-bugs)
|
||||||
* [not my bugs](#not-my-bugs)
|
* [not my bugs](#not-my-bugs)
|
||||||
@@ -44,6 +46,7 @@ turn your phone or raspi into a portable file server with resumable uploads/down
|
|||||||
* [browser support](#browser-support)
|
* [browser support](#browser-support)
|
||||||
* [client examples](#client-examples)
|
* [client examples](#client-examples)
|
||||||
* [up2k](#up2k)
|
* [up2k](#up2k)
|
||||||
|
* [performance](#performance)
|
||||||
* [dependencies](#dependencies)
|
* [dependencies](#dependencies)
|
||||||
* [optional dependencies](#optional-dependencies)
|
* [optional dependencies](#optional-dependencies)
|
||||||
* [install recommended deps](#install-recommended-deps)
|
* [install recommended deps](#install-recommended-deps)
|
||||||
@@ -68,6 +71,7 @@ some recommended options:
|
|||||||
* `-e2dsa` enables general file indexing, see [search configuration](#search-configuration)
|
* `-e2dsa` enables general file indexing, see [search configuration](#search-configuration)
|
||||||
* `-e2ts` enables audio metadata indexing (needs either FFprobe or mutagen), see [optional dependencies](#optional-dependencies)
|
* `-e2ts` enables audio metadata indexing (needs either FFprobe or mutagen), see [optional dependencies](#optional-dependencies)
|
||||||
* `-v /mnt/music:/music:r:afoo -a foo:bar` shares `/mnt/music` as `/music`, `r`eadable by anyone, with user `foo` as `a`dmin (read/write), password `bar`
|
* `-v /mnt/music:/music:r:afoo -a foo:bar` shares `/mnt/music` as `/music`, `r`eadable by anyone, with user `foo` as `a`dmin (read/write), password `bar`
|
||||||
|
* the syntax is `-v src:dst:perm:perm:...` so local-path, url-path, and one or more permissions to set
|
||||||
* replace `:r:afoo` with `:rfoo` to only make the folder readable by `foo` and nobody else
|
* replace `:r:afoo` with `:rfoo` to only make the folder readable by `foo` and nobody else
|
||||||
* in addition to `r`ead and `a`dmin, `w`rite makes a folder write-only, so cannot list/access files in it
|
* in addition to `r`ead and `a`dmin, `w`rite makes a folder write-only, so cannot list/access files in it
|
||||||
* `--ls '**,*,ln,p,r'` to crash on startup if any of the volumes contain a symlink which point outside the volume, as that could give users unintended access
|
* `--ls '**,*,ln,p,r'` to crash on startup if any of the volumes contain a symlink which point outside the volume, as that could give users unintended access
|
||||||
@@ -77,6 +81,19 @@ you may also want these, especially on servers:
|
|||||||
* [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 behind nginx (for better https)
|
||||||
|
|
||||||
|
|
||||||
|
### on debian
|
||||||
|
|
||||||
|
recommended steps to enable audio metadata and thumbnails (from images and videos):
|
||||||
|
|
||||||
|
* as root, run the following:
|
||||||
|
`apt install python3 python3-pip python3-dev ffmpeg`
|
||||||
|
|
||||||
|
* then, as the user which will be running copyparty (so hopefully not root), run this:
|
||||||
|
`python3 -m pip install --user -U Pillow pillow-avif-plugin`
|
||||||
|
|
||||||
|
(skipped `pyheif-pillow-opener` because apparently debian is too old to build it)
|
||||||
|
|
||||||
|
|
||||||
## notes
|
## notes
|
||||||
|
|
||||||
general:
|
general:
|
||||||
@@ -128,6 +145,13 @@ summary: all planned features work! now please enjoy the bloatening
|
|||||||
* ☑ editor (sure why not)
|
* ☑ editor (sure why not)
|
||||||
|
|
||||||
|
|
||||||
|
## testimonials
|
||||||
|
|
||||||
|
small collection of user feedback
|
||||||
|
|
||||||
|
`good enough`, `surprisingly correct`, `certified good software`, `just works`, `why`
|
||||||
|
|
||||||
|
|
||||||
# bugs
|
# bugs
|
||||||
|
|
||||||
* Windows: python 3.7 and older cannot read tags with ffprobe, so use mutagen or upgrade
|
* Windows: python 3.7 and older cannot read tags with ffprobe, so use mutagen or upgrade
|
||||||
@@ -139,6 +163,8 @@ summary: all planned features work! now please enjoy the bloatening
|
|||||||
|
|
||||||
* all volumes must exist / be available on startup; up2k (mtp especially) gets funky otherwise
|
* all volumes must exist / be available on startup; up2k (mtp especially) gets funky otherwise
|
||||||
* cannot mount something at `/d1/d2/d3` unless `d2` exists inside `d1`
|
* cannot mount something at `/d1/d2/d3` unless `d2` exists inside `d1`
|
||||||
|
* dupe files will not have metadata (audio tags etc) displayed in the file listing
|
||||||
|
* because they don't get `up` entries in the db (probably best fix) and `tx_browser` does not `lstat`
|
||||||
* probably more, pls let me know
|
* probably more, pls let me know
|
||||||
|
|
||||||
## not my bugs
|
## not my bugs
|
||||||
@@ -174,13 +200,21 @@ the browser has the following hotkeys
|
|||||||
* `G` toggle list / grid view
|
* `G` toggle list / grid view
|
||||||
* `T` toggle thumbnails / icons
|
* `T` toggle thumbnails / icons
|
||||||
* when playing audio:
|
* when playing audio:
|
||||||
* `0..9` jump to 10%..90%
|
|
||||||
* `U/O` skip 10sec back/forward
|
|
||||||
* `J/L` prev/next song
|
* `J/L` prev/next song
|
||||||
|
* `U/O` skip 10sec back/forward
|
||||||
|
* `0..9` jump to 10%..90%
|
||||||
* `P` play/pause (also starts playing the folder)
|
* `P` play/pause (also starts playing the folder)
|
||||||
|
* when viewing images / playing videos:
|
||||||
|
* `J/L, Left/Right` prev/next file
|
||||||
|
* `Home/End` first/last file
|
||||||
|
* `U/O` skip 10sec back/forward
|
||||||
|
* `P/K/Space` play/pause video
|
||||||
|
* `Esc` close viewer
|
||||||
|
* when tree-sidebar is open:
|
||||||
|
* `A/D` adjust tree width
|
||||||
* in the grid view:
|
* in the grid view:
|
||||||
* `S` toggle multiselect
|
* `S` toggle multiselect
|
||||||
* `A/D` zoom
|
* shift+`A/D` zoom
|
||||||
|
|
||||||
|
|
||||||
## tree-mode
|
## tree-mode
|
||||||
@@ -198,6 +232,8 @@ it does static images with Pillow and uses FFmpeg for video files, so you may wa
|
|||||||
|
|
||||||
images named `folder.jpg` and `folder.png` become the thumbnail of the folder they're in
|
images named `folder.jpg` and `folder.png` become the thumbnail of the folder they're in
|
||||||
|
|
||||||
|
in the grid/thumbnail view, if the audio player panel is open, songs will start playing when clicked
|
||||||
|
|
||||||
|
|
||||||
## zip downloads
|
## zip downloads
|
||||||
|
|
||||||
@@ -465,6 +501,23 @@ quick outline of the up2k protocol, see [uploading](#uploading) for the web-clie
|
|||||||
* client does another handshake with the hashlist; server replies with OK or a list of chunks to reupload
|
* client does another handshake with the hashlist; server replies with OK or a list of chunks to reupload
|
||||||
|
|
||||||
|
|
||||||
|
# performance
|
||||||
|
|
||||||
|
defaults are good for most cases, don't mind the `cannot efficiently use multiple CPU cores` message, it's very unlikely to be a problem
|
||||||
|
|
||||||
|
below are some tweaks roughly ordered by usefulness:
|
||||||
|
|
||||||
|
* `-q` disables logging and can help a bunch, even when combined with `-lo` to redirect logs to file
|
||||||
|
* `--http-only` or `--https-only` (unless you want to support both protocols) will reduce the delay before a new connection is established
|
||||||
|
* `--hist` pointing to a fast location (ssd) will make directory listings and searches faster when `-e2d` or `-e2t` is set
|
||||||
|
* `--no-hash` when indexing a networked disk if you don't care about the actual filehashes and only want the names/tags searchable
|
||||||
|
* `-j` enables multiprocessing (actual multithreading) and can make copyparty perform better in cpu-intensive workloads, for example:
|
||||||
|
* huge amount of short-lived connections
|
||||||
|
* really heavy traffic (downloads/uploads)
|
||||||
|
|
||||||
|
...however it adds an overhead to internal communication so it might be a net loss, see if it works 4 u
|
||||||
|
|
||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
|
|
||||||
* `jinja2` (is built into the SFX)
|
* `jinja2` (is built into the SFX)
|
||||||
@@ -594,13 +647,15 @@ in the `scripts` folder:
|
|||||||
roughly sorted by priority
|
roughly sorted by priority
|
||||||
|
|
||||||
* readme.md as epilogue
|
* readme.md as epilogue
|
||||||
* single sha512 across all up2k chunks? maybe
|
|
||||||
* reduce up2k roundtrips
|
* reduce up2k roundtrips
|
||||||
* start from a chunk index and just go
|
* start from a chunk index and just go
|
||||||
* terminate client on bad data
|
* terminate client on bad data
|
||||||
|
* logging to file
|
||||||
|
|
||||||
discarded ideas
|
discarded ideas
|
||||||
|
|
||||||
|
* single sha512 across all up2k chunks?
|
||||||
|
* crypto.subtle cannot into streaming, would have to use hashwasm, expensive
|
||||||
* separate sqlite table per tag
|
* separate sqlite table per tag
|
||||||
* performance fixed by skipping some indexes (`+mt.k`)
|
* performance fixed by skipping some indexes (`+mt.k`)
|
||||||
* audio fingerprinting
|
* audio fingerprinting
|
||||||
@@ -615,3 +670,6 @@ discarded ideas
|
|||||||
* nah
|
* nah
|
||||||
* look into android thumbnail cache file format
|
* look into android thumbnail cache file format
|
||||||
* absolutely not
|
* absolutely not
|
||||||
|
* indexedDB for hashes, cfg enable/clear/sz, 2gb avail, ~9k for 1g, ~4k for 100m, 500k items before autoeviction
|
||||||
|
* blank hashlist when up-ok to skip handshake
|
||||||
|
* too many confusing side-effects
|
||||||
|
|||||||
@@ -345,7 +345,7 @@ class Gateway(object):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def sendreq(self, *args, headers={}, **kwargs):
|
def sendreq(self, meth, path, headers, **kwargs):
|
||||||
if self.password:
|
if self.password:
|
||||||
headers["Cookie"] = "=".join(["cppwd", self.password])
|
headers["Cookie"] = "=".join(["cppwd", self.password])
|
||||||
|
|
||||||
@@ -354,21 +354,21 @@ class Gateway(object):
|
|||||||
if c.rx_path:
|
if c.rx_path:
|
||||||
raise Exception()
|
raise Exception()
|
||||||
|
|
||||||
c.request(*list(args), headers=headers, **kwargs)
|
c.request(meth, path, headers=headers, **kwargs)
|
||||||
c.rx = c.getresponse()
|
c.rx = c.getresponse()
|
||||||
return c
|
return c
|
||||||
except:
|
except:
|
||||||
tid = threading.current_thread().ident
|
tid = threading.current_thread().ident
|
||||||
dbg(
|
dbg(
|
||||||
"\033[1;37;44mbad conn {:x}\n {}\n {}\033[0m".format(
|
"\033[1;37;44mbad conn {:x}\n {} {}\n {}\033[0m".format(
|
||||||
tid, " ".join(str(x) for x in args), c.rx_path if c else "(null)"
|
tid, meth, path, c.rx_path if c else "(null)"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.closeconn(c)
|
self.closeconn(c)
|
||||||
c = self.getconn()
|
c = self.getconn()
|
||||||
try:
|
try:
|
||||||
c.request(*list(args), headers=headers, **kwargs)
|
c.request(meth, path, headers=headers, **kwargs)
|
||||||
c.rx = c.getresponse()
|
c.rx = c.getresponse()
|
||||||
return c
|
return c
|
||||||
except:
|
except:
|
||||||
@@ -386,7 +386,7 @@ class Gateway(object):
|
|||||||
path = dewin(path)
|
path = dewin(path)
|
||||||
|
|
||||||
web_path = self.quotep("/" + "/".join([self.web_root, path])) + "?dots"
|
web_path = self.quotep("/" + "/".join([self.web_root, path])) + "?dots"
|
||||||
c = self.sendreq("GET", web_path)
|
c = self.sendreq("GET", web_path, {})
|
||||||
if c.rx.status != 200:
|
if c.rx.status != 200:
|
||||||
self.closeconn(c)
|
self.closeconn(c)
|
||||||
log(
|
log(
|
||||||
@@ -440,7 +440,7 @@ class Gateway(object):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
c = self.sendreq("GET", web_path, headers={"Range": hdr_range})
|
c = self.sendreq("GET", web_path, {"Range": hdr_range})
|
||||||
if c.rx.status != http.client.PARTIAL_CONTENT:
|
if c.rx.status != http.client.PARTIAL_CONTENT:
|
||||||
self.closeconn(c)
|
self.closeconn(c)
|
||||||
raise Exception(
|
raise Exception(
|
||||||
|
|||||||
@@ -54,10 +54,13 @@ MACOS = platform.system() == "Darwin"
|
|||||||
info = log = dbg = None
|
info = log = dbg = None
|
||||||
|
|
||||||
|
|
||||||
print("{} v{} @ {}".format(
|
print(
|
||||||
|
"{} v{} @ {}".format(
|
||||||
platform.python_implementation(),
|
platform.python_implementation(),
|
||||||
".".join([str(x) for x in sys.version_info]),
|
".".join([str(x) for x in sys.version_info]),
|
||||||
sys.executable))
|
sys.executable,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -299,14 +302,14 @@ class Gateway(object):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def sendreq(self, *args, headers={}, **kwargs):
|
def sendreq(self, meth, path, headers, **kwargs):
|
||||||
tid = get_tid()
|
tid = get_tid()
|
||||||
if self.password:
|
if self.password:
|
||||||
headers["Cookie"] = "=".join(["cppwd", self.password])
|
headers["Cookie"] = "=".join(["cppwd", self.password])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
c = self.getconn(tid)
|
c = self.getconn(tid)
|
||||||
c.request(*list(args), headers=headers, **kwargs)
|
c.request(meth, path, headers=headers, **kwargs)
|
||||||
return c.getresponse()
|
return c.getresponse()
|
||||||
except:
|
except:
|
||||||
dbg("bad conn")
|
dbg("bad conn")
|
||||||
@@ -314,7 +317,7 @@ class Gateway(object):
|
|||||||
self.closeconn(tid)
|
self.closeconn(tid)
|
||||||
try:
|
try:
|
||||||
c = self.getconn(tid)
|
c = self.getconn(tid)
|
||||||
c.request(*list(args), headers=headers, **kwargs)
|
c.request(meth, path, headers=headers, **kwargs)
|
||||||
return c.getresponse()
|
return c.getresponse()
|
||||||
except:
|
except:
|
||||||
info("http connection failed:\n" + traceback.format_exc())
|
info("http connection failed:\n" + traceback.format_exc())
|
||||||
@@ -331,7 +334,7 @@ class Gateway(object):
|
|||||||
path = dewin(path)
|
path = dewin(path)
|
||||||
|
|
||||||
web_path = self.quotep("/" + "/".join([self.web_root, path])) + "?dots&ls"
|
web_path = self.quotep("/" + "/".join([self.web_root, path])) + "?dots&ls"
|
||||||
r = self.sendreq("GET", web_path)
|
r = self.sendreq("GET", web_path, {})
|
||||||
if r.status != 200:
|
if r.status != 200:
|
||||||
self.closeconn()
|
self.closeconn()
|
||||||
log(
|
log(
|
||||||
@@ -368,7 +371,7 @@ class Gateway(object):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
r = self.sendreq("GET", web_path, headers={"Range": hdr_range})
|
r = self.sendreq("GET", web_path, {"Range": hdr_range})
|
||||||
if r.status != http.client.PARTIAL_CONTENT:
|
if r.status != http.client.PARTIAL_CONTENT:
|
||||||
self.closeconn()
|
self.closeconn()
|
||||||
raise Exception(
|
raise Exception(
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ def main():
|
|||||||
try:
|
try:
|
||||||
det(tf)
|
det(tf)
|
||||||
except:
|
except:
|
||||||
pass
|
pass # mute
|
||||||
finally:
|
finally:
|
||||||
os.unlink(tf)
|
os.unlink(tf)
|
||||||
|
|
||||||
|
|||||||
123
bin/mtag/audio-key-slicing.py
Executable file
123
bin/mtag/audio-key-slicing.py
Executable file
@@ -0,0 +1,123 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import subprocess as sp
|
||||||
|
|
||||||
|
import keyfinder
|
||||||
|
|
||||||
|
from copyparty.util import fsenc
|
||||||
|
|
||||||
|
"""
|
||||||
|
dep: github/mixxxdj/libkeyfinder
|
||||||
|
dep: pypi/keyfinder
|
||||||
|
dep: ffmpeg
|
||||||
|
|
||||||
|
note: this is a janky edition of the regular audio-key.py,
|
||||||
|
slicing the files at 20sec intervals and keeping 5sec from each,
|
||||||
|
surprisingly accurate but still garbage (446 ok, 69 bad, 13% miss)
|
||||||
|
|
||||||
|
it is fast tho
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def get_duration():
|
||||||
|
# TODO provide ffprobe tags to mtp as json
|
||||||
|
|
||||||
|
# fmt: off
|
||||||
|
dur = sp.check_output([
|
||||||
|
"ffprobe",
|
||||||
|
"-hide_banner",
|
||||||
|
"-v", "fatal",
|
||||||
|
"-show_streams",
|
||||||
|
"-show_format",
|
||||||
|
fsenc(sys.argv[1])
|
||||||
|
])
|
||||||
|
# fmt: on
|
||||||
|
|
||||||
|
dur = dur.decode("ascii", "replace").split("\n")
|
||||||
|
dur = [x.split("=")[1] for x in dur if x.startswith("duration=")]
|
||||||
|
dur = [float(x) for x in dur if re.match(r"^[0-9\.,]+$", x)]
|
||||||
|
return list(sorted(dur))[-1] if dur else None
|
||||||
|
|
||||||
|
|
||||||
|
def get_segs(dur):
|
||||||
|
# keep first 5s of each 20s,
|
||||||
|
# keep entire last segment
|
||||||
|
ofs = 0
|
||||||
|
segs = []
|
||||||
|
while True:
|
||||||
|
seg = [ofs, 5]
|
||||||
|
segs.append(seg)
|
||||||
|
if dur - ofs < 20:
|
||||||
|
seg[-1] = int(dur - seg[0])
|
||||||
|
break
|
||||||
|
|
||||||
|
ofs += 20
|
||||||
|
|
||||||
|
return segs
|
||||||
|
|
||||||
|
|
||||||
|
def slice(tf):
|
||||||
|
dur = get_duration()
|
||||||
|
dur = min(dur, 600) # max 10min
|
||||||
|
segs = get_segs(dur)
|
||||||
|
|
||||||
|
# fmt: off
|
||||||
|
cmd = [
|
||||||
|
"ffmpeg",
|
||||||
|
"-nostdin",
|
||||||
|
"-hide_banner",
|
||||||
|
"-v", "fatal",
|
||||||
|
"-y"
|
||||||
|
]
|
||||||
|
|
||||||
|
for seg in segs:
|
||||||
|
cmd.extend([
|
||||||
|
"-ss", str(seg[0]),
|
||||||
|
"-i", fsenc(sys.argv[1])
|
||||||
|
])
|
||||||
|
|
||||||
|
filt = ""
|
||||||
|
for n, seg in enumerate(segs):
|
||||||
|
filt += "[{}:a:0]atrim=duration={}[a{}]; ".format(n, seg[1], n)
|
||||||
|
|
||||||
|
prev = "a0"
|
||||||
|
for n in range(1, len(segs)):
|
||||||
|
nxt = "b{}".format(n)
|
||||||
|
filt += "[{}][a{}]acrossfade=d=0.5[{}]; ".format(prev, n, nxt)
|
||||||
|
prev = nxt
|
||||||
|
|
||||||
|
cmd.extend([
|
||||||
|
"-filter_complex", filt[:-2],
|
||||||
|
"-map", "[{}]".format(nxt),
|
||||||
|
"-sample_fmt", "s16",
|
||||||
|
tf
|
||||||
|
])
|
||||||
|
# fmt: on
|
||||||
|
|
||||||
|
# print(cmd)
|
||||||
|
sp.check_call(cmd)
|
||||||
|
|
||||||
|
|
||||||
|
def det(tf):
|
||||||
|
slice(tf)
|
||||||
|
print(keyfinder.key(tf).camelot())
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".flac", delete=False) as f:
|
||||||
|
f.write(b"h")
|
||||||
|
tf = f.name
|
||||||
|
|
||||||
|
try:
|
||||||
|
det(tf)
|
||||||
|
finally:
|
||||||
|
os.unlink(tf)
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -1,18 +1,54 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import subprocess as sp
|
||||||
import keyfinder
|
import keyfinder
|
||||||
|
|
||||||
|
from copyparty.util import fsenc
|
||||||
|
|
||||||
"""
|
"""
|
||||||
dep: github/mixxxdj/libkeyfinder
|
dep: github/mixxxdj/libkeyfinder
|
||||||
dep: pypi/keyfinder
|
dep: pypi/keyfinder
|
||||||
dep: ffmpeg
|
dep: ffmpeg
|
||||||
|
|
||||||
note: cannot fsenc
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
# tried trimming the first/last 5th, bad idea,
|
||||||
|
# misdetects 9a law field (Sphere Caliber) as 10b,
|
||||||
|
# obvious when mixing 9a ghostly parapara ship
|
||||||
|
|
||||||
|
|
||||||
|
def det(tf):
|
||||||
|
# fmt: off
|
||||||
|
sp.check_call([
|
||||||
|
"ffmpeg",
|
||||||
|
"-nostdin",
|
||||||
|
"-hide_banner",
|
||||||
|
"-v", "fatal",
|
||||||
|
"-y", "-i", fsenc(sys.argv[1]),
|
||||||
|
"-t", "300",
|
||||||
|
"-sample_fmt", "s16",
|
||||||
|
tf
|
||||||
|
])
|
||||||
|
# fmt: on
|
||||||
|
|
||||||
|
print(keyfinder.key(tf).camelot())
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
with tempfile.NamedTemporaryFile(suffix=".flac", delete=False) as f:
|
||||||
|
f.write(b"h")
|
||||||
|
tf = f.name
|
||||||
|
|
||||||
try:
|
try:
|
||||||
print(keyfinder.key(sys.argv[1]).camelot())
|
det(tf)
|
||||||
except:
|
except:
|
||||||
pass
|
pass # mute
|
||||||
|
finally:
|
||||||
|
os.unlink(tf)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|||||||
@@ -1,7 +1,15 @@
|
|||||||
# when running copyparty behind a reverse-proxy,
|
# when running copyparty behind a reverse proxy,
|
||||||
# make sure that copyparty allows at least as many clients as the proxy does,
|
# the following arguments are recommended:
|
||||||
# so run copyparty with -nc 512 if your nginx has the default limits
|
#
|
||||||
# (worker_processes 1, worker_connections 512)
|
# -nc 512 important, see next paragraph
|
||||||
|
# --http-only lower latency on initial connection
|
||||||
|
# -i 127.0.0.1 only accept connections from nginx
|
||||||
|
#
|
||||||
|
# -nc must match or exceed the webserver's max number of concurrent clients;
|
||||||
|
# nginx default is 512 (worker_processes 1, worker_connections 512)
|
||||||
|
#
|
||||||
|
# you may also consider adding -j0 for CPU-intensive configurations
|
||||||
|
# (not that i can really think of any good examples)
|
||||||
|
|
||||||
upstream cpp {
|
upstream cpp {
|
||||||
server 127.0.0.1:3923;
|
server 127.0.0.1:3923;
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ import os
|
|||||||
PY2 = sys.version_info[0] == 2
|
PY2 = sys.version_info[0] == 2
|
||||||
if PY2:
|
if PY2:
|
||||||
sys.dont_write_bytecode = True
|
sys.dont_write_bytecode = True
|
||||||
|
unicode = unicode
|
||||||
|
else:
|
||||||
|
unicode = str
|
||||||
|
|
||||||
WINDOWS = False
|
WINDOWS = False
|
||||||
if platform.system() == "Windows":
|
if platform.system() == "Windows":
|
||||||
|
|||||||
@@ -20,10 +20,10 @@ import threading
|
|||||||
import traceback
|
import traceback
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
|
|
||||||
from .__init__ import E, WINDOWS, VT100, PY2
|
from .__init__ import E, WINDOWS, VT100, PY2, unicode
|
||||||
from .__version__ import S_VERSION, S_BUILD_DT, CODENAME
|
from .__version__ import S_VERSION, S_BUILD_DT, CODENAME
|
||||||
from .svchub import SvcHub
|
from .svchub import SvcHub
|
||||||
from .util import py_desc, align_tab, IMPLICATIONS, alltrace
|
from .util import py_desc, align_tab, IMPLICATIONS
|
||||||
|
|
||||||
HAVE_SSL = True
|
HAVE_SSL = True
|
||||||
try:
|
try:
|
||||||
@@ -31,6 +31,8 @@ try:
|
|||||||
except:
|
except:
|
||||||
HAVE_SSL = False
|
HAVE_SSL = False
|
||||||
|
|
||||||
|
printed = ""
|
||||||
|
|
||||||
|
|
||||||
class RiceFormatter(argparse.HelpFormatter):
|
class RiceFormatter(argparse.HelpFormatter):
|
||||||
def _get_help_string(self, action):
|
def _get_help_string(self, action):
|
||||||
@@ -61,8 +63,15 @@ class Dodge11874(RiceFormatter):
|
|||||||
super(Dodge11874, self).__init__(*args, **kwargs)
|
super(Dodge11874, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def lprint(*a, **ka):
|
||||||
|
global printed
|
||||||
|
|
||||||
|
printed += " ".join(unicode(x) for x in a) + ka.get("end", "\n")
|
||||||
|
print(*a, **ka)
|
||||||
|
|
||||||
|
|
||||||
def warn(msg):
|
def warn(msg):
|
||||||
print("\033[1mwarning:\033[0;33m {}\033[0m\n".format(msg))
|
lprint("\033[1mwarning:\033[0;33m {}\033[0m\n".format(msg))
|
||||||
|
|
||||||
|
|
||||||
def ensure_locale():
|
def ensure_locale():
|
||||||
@@ -73,7 +82,7 @@ def ensure_locale():
|
|||||||
]:
|
]:
|
||||||
try:
|
try:
|
||||||
locale.setlocale(locale.LC_ALL, x)
|
locale.setlocale(locale.LC_ALL, x)
|
||||||
print("Locale:", x)
|
lprint("Locale:", x)
|
||||||
break
|
break
|
||||||
except:
|
except:
|
||||||
continue
|
continue
|
||||||
@@ -94,7 +103,7 @@ def ensure_cert():
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
if filecmp.cmp(cert_cfg, cert_insec):
|
if filecmp.cmp(cert_cfg, cert_insec):
|
||||||
print(
|
lprint(
|
||||||
"\033[33m using default TLS certificate; https will be insecure."
|
"\033[33m using default TLS certificate; https will be insecure."
|
||||||
+ "\033[36m\n certificate location: {}\033[0m\n".format(cert_cfg)
|
+ "\033[36m\n certificate location: {}\033[0m\n".format(cert_cfg)
|
||||||
)
|
)
|
||||||
@@ -123,7 +132,7 @@ def configure_ssl_ver(al):
|
|||||||
if "help" in sslver:
|
if "help" in sslver:
|
||||||
avail = [terse_sslver(x[6:]) for x in flags]
|
avail = [terse_sslver(x[6:]) for x in flags]
|
||||||
avail = " ".join(sorted(avail) + ["all"])
|
avail = " ".join(sorted(avail) + ["all"])
|
||||||
print("\navailable ssl/tls versions:\n " + avail)
|
lprint("\navailable ssl/tls versions:\n " + avail)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
al.ssl_flags_en = 0
|
al.ssl_flags_en = 0
|
||||||
@@ -143,7 +152,7 @@ def configure_ssl_ver(al):
|
|||||||
|
|
||||||
for k in ["ssl_flags_en", "ssl_flags_de"]:
|
for k in ["ssl_flags_en", "ssl_flags_de"]:
|
||||||
num = getattr(al, k)
|
num = getattr(al, k)
|
||||||
print("{}: {:8x} ({})".format(k, num, num))
|
lprint("{}: {:8x} ({})".format(k, num, num))
|
||||||
|
|
||||||
# think i need that beer now
|
# think i need that beer now
|
||||||
|
|
||||||
@@ -160,13 +169,13 @@ def configure_ssl_ciphers(al):
|
|||||||
try:
|
try:
|
||||||
ctx.set_ciphers(al.ciphers)
|
ctx.set_ciphers(al.ciphers)
|
||||||
except:
|
except:
|
||||||
print("\n\033[1;31mfailed to set ciphers\033[0m\n")
|
lprint("\n\033[1;31mfailed to set ciphers\033[0m\n")
|
||||||
|
|
||||||
if not hasattr(ctx, "get_ciphers"):
|
if not hasattr(ctx, "get_ciphers"):
|
||||||
print("cannot read cipher list: openssl or python too old")
|
lprint("cannot read cipher list: openssl or python too old")
|
||||||
else:
|
else:
|
||||||
ciphers = [x["description"] for x in ctx.get_ciphers()]
|
ciphers = [x["description"] for x in ctx.get_ciphers()]
|
||||||
print("\n ".join(["\nenabled ciphers:"] + align_tab(ciphers) + [""]))
|
lprint("\n ".join(["\nenabled ciphers:"] + align_tab(ciphers) + [""]))
|
||||||
|
|
||||||
if is_help:
|
if is_help:
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
@@ -182,16 +191,6 @@ def sighandler(sig=None, frame=None):
|
|||||||
print("\n".join(msg))
|
print("\n".join(msg))
|
||||||
|
|
||||||
|
|
||||||
def stackmon(fp, ival):
|
|
||||||
ctr = 0
|
|
||||||
while True:
|
|
||||||
ctr += 1
|
|
||||||
time.sleep(ival)
|
|
||||||
st = "{}, {}\n{}".format(ctr, time.time(), alltrace())
|
|
||||||
with open(fp, "wb") as f:
|
|
||||||
f.write(st.encode("utf-8", "replace"))
|
|
||||||
|
|
||||||
|
|
||||||
def run_argparse(argv, formatter):
|
def run_argparse(argv, formatter):
|
||||||
ap = argparse.ArgumentParser(
|
ap = argparse.ArgumentParser(
|
||||||
formatter_class=formatter,
|
formatter_class=formatter,
|
||||||
@@ -249,30 +248,32 @@ def run_argparse(argv, formatter):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
# fmt: off
|
# fmt: off
|
||||||
ap.add_argument("-c", metavar="PATH", type=str, action="append", help="add config file")
|
u = unicode
|
||||||
ap.add_argument("-nc", metavar="NUM", type=int, default=64, help="max num clients")
|
ap2 = ap.add_argument_group('general options')
|
||||||
ap.add_argument("-j", metavar="CORES", type=int, default=1, help="max num cpu cores")
|
ap2.add_argument("-c", metavar="PATH", type=u, action="append", help="add config file")
|
||||||
ap.add_argument("-a", metavar="ACCT", type=str, action="append", help="add account, USER:PASS; example [ed:wark")
|
ap2.add_argument("-nc", metavar="NUM", type=int, default=64, help="max num clients")
|
||||||
ap.add_argument("-v", metavar="VOL", type=str, action="append", help="add volume, SRC:DST:FLAG; example [.::r], [/mnt/nas/music:/music:r:aed")
|
ap2.add_argument("-j", metavar="CORES", type=int, default=1, help="max num cpu cores")
|
||||||
ap.add_argument("-ed", action="store_true", help="enable ?dots")
|
ap2.add_argument("-a", metavar="ACCT", type=u, action="append", help="add account, USER:PASS; example [ed:wark")
|
||||||
ap.add_argument("-emp", action="store_true", help="enable markdown plugins")
|
ap2.add_argument("-v", metavar="VOL", type=u, action="append", help="add volume, SRC:DST:FLAG; example [.::r], [/mnt/nas/music:/music:r:aed")
|
||||||
ap.add_argument("-mcr", metavar="SEC", type=int, default=60, help="md-editor mod-chk rate")
|
ap2.add_argument("-ed", action="store_true", help="enable ?dots")
|
||||||
ap.add_argument("--dotpart", action="store_true", help="dotfile incomplete uploads")
|
ap2.add_argument("-emp", action="store_true", help="enable markdown plugins")
|
||||||
ap.add_argument("--sparse", metavar="MiB", type=int, default=4, help="up2k min.size threshold (mswin-only)")
|
ap2.add_argument("-mcr", metavar="SEC", type=int, default=60, help="md-editor mod-chk rate")
|
||||||
ap.add_argument("--urlform", metavar="MODE", type=str, default="print,get", help="how to handle url-forms; examples: [stash], [save,get]")
|
ap2.add_argument("--dotpart", action="store_true", help="dotfile incomplete uploads")
|
||||||
|
ap2.add_argument("--sparse", metavar="MiB", type=int, default=4, help="up2k min.size threshold (mswin-only)")
|
||||||
|
ap2.add_argument("--urlform", metavar="MODE", type=u, default="print,get", help="how to handle url-forms; examples: [stash], [save,get]")
|
||||||
|
|
||||||
ap2 = ap.add_argument_group('network options')
|
ap2 = ap.add_argument_group('network options')
|
||||||
ap2.add_argument("-i", metavar="IP", type=str, default="0.0.0.0", help="ip to bind (comma-sep.)")
|
ap2.add_argument("-i", metavar="IP", type=u, default="0.0.0.0", help="ip to bind (comma-sep.)")
|
||||||
ap2.add_argument("-p", metavar="PORT", type=str, default="3923", help="ports to bind (comma/range)")
|
ap2.add_argument("-p", metavar="PORT", type=u, default="3923", help="ports to bind (comma/range)")
|
||||||
ap2.add_argument("--rproxy", metavar="DEPTH", type=int, default=1, help="which ip to keep; 0 = tcp, 1 = origin (first x-fwd), 2 = cloudflare, 3 = nginx, -1 = closest proxy")
|
ap2.add_argument("--rproxy", metavar="DEPTH", type=int, default=1, help="which ip to keep; 0 = tcp, 1 = origin (first x-fwd), 2 = cloudflare, 3 = nginx, -1 = closest proxy")
|
||||||
|
|
||||||
ap2 = ap.add_argument_group('SSL/TLS options')
|
ap2 = ap.add_argument_group('SSL/TLS options')
|
||||||
ap2.add_argument("--http-only", action="store_true", help="disable ssl/tls")
|
ap2.add_argument("--http-only", action="store_true", help="disable ssl/tls")
|
||||||
ap2.add_argument("--https-only", action="store_true", help="disable plaintext")
|
ap2.add_argument("--https-only", action="store_true", help="disable plaintext")
|
||||||
ap2.add_argument("--ssl-ver", metavar="LIST", type=str, help="set allowed ssl/tls versions; [help] shows available versions; default is what your python version considers safe")
|
ap2.add_argument("--ssl-ver", metavar="LIST", type=u, help="set allowed ssl/tls versions; [help] shows available versions; default is what your python version considers safe")
|
||||||
ap2.add_argument("--ciphers", metavar="LIST", help="set allowed ssl/tls ciphers; [help] shows available ciphers")
|
ap2.add_argument("--ciphers", metavar="LIST", type=u, help="set allowed ssl/tls ciphers; [help] shows available ciphers")
|
||||||
ap2.add_argument("--ssl-dbg", action="store_true", help="dump some tls info")
|
ap2.add_argument("--ssl-dbg", action="store_true", help="dump some tls info")
|
||||||
ap2.add_argument("--ssl-log", metavar="PATH", help="log master secrets")
|
ap2.add_argument("--ssl-log", metavar="PATH", type=u, help="log master secrets")
|
||||||
|
|
||||||
ap2 = ap.add_argument_group('opt-outs')
|
ap2 = ap.add_argument_group('opt-outs')
|
||||||
ap2.add_argument("-nw", action="store_true", help="disable writes (benchmark)")
|
ap2.add_argument("-nw", action="store_true", help="disable writes (benchmark)")
|
||||||
@@ -281,14 +282,16 @@ def run_argparse(argv, formatter):
|
|||||||
ap2.add_argument("--no-zip", action="store_true", help="disable download as zip/tar")
|
ap2.add_argument("--no-zip", action="store_true", help="disable download as zip/tar")
|
||||||
|
|
||||||
ap2 = ap.add_argument_group('safety options')
|
ap2 = ap.add_argument_group('safety options')
|
||||||
ap2.add_argument("--ls", metavar="U[,V[,F]]", help="scan all volumes; arguments USER,VOL,FLAGS; example [**,*,ln,p,r]")
|
ap2.add_argument("--ls", metavar="U[,V[,F]]", type=u, help="scan all volumes; arguments USER,VOL,FLAGS; example [**,*,ln,p,r]")
|
||||||
ap2.add_argument("--salt", type=str, default="hunter2", help="up2k file-hash salt")
|
ap2.add_argument("--salt", type=u, default="hunter2", help="up2k file-hash salt")
|
||||||
|
|
||||||
ap2 = ap.add_argument_group('logging options')
|
ap2 = ap.add_argument_group('logging options')
|
||||||
ap2.add_argument("-q", action="store_true", help="quiet")
|
ap2.add_argument("-q", action="store_true", help="quiet")
|
||||||
|
ap2.add_argument("-lo", metavar="PATH", type=u, help="logfile, example: cpp-%%Y-%%m%%d-%%H%%M%%S.txt.xz")
|
||||||
ap2.add_argument("--log-conn", action="store_true", help="print tcp-server msgs")
|
ap2.add_argument("--log-conn", action="store_true", help="print tcp-server msgs")
|
||||||
ap2.add_argument("--ihead", metavar="HEADER", action='append', help="dump incoming header")
|
ap2.add_argument("--log-htp", action="store_true", help="print http-server threadpool scaling")
|
||||||
ap2.add_argument("--lf-url", metavar="RE", type=str, default=r"^/\.cpr/|\?th=[wj]$", help="dont log URLs matching")
|
ap2.add_argument("--ihead", metavar="HEADER", type=u, action='append', help="dump incoming header")
|
||||||
|
ap2.add_argument("--lf-url", metavar="RE", type=u, default=r"^/\.cpr/|\?th=[wj]$", help="dont log URLs matching")
|
||||||
|
|
||||||
ap2 = ap.add_argument_group('admin panel options')
|
ap2 = ap.add_argument_group('admin panel options')
|
||||||
ap2.add_argument("--no-rescan", action="store_true", help="disable ?scan (volume reindexing)")
|
ap2.add_argument("--no-rescan", action="store_true", help="disable ?scan (volume reindexing)")
|
||||||
@@ -303,8 +306,9 @@ def run_argparse(argv, formatter):
|
|||||||
ap2.add_argument("--th-no-webp", action="store_true", help="disable webp output")
|
ap2.add_argument("--th-no-webp", action="store_true", help="disable webp output")
|
||||||
ap2.add_argument("--th-ff-jpg", action="store_true", help="force jpg for video thumbs")
|
ap2.add_argument("--th-ff-jpg", action="store_true", help="force jpg for video thumbs")
|
||||||
ap2.add_argument("--th-poke", metavar="SEC", type=int, default=300, help="activity labeling cooldown")
|
ap2.add_argument("--th-poke", metavar="SEC", type=int, default=300, help="activity labeling cooldown")
|
||||||
ap2.add_argument("--th-clean", metavar="SEC", type=int, default=43200, help="cleanup interval")
|
ap2.add_argument("--th-clean", metavar="SEC", type=int, default=43200, help="cleanup interval; 0=disabled")
|
||||||
ap2.add_argument("--th-maxage", metavar="SEC", type=int, default=604800, help="max folder age")
|
ap2.add_argument("--th-maxage", metavar="SEC", type=int, default=604800, help="max folder age")
|
||||||
|
ap2.add_argument("--th-covers", metavar="N,N", type=u, default="folder.png,folder.jpg,cover.png,cover.jpg", help="folder thumbnails to stat for")
|
||||||
|
|
||||||
ap2 = ap.add_argument_group('database options')
|
ap2 = ap.add_argument_group('database options')
|
||||||
ap2.add_argument("-e2d", action="store_true", help="enable up2k database")
|
ap2.add_argument("-e2d", action="store_true", help="enable up2k database")
|
||||||
@@ -313,24 +317,26 @@ def run_argparse(argv, formatter):
|
|||||||
ap2.add_argument("-e2t", action="store_true", help="enable metadata indexing")
|
ap2.add_argument("-e2t", action="store_true", help="enable metadata indexing")
|
||||||
ap2.add_argument("-e2ts", action="store_true", help="enable metadata scanner, sets -e2t")
|
ap2.add_argument("-e2ts", action="store_true", help="enable metadata scanner, sets -e2t")
|
||||||
ap2.add_argument("-e2tsr", action="store_true", help="rescan all metadata, sets -e2ts")
|
ap2.add_argument("-e2tsr", action="store_true", help="rescan all metadata, sets -e2ts")
|
||||||
ap2.add_argument("--hist", metavar="PATH", type=str, help="where to store volume state")
|
ap2.add_argument("--hist", metavar="PATH", type=u, help="where to store volume state")
|
||||||
ap2.add_argument("--no-hash", action="store_true", help="disable hashing during e2ds folder scans")
|
ap2.add_argument("--no-hash", action="store_true", help="disable hashing during e2ds folder scans")
|
||||||
ap2.add_argument("--no-mutagen", action="store_true", help="use ffprobe for tags instead")
|
ap2.add_argument("--no-mutagen", action="store_true", help="use ffprobe for tags instead")
|
||||||
ap2.add_argument("--no-mtag-mt", action="store_true", help="disable tag-read parallelism")
|
ap2.add_argument("--no-mtag-mt", action="store_true", help="disable tag-read parallelism")
|
||||||
ap2.add_argument("-mtm", metavar="M=t,t,t", action="append", type=str, help="add/replace metadata mapping")
|
ap2.add_argument("-mtm", metavar="M=t,t,t", type=u, action="append", help="add/replace metadata mapping")
|
||||||
ap2.add_argument("-mte", metavar="M,M,M", type=str, help="tags to index/display (comma-sep.)",
|
ap2.add_argument("-mte", metavar="M,M,M", type=u, help="tags to index/display (comma-sep.)",
|
||||||
default="circle,album,.tn,artist,title,.bpm,key,.dur,.q,.vq,.aq,ac,vc,res,.fps")
|
default="circle,album,.tn,artist,title,.bpm,key,.dur,.q,.vq,.aq,ac,vc,res,.fps")
|
||||||
ap2.add_argument("-mtp", metavar="M=[f,]bin", action="append", type=str, help="read tag M using bin")
|
ap2.add_argument("-mtp", metavar="M=[f,]bin", type=u, action="append", help="read tag M using bin")
|
||||||
ap2.add_argument("--srch-time", metavar="SEC", type=int, default=30, help="search deadline")
|
ap2.add_argument("--srch-time", metavar="SEC", type=int, default=30, help="search deadline")
|
||||||
|
|
||||||
ap2 = ap.add_argument_group('appearance options')
|
ap2 = ap.add_argument_group('appearance options')
|
||||||
ap2.add_argument("--css-browser", metavar="L", help="URL to additional CSS to include")
|
ap2.add_argument("--css-browser", metavar="L", type=u, help="URL to additional CSS to include")
|
||||||
|
|
||||||
ap2 = ap.add_argument_group('debug options')
|
ap2 = ap.add_argument_group('debug options')
|
||||||
ap2.add_argument("--no-sendfile", action="store_true", help="disable sendfile")
|
ap2.add_argument("--no-sendfile", action="store_true", help="disable sendfile")
|
||||||
ap2.add_argument("--no-scandir", action="store_true", help="disable scandir")
|
ap2.add_argument("--no-scandir", action="store_true", help="disable scandir")
|
||||||
ap2.add_argument("--no-fastboot", action="store_true", help="wait for up2k indexing")
|
ap2.add_argument("--no-fastboot", action="store_true", help="wait for up2k indexing")
|
||||||
ap2.add_argument("--stackmon", metavar="P,S", help="write stacktrace to Path every S second")
|
ap2.add_argument("--no-htp", action="store_true", help="disable httpserver threadpool, create threads as-needed instead")
|
||||||
|
ap2.add_argument("--stackmon", metavar="P,S", type=u, help="write stacktrace to Path every S second")
|
||||||
|
ap2.add_argument("--log-thrs", metavar="SEC", type=float, help="list active threads every SEC")
|
||||||
|
|
||||||
return ap.parse_args(args=argv[1:])
|
return ap.parse_args(args=argv[1:])
|
||||||
# fmt: on
|
# fmt: on
|
||||||
@@ -347,7 +353,7 @@ def main(argv=None):
|
|||||||
desc = py_desc().replace("[", "\033[1;30m[")
|
desc = py_desc().replace("[", "\033[1;30m[")
|
||||||
|
|
||||||
f = '\033[36mcopyparty v{} "\033[35m{}\033[36m" ({})\n{}\033[0m\n'
|
f = '\033[36mcopyparty v{} "\033[35m{}\033[36m" ({})\n{}\033[0m\n'
|
||||||
print(f.format(S_VERSION, CODENAME, S_BUILD_DT, desc))
|
lprint(f.format(S_VERSION, CODENAME, S_BUILD_DT, desc))
|
||||||
|
|
||||||
ensure_locale()
|
ensure_locale()
|
||||||
if HAVE_SSL:
|
if HAVE_SSL:
|
||||||
@@ -361,7 +367,7 @@ def main(argv=None):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
msg = "\033[1;31mWARNING:\033[0;1m\n {} \033[0;33mwas replaced with\033[0;1m {} \033[0;33mand will be removed\n\033[0m"
|
msg = "\033[1;31mWARNING:\033[0;1m\n {} \033[0;33mwas replaced with\033[0;1m {} \033[0;33mand will be removed\n\033[0m"
|
||||||
print(msg.format(dk, nk))
|
lprint(msg.format(dk, nk))
|
||||||
argv[idx] = nk
|
argv[idx] = nk
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
|
|
||||||
@@ -370,16 +376,6 @@ def main(argv=None):
|
|||||||
except AssertionError:
|
except AssertionError:
|
||||||
al = run_argparse(argv, Dodge11874)
|
al = run_argparse(argv, Dodge11874)
|
||||||
|
|
||||||
if al.stackmon:
|
|
||||||
fp, f = al.stackmon.rsplit(",", 1)
|
|
||||||
f = int(f)
|
|
||||||
t = threading.Thread(
|
|
||||||
target=stackmon,
|
|
||||||
args=(fp, f),
|
|
||||||
)
|
|
||||||
t.daemon = True
|
|
||||||
t.start()
|
|
||||||
|
|
||||||
# propagate implications
|
# propagate implications
|
||||||
for k1, k2 in IMPLICATIONS:
|
for k1, k2 in IMPLICATIONS:
|
||||||
if getattr(al, k1):
|
if getattr(al, k1):
|
||||||
@@ -415,7 +411,7 @@ def main(argv=None):
|
|||||||
|
|
||||||
# signal.signal(signal.SIGINT, sighandler)
|
# signal.signal(signal.SIGINT, sighandler)
|
||||||
|
|
||||||
SvcHub(al).run()
|
SvcHub(al, argv, printed).run()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
# coding: utf-8
|
# coding: utf-8
|
||||||
|
|
||||||
VERSION = (0, 11, 24)
|
VERSION = (0, 11, 39)
|
||||||
CODENAME = "the grid"
|
CODENAME = "the grid"
|
||||||
BUILD_DT = (2021, 6, 22)
|
BUILD_DT = (2021, 7, 13)
|
||||||
|
|
||||||
S_VERSION = ".".join(map(str, VERSION))
|
S_VERSION = ".".join(map(str, VERSION))
|
||||||
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
|
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
|
||||||
|
|||||||
@@ -10,13 +10,14 @@ import hashlib
|
|||||||
import threading
|
import threading
|
||||||
|
|
||||||
from .__init__ import WINDOWS
|
from .__init__ import WINDOWS
|
||||||
from .util import IMPLICATIONS, uncyg, undot, Pebkac, fsdec, fsenc, statdir, nuprint
|
from .util import IMPLICATIONS, uncyg, undot, Pebkac, fsdec, fsenc, statdir
|
||||||
|
|
||||||
|
|
||||||
class VFS(object):
|
class VFS(object):
|
||||||
"""single level in the virtual fs"""
|
"""single level in the virtual fs"""
|
||||||
|
|
||||||
def __init__(self, realpath, vpath, uread=[], uwrite=[], uadm=[], flags={}):
|
def __init__(self, log, realpath, vpath, uread, uwrite, uadm, flags):
|
||||||
|
self.log = log
|
||||||
self.realpath = realpath # absolute path on host filesystem
|
self.realpath = realpath # absolute path on host filesystem
|
||||||
self.vpath = vpath # absolute path in the virtual filesystem
|
self.vpath = vpath # absolute path in the virtual filesystem
|
||||||
self.uread = uread # users who can read this
|
self.uread = uread # users who can read this
|
||||||
@@ -62,6 +63,7 @@ class VFS(object):
|
|||||||
return self.nodes[name].add(src, dst)
|
return self.nodes[name].add(src, dst)
|
||||||
|
|
||||||
vn = VFS(
|
vn = VFS(
|
||||||
|
self.log,
|
||||||
os.path.join(self.realpath, name) if self.realpath else None,
|
os.path.join(self.realpath, name) if self.realpath else None,
|
||||||
"{}/{}".format(self.vpath, name).lstrip("/"),
|
"{}/{}".format(self.vpath, name).lstrip("/"),
|
||||||
self.uread,
|
self.uread,
|
||||||
@@ -79,7 +81,7 @@ class VFS(object):
|
|||||||
|
|
||||||
# leaf does not exist; create and keep permissions blank
|
# leaf does not exist; create and keep permissions blank
|
||||||
vp = "{}/{}".format(self.vpath, dst).lstrip("/")
|
vp = "{}/{}".format(self.vpath, dst).lstrip("/")
|
||||||
vn = VFS(src, vp)
|
vn = VFS(self.log, src, vp, [], [], [], {})
|
||||||
vn.dbv = self.dbv or self
|
vn.dbv = self.dbv or self
|
||||||
self.nodes[dst] = vn
|
self.nodes[dst] = vn
|
||||||
return vn
|
return vn
|
||||||
@@ -181,7 +183,7 @@ class VFS(object):
|
|||||||
"""return user-readable [fsdir,real,virt] items at vpath"""
|
"""return user-readable [fsdir,real,virt] items at vpath"""
|
||||||
virt_vis = {} # nodes readable by user
|
virt_vis = {} # nodes readable by user
|
||||||
abspath = self.canonical(rem)
|
abspath = self.canonical(rem)
|
||||||
real = list(statdir(nuprint, scandir, lstat, abspath))
|
real = list(statdir(self.log, scandir, lstat, abspath))
|
||||||
real.sort()
|
real.sort()
|
||||||
if not rem:
|
if not rem:
|
||||||
for name, vn2 in sorted(self.nodes.items()):
|
for name, vn2 in sorted(self.nodes.items()):
|
||||||
@@ -208,8 +210,13 @@ class VFS(object):
|
|||||||
rem, uname, scandir, incl_wo=False, lstat=lstat
|
rem, uname, scandir, incl_wo=False, lstat=lstat
|
||||||
)
|
)
|
||||||
|
|
||||||
if seen and not fsroot.startswith(seen[-1]) and fsroot in seen:
|
if (
|
||||||
print("bailing from symlink loop,\n {}\n {}".format(seen[-1], fsroot))
|
seen
|
||||||
|
and (not fsroot.startswith(seen[-1]) or fsroot == seen[-1])
|
||||||
|
and fsroot in seen
|
||||||
|
):
|
||||||
|
m = "bailing from symlink loop,\n prev: {}\n curr: {}\n from: {}/{}"
|
||||||
|
self.log("vfs.walk", m.format(seen[-1], fsroot, self.vpath, rem), 3)
|
||||||
return
|
return
|
||||||
|
|
||||||
seen = seen[:] + [fsroot]
|
seen = seen[:] + [fsroot]
|
||||||
@@ -242,6 +249,10 @@ class VFS(object):
|
|||||||
if flt:
|
if flt:
|
||||||
flt = {k: True for k in flt}
|
flt = {k: True for k in flt}
|
||||||
|
|
||||||
|
f1 = "{0}.hist{0}up2k.".format(os.sep)
|
||||||
|
f2a = os.sep + "dir.txt"
|
||||||
|
f2b = "{0}.hist{0}".format(os.sep)
|
||||||
|
|
||||||
for vpath, apath, files, rd, vd in self.walk(
|
for vpath, apath, files, rd, vd in self.walk(
|
||||||
"", vrem, [], uname, dots, scandir, False
|
"", vrem, [], uname, dots, scandir, False
|
||||||
):
|
):
|
||||||
@@ -275,7 +286,11 @@ class VFS(object):
|
|||||||
del vd[x]
|
del vd[x]
|
||||||
|
|
||||||
# up2k filetring based on actual abspath
|
# up2k filetring based on actual abspath
|
||||||
files = [x for x in files if "{0}.hist{0}up2k.".format(os.sep) not in x[1]]
|
files = [
|
||||||
|
x
|
||||||
|
for x in files
|
||||||
|
if f1 not in x[1] and (not x[1].endswith(f2a) or f2b not in x[1])
|
||||||
|
]
|
||||||
|
|
||||||
for f in [{"vp": v, "ap": a, "st": n[1]} for v, a, n in files]:
|
for f in [{"vp": v, "ap": a, "st": n[1]} for v, a, n in files]:
|
||||||
yield f
|
yield f
|
||||||
@@ -466,7 +481,7 @@ class AuthSrv(object):
|
|||||||
)
|
)
|
||||||
except:
|
except:
|
||||||
m = "\n\033[1;31m\nerror in config file {} on line {}:\n\033[0m"
|
m = "\n\033[1;31m\nerror in config file {} on line {}:\n\033[0m"
|
||||||
print(m.format(cfg_fn, self.line_ctr))
|
self.log(m.format(cfg_fn, self.line_ctr), 1)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
# case-insensitive; normalize
|
# case-insensitive; normalize
|
||||||
@@ -482,10 +497,10 @@ class AuthSrv(object):
|
|||||||
|
|
||||||
if not mount:
|
if not mount:
|
||||||
# -h says our defaults are CWD at root and read/write for everyone
|
# -h says our defaults are CWD at root and read/write for everyone
|
||||||
vfs = VFS(os.path.abspath("."), "", ["*"], ["*"])
|
vfs = VFS(self.log_func, os.path.abspath("."), "", ["*"], ["*"], ["*"], {})
|
||||||
elif "" not in mount:
|
elif "" not in mount:
|
||||||
# there's volumes but no root; make root inaccessible
|
# there's volumes but no root; make root inaccessible
|
||||||
vfs = VFS(None, "")
|
vfs = VFS(self.log_func, None, "", [], [], [], {})
|
||||||
vfs.flags["d2d"] = True
|
vfs.flags["d2d"] = True
|
||||||
|
|
||||||
maxdepth = 0
|
maxdepth = 0
|
||||||
@@ -497,7 +512,13 @@ class AuthSrv(object):
|
|||||||
if dst == "":
|
if dst == "":
|
||||||
# rootfs was mapped; fully replaces the default CWD vfs
|
# rootfs was mapped; fully replaces the default CWD vfs
|
||||||
vfs = VFS(
|
vfs = VFS(
|
||||||
mount[dst], dst, mread[dst], mwrite[dst], madm[dst], mflags[dst]
|
self.log_func,
|
||||||
|
mount[dst],
|
||||||
|
dst,
|
||||||
|
mread[dst],
|
||||||
|
mwrite[dst],
|
||||||
|
madm[dst],
|
||||||
|
mflags[dst],
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -693,6 +714,11 @@ class AuthSrv(object):
|
|||||||
self.user = user
|
self.user = user
|
||||||
self.iuser = {v: k for k, v in user.items()}
|
self.iuser = {v: k for k, v in user.items()}
|
||||||
|
|
||||||
|
self.re_pwd = None
|
||||||
|
pwds = [re.escape(x) for x in self.iuser.keys()]
|
||||||
|
if pwds:
|
||||||
|
self.re_pwd = re.compile("=(" + "|".join(pwds) + ")([]&; ]|$)")
|
||||||
|
|
||||||
# import pprint
|
# import pprint
|
||||||
# pprint.pprint({"usr": user, "rd": mread, "wr": mwrite, "mnt": mount})
|
# pprint.pprint({"usr": user, "rd": mread, "wr": mwrite, "mnt": mount})
|
||||||
|
|
||||||
@@ -775,7 +801,7 @@ class AuthSrv(object):
|
|||||||
msg = [x[1] for x in files]
|
msg = [x[1] for x in files]
|
||||||
|
|
||||||
if msg:
|
if msg:
|
||||||
nuprint("\n".join(msg))
|
self.log("\n" + "\n".join(msg))
|
||||||
|
|
||||||
if n_bads and flag_p:
|
if n_bads and flag_p:
|
||||||
raise Exception("found symlink leaving volume, and strict is set")
|
raise Exception("found symlink leaving volume, and strict is set")
|
||||||
|
|||||||
@@ -4,17 +4,11 @@ from __future__ import print_function, unicode_literals
|
|||||||
import time
|
import time
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
from .__init__ import PY2, WINDOWS, VT100
|
|
||||||
from .broker_util import try_exec
|
from .broker_util import try_exec
|
||||||
from .broker_mpw import MpWorker
|
from .broker_mpw import MpWorker
|
||||||
from .util import mp
|
from .util import mp
|
||||||
|
|
||||||
|
|
||||||
if PY2 and not WINDOWS:
|
|
||||||
from multiprocessing.reduction import ForkingPickler
|
|
||||||
from StringIO import StringIO as MemesIO # pylint: disable=import-error
|
|
||||||
|
|
||||||
|
|
||||||
class BrokerMp(object):
|
class BrokerMp(object):
|
||||||
"""external api; manages MpWorkers"""
|
"""external api; manages MpWorkers"""
|
||||||
|
|
||||||
@@ -33,19 +27,17 @@ class BrokerMp(object):
|
|||||||
cores = mp.cpu_count()
|
cores = mp.cpu_count()
|
||||||
|
|
||||||
self.log("broker", "booting {} subprocesses".format(cores))
|
self.log("broker", "booting {} subprocesses".format(cores))
|
||||||
for n in range(cores):
|
for n in range(1, cores + 1):
|
||||||
q_pend = mp.Queue(1)
|
q_pend = mp.Queue(1)
|
||||||
q_yield = mp.Queue(64)
|
q_yield = mp.Queue(64)
|
||||||
|
|
||||||
proc = mp.Process(target=MpWorker, args=(q_pend, q_yield, self.args, n))
|
proc = mp.Process(target=MpWorker, args=(q_pend, q_yield, self.args, n))
|
||||||
proc.q_pend = q_pend
|
proc.q_pend = q_pend
|
||||||
proc.q_yield = q_yield
|
proc.q_yield = q_yield
|
||||||
proc.nid = n
|
|
||||||
proc.clients = {}
|
proc.clients = {}
|
||||||
proc.workload = 0
|
|
||||||
|
|
||||||
thr = threading.Thread(
|
thr = threading.Thread(
|
||||||
target=self.collector, args=(proc,), name="mp-collector"
|
target=self.collector, args=(proc,), name="mp-sink-{}".format(n)
|
||||||
)
|
)
|
||||||
thr.daemon = True
|
thr.daemon = True
|
||||||
thr.start()
|
thr.start()
|
||||||
@@ -53,13 +45,6 @@ class BrokerMp(object):
|
|||||||
self.procs.append(proc)
|
self.procs.append(proc)
|
||||||
proc.start()
|
proc.start()
|
||||||
|
|
||||||
if not self.args.q:
|
|
||||||
thr = threading.Thread(
|
|
||||||
target=self.debug_load_balancer, name="mp-dbg-loadbalancer"
|
|
||||||
)
|
|
||||||
thr.daemon = True
|
|
||||||
thr.start()
|
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
self.log("broker", "shutting down")
|
self.log("broker", "shutting down")
|
||||||
for n, proc in enumerate(self.procs):
|
for n, proc in enumerate(self.procs):
|
||||||
@@ -89,20 +74,6 @@ class BrokerMp(object):
|
|||||||
if dest == "log":
|
if dest == "log":
|
||||||
self.log(*args)
|
self.log(*args)
|
||||||
|
|
||||||
elif dest == "workload":
|
|
||||||
with self.mutex:
|
|
||||||
proc.workload = args[0]
|
|
||||||
|
|
||||||
elif dest == "httpdrop":
|
|
||||||
addr = args[0]
|
|
||||||
|
|
||||||
with self.mutex:
|
|
||||||
del proc.clients[addr]
|
|
||||||
if not proc.clients:
|
|
||||||
proc.workload = 0
|
|
||||||
|
|
||||||
self.hub.tcpsrv.num_clients.add(-1)
|
|
||||||
|
|
||||||
elif dest == "retq":
|
elif dest == "retq":
|
||||||
# response from previous ipc call
|
# response from previous ipc call
|
||||||
with self.retpend_mutex:
|
with self.retpend_mutex:
|
||||||
@@ -128,38 +99,9 @@ class BrokerMp(object):
|
|||||||
returns a Queue object which eventually contains the response if want_retval
|
returns a Queue object which eventually contains the response if want_retval
|
||||||
(not-impl here since nothing uses it yet)
|
(not-impl here since nothing uses it yet)
|
||||||
"""
|
"""
|
||||||
if dest == "httpconn":
|
if dest == "listen":
|
||||||
sck, addr = args
|
for p in self.procs:
|
||||||
sck2 = sck
|
p.q_pend.put([0, dest, [args[0], len(self.procs)]])
|
||||||
if PY2:
|
|
||||||
buf = MemesIO()
|
|
||||||
ForkingPickler(buf).dump(sck)
|
|
||||||
sck2 = buf.getvalue()
|
|
||||||
|
|
||||||
proc = sorted(self.procs, key=lambda x: x.workload)[0]
|
|
||||||
proc.q_pend.put([0, dest, [sck2, addr]])
|
|
||||||
|
|
||||||
with self.mutex:
|
|
||||||
proc.clients[addr] = 50
|
|
||||||
proc.workload += 50
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise Exception("what is " + str(dest))
|
raise Exception("what is " + str(dest))
|
||||||
|
|
||||||
def debug_load_balancer(self):
|
|
||||||
fmt = "\033[1m{}\033[0;36m{:4}\033[0m "
|
|
||||||
if not VT100:
|
|
||||||
fmt = "({}{:4})"
|
|
||||||
|
|
||||||
last = ""
|
|
||||||
while self.procs:
|
|
||||||
msg = ""
|
|
||||||
for proc in self.procs:
|
|
||||||
msg += fmt.format(len(proc.clients), proc.workload)
|
|
||||||
|
|
||||||
if msg != last:
|
|
||||||
last = msg
|
|
||||||
with self.hub.log_mutex:
|
|
||||||
print(msg)
|
|
||||||
|
|
||||||
time.sleep(0.1)
|
|
||||||
|
|||||||
@@ -3,18 +3,13 @@ from __future__ import print_function, unicode_literals
|
|||||||
from copyparty.authsrv import AuthSrv
|
from copyparty.authsrv import AuthSrv
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import time
|
|
||||||
import signal
|
import signal
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
from .__init__ import PY2, WINDOWS
|
|
||||||
from .broker_util import ExceptionalQueue
|
from .broker_util import ExceptionalQueue
|
||||||
from .httpsrv import HttpSrv
|
from .httpsrv import HttpSrv
|
||||||
from .util import FAKE_MP
|
from .util import FAKE_MP
|
||||||
|
|
||||||
if PY2 and not WINDOWS:
|
|
||||||
import pickle # nosec
|
|
||||||
|
|
||||||
|
|
||||||
class MpWorker(object):
|
class MpWorker(object):
|
||||||
"""one single mp instance"""
|
"""one single mp instance"""
|
||||||
@@ -25,10 +20,11 @@ class MpWorker(object):
|
|||||||
self.args = args
|
self.args = args
|
||||||
self.n = n
|
self.n = n
|
||||||
|
|
||||||
|
self.log = self._log_disabled if args.q and not args.lo else self._log_enabled
|
||||||
|
|
||||||
self.retpend = {}
|
self.retpend = {}
|
||||||
self.retpend_mutex = threading.Lock()
|
self.retpend_mutex = threading.Lock()
|
||||||
self.mutex = threading.Lock()
|
self.mutex = threading.Lock()
|
||||||
self.workload_thr_alive = False
|
|
||||||
|
|
||||||
# we inherited signal_handler from parent,
|
# we inherited signal_handler from parent,
|
||||||
# replace it with something harmless
|
# replace it with something harmless
|
||||||
@@ -39,8 +35,7 @@ class MpWorker(object):
|
|||||||
self.asrv = AuthSrv(args, None, False)
|
self.asrv = AuthSrv(args, None, False)
|
||||||
|
|
||||||
# instantiate all services here (TODO: inheritance?)
|
# instantiate all services here (TODO: inheritance?)
|
||||||
self.httpsrv = HttpSrv(self, True)
|
self.httpsrv = HttpSrv(self, n)
|
||||||
self.httpsrv.disconnect_func = self.httpdrop
|
|
||||||
|
|
||||||
# on winxp and some other platforms,
|
# on winxp and some other platforms,
|
||||||
# use thr.join() to block all signals
|
# use thr.join() to block all signals
|
||||||
@@ -53,15 +48,15 @@ class MpWorker(object):
|
|||||||
# print('k')
|
# print('k')
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def log(self, src, msg, c=0):
|
def _log_enabled(self, src, msg, c=0):
|
||||||
self.q_yield.put([0, "log", [src, msg, c]])
|
self.q_yield.put([0, "log", [src, msg, c]])
|
||||||
|
|
||||||
|
def _log_disabled(self, src, msg, c=0):
|
||||||
|
pass
|
||||||
|
|
||||||
def logw(self, msg, c=0):
|
def logw(self, msg, c=0):
|
||||||
self.log("mp{}".format(self.n), msg, c)
|
self.log("mp{}".format(self.n), msg, c)
|
||||||
|
|
||||||
def httpdrop(self, addr):
|
|
||||||
self.q_yield.put([0, "httpdrop", [addr]])
|
|
||||||
|
|
||||||
def main(self):
|
def main(self):
|
||||||
while True:
|
while True:
|
||||||
retq_id, dest, args = self.q_pend.get()
|
retq_id, dest, args = self.q_pend.get()
|
||||||
@@ -73,24 +68,8 @@ class MpWorker(object):
|
|||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
return
|
return
|
||||||
|
|
||||||
elif dest == "httpconn":
|
elif dest == "listen":
|
||||||
sck, addr = args
|
self.httpsrv.listen(args[0], args[1])
|
||||||
if PY2:
|
|
||||||
sck = pickle.loads(sck) # nosec
|
|
||||||
|
|
||||||
if self.args.log_conn:
|
|
||||||
self.log("%s %s" % addr, "|%sC-qpop" % ("-" * 4,), c="1;30")
|
|
||||||
|
|
||||||
self.httpsrv.accept(sck, addr)
|
|
||||||
|
|
||||||
with self.mutex:
|
|
||||||
if not self.workload_thr_alive:
|
|
||||||
self.workload_thr_alive = True
|
|
||||||
thr = threading.Thread(
|
|
||||||
target=self.thr_workload, name="mpw-workload"
|
|
||||||
)
|
|
||||||
thr.daemon = True
|
|
||||||
thr.start()
|
|
||||||
|
|
||||||
elif dest == "retq":
|
elif dest == "retq":
|
||||||
# response from previous ipc call
|
# response from previous ipc call
|
||||||
@@ -114,16 +93,3 @@ class MpWorker(object):
|
|||||||
|
|
||||||
self.q_yield.put([retq_id, dest, args])
|
self.q_yield.put([retq_id, dest, args])
|
||||||
return retq
|
return retq
|
||||||
|
|
||||||
def thr_workload(self):
|
|
||||||
"""announce workloads to MpSrv (the mp controller / loadbalancer)"""
|
|
||||||
# avoid locking in extract_filedata by tracking difference here
|
|
||||||
while True:
|
|
||||||
time.sleep(0.2)
|
|
||||||
with self.mutex:
|
|
||||||
if self.httpsrv.num_clients() == 0:
|
|
||||||
# no clients rn, termiante thread
|
|
||||||
self.workload_thr_alive = False
|
|
||||||
return
|
|
||||||
|
|
||||||
self.q_yield.put([0, "workload", [self.httpsrv.workload]])
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ from __future__ import print_function, unicode_literals
|
|||||||
|
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
from .authsrv import AuthSrv
|
|
||||||
from .httpsrv import HttpSrv
|
from .httpsrv import HttpSrv
|
||||||
from .broker_util import ExceptionalQueue, try_exec
|
from .broker_util import ExceptionalQueue, try_exec
|
||||||
|
|
||||||
@@ -20,8 +19,7 @@ class BrokerThr(object):
|
|||||||
self.mutex = threading.Lock()
|
self.mutex = threading.Lock()
|
||||||
|
|
||||||
# instantiate all services here (TODO: inheritance?)
|
# instantiate all services here (TODO: inheritance?)
|
||||||
self.httpsrv = HttpSrv(self)
|
self.httpsrv = HttpSrv(self, None)
|
||||||
self.httpsrv.disconnect_func = self.httpdrop
|
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
# self.log("broker", "shutting down")
|
# self.log("broker", "shutting down")
|
||||||
@@ -29,12 +27,8 @@ class BrokerThr(object):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def put(self, want_retval, dest, *args):
|
def put(self, want_retval, dest, *args):
|
||||||
if dest == "httpconn":
|
if dest == "listen":
|
||||||
sck, addr = args
|
self.httpsrv.listen(args[0], 1)
|
||||||
if self.args.log_conn:
|
|
||||||
self.log("%s %s" % addr, "|%sC-qpop" % ("-" * 4,), c="1;30")
|
|
||||||
|
|
||||||
self.httpsrv.accept(sck, addr)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# new ipc invoking managed service in hub
|
# new ipc invoking managed service in hub
|
||||||
@@ -51,6 +45,3 @@ class BrokerThr(object):
|
|||||||
retq = ExceptionalQueue(1)
|
retq = ExceptionalQueue(1)
|
||||||
retq.put(rv)
|
retq.put(rv)
|
||||||
return retq
|
return retq
|
||||||
|
|
||||||
def httpdrop(self, addr):
|
|
||||||
self.hub.tcpsrv.num_clients.add(-1)
|
|
||||||
|
|||||||
@@ -13,15 +13,12 @@ import ctypes
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import calendar
|
import calendar
|
||||||
|
|
||||||
from .__init__ import E, PY2, WINDOWS, ANYWIN
|
from .__init__ import E, PY2, WINDOWS, ANYWIN, unicode
|
||||||
from .util import * # noqa # pylint: disable=unused-wildcard-import
|
from .util import * # noqa # pylint: disable=unused-wildcard-import
|
||||||
from .authsrv import AuthSrv
|
from .authsrv import AuthSrv
|
||||||
from .szip import StreamZip
|
from .szip import StreamZip
|
||||||
from .star import StreamTar
|
from .star import StreamTar
|
||||||
|
|
||||||
if not PY2:
|
|
||||||
unicode = str
|
|
||||||
|
|
||||||
|
|
||||||
NO_CACHE = {"Cache-Control": "no-cache"}
|
NO_CACHE = {"Cache-Control": "no-cache"}
|
||||||
NO_STORE = {"Cache-Control": "no-store; max-age=0"}
|
NO_STORE = {"Cache-Control": "no-store; max-age=0"}
|
||||||
@@ -40,7 +37,6 @@ class HttpCli(object):
|
|||||||
self.ip = conn.addr[0]
|
self.ip = conn.addr[0]
|
||||||
self.addr = conn.addr # type: tuple[str, int]
|
self.addr = conn.addr # type: tuple[str, int]
|
||||||
self.args = conn.args
|
self.args = conn.args
|
||||||
self.is_mp = conn.is_mp
|
|
||||||
self.asrv = conn.asrv # type: AuthSrv
|
self.asrv = conn.asrv # type: AuthSrv
|
||||||
self.ico = conn.ico
|
self.ico = conn.ico
|
||||||
self.thumbcli = conn.thumbcli
|
self.thumbcli = conn.thumbcli
|
||||||
@@ -54,8 +50,16 @@ class HttpCli(object):
|
|||||||
self.out_headers = {"Access-Control-Allow-Origin": "*"}
|
self.out_headers = {"Access-Control-Allow-Origin": "*"}
|
||||||
|
|
||||||
def log(self, msg, c=0):
|
def log(self, msg, c=0):
|
||||||
|
ptn = self.asrv.re_pwd
|
||||||
|
if ptn and ptn.search(msg):
|
||||||
|
msg = ptn.sub(self.unpwd, msg)
|
||||||
|
|
||||||
self.log_func(self.log_src, msg, c)
|
self.log_func(self.log_src, msg, c)
|
||||||
|
|
||||||
|
def unpwd(self, m):
|
||||||
|
a, b = m.groups()
|
||||||
|
return "=\033[7m {} \033[27m{}".format(self.asrv.iuser[a], b)
|
||||||
|
|
||||||
def _check_nonfatal(self, ex):
|
def _check_nonfatal(self, ex):
|
||||||
return ex.code < 400 or ex.code in [404, 429]
|
return ex.code < 400 or ex.code in [404, 429]
|
||||||
|
|
||||||
@@ -64,9 +68,13 @@ class HttpCli(object):
|
|||||||
if rem.startswith("/") or rem.startswith("../") or "/../" in rem:
|
if rem.startswith("/") or rem.startswith("../") or "/../" in rem:
|
||||||
raise Exception("that was close")
|
raise Exception("that was close")
|
||||||
|
|
||||||
def j2(self, name, **kwargs):
|
def j2(self, name, **ka):
|
||||||
tpl = self.conn.hsrv.j2[name]
|
tpl = self.conn.hsrv.j2[name]
|
||||||
return tpl.render(**kwargs) if kwargs else tpl
|
if ka:
|
||||||
|
ka["ts"] = self.conn.hsrv.cachebuster()
|
||||||
|
return tpl.render(**ka)
|
||||||
|
|
||||||
|
return tpl
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
"""returns true if connection can be reused"""
|
"""returns true if connection can be reused"""
|
||||||
@@ -86,9 +94,13 @@ class HttpCli(object):
|
|||||||
try:
|
try:
|
||||||
self.mode, self.req, self.http_ver = headerlines[0].split(" ")
|
self.mode, self.req, self.http_ver = headerlines[0].split(" ")
|
||||||
except:
|
except:
|
||||||
raise Pebkac(400, "bad headers:\n" + "\n".join(headerlines))
|
msg = " ]\n#[ ".join(headerlines)
|
||||||
|
raise Pebkac(400, "bad headers:\n#[ " + msg + " ]")
|
||||||
|
|
||||||
except Pebkac as ex:
|
except Pebkac as ex:
|
||||||
|
self.mode = "GET"
|
||||||
|
self.req = "[junk]"
|
||||||
|
self.http_ver = "HTTP/1.1"
|
||||||
# self.log("pebkac at httpcli.run #1: " + repr(ex))
|
# self.log("pebkac at httpcli.run #1: " + repr(ex))
|
||||||
self.keepalive = self._check_nonfatal(ex)
|
self.keepalive = self._check_nonfatal(ex)
|
||||||
self.loud_reply(unicode(ex), status=ex.code)
|
self.loud_reply(unicode(ex), status=ex.code)
|
||||||
@@ -173,6 +185,9 @@ class HttpCli(object):
|
|||||||
self.rvol, self.wvol, self.avol = [[], [], []]
|
self.rvol, self.wvol, self.avol = [[], [], []]
|
||||||
self.asrv.vfs.user_tree(self.uname, self.rvol, self.wvol, self.avol)
|
self.asrv.vfs.user_tree(self.uname, self.rvol, self.wvol, self.avol)
|
||||||
|
|
||||||
|
if pwd and "pw" in self.ouparam and pwd != cookies.get("cppwd"):
|
||||||
|
self.out_headers["Set-Cookie"] = self.get_pwd_cookie(pwd)[0]
|
||||||
|
|
||||||
ua = self.headers.get("user-agent", "")
|
ua = self.headers.get("user-agent", "")
|
||||||
self.is_rclone = ua.startswith("rclone/")
|
self.is_rclone = ua.startswith("rclone/")
|
||||||
if self.is_rclone:
|
if self.is_rclone:
|
||||||
@@ -211,7 +226,7 @@ class HttpCli(object):
|
|||||||
except Pebkac:
|
except Pebkac:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def send_headers(self, length, status=200, mime=None, headers={}):
|
def send_headers(self, length, status=200, mime=None, headers=None):
|
||||||
response = ["{} {} {}".format(self.http_ver, status, HTTPCODE[status])]
|
response = ["{} {} {}".format(self.http_ver, status, HTTPCODE[status])]
|
||||||
|
|
||||||
if length is not None:
|
if length is not None:
|
||||||
@@ -221,6 +236,7 @@ class HttpCli(object):
|
|||||||
response.append("Connection: " + ("Keep-Alive" if self.keepalive else "Close"))
|
response.append("Connection: " + ("Keep-Alive" if self.keepalive else "Close"))
|
||||||
|
|
||||||
# headers{} overrides anything set previously
|
# headers{} overrides anything set previously
|
||||||
|
if headers:
|
||||||
self.out_headers.update(headers)
|
self.out_headers.update(headers)
|
||||||
|
|
||||||
# default to utf8 html if no content-type is set
|
# default to utf8 html if no content-type is set
|
||||||
@@ -238,7 +254,7 @@ class HttpCli(object):
|
|||||||
except:
|
except:
|
||||||
raise Pebkac(400, "client d/c while replying headers")
|
raise Pebkac(400, "client d/c while replying headers")
|
||||||
|
|
||||||
def reply(self, body, status=200, mime=None, headers={}):
|
def reply(self, body, status=200, mime=None, headers=None):
|
||||||
# TODO something to reply with user-supplied values safely
|
# TODO something to reply with user-supplied values safely
|
||||||
self.send_headers(len(body), status, mime, headers)
|
self.send_headers(len(body), status, mime, headers)
|
||||||
|
|
||||||
@@ -254,7 +270,7 @@ class HttpCli(object):
|
|||||||
self.log(body.rstrip())
|
self.log(body.rstrip())
|
||||||
self.reply(b"<pre>" + body.encode("utf-8") + b"\r\n", *list(args), **kwargs)
|
self.reply(b"<pre>" + body.encode("utf-8") + b"\r\n", *list(args), **kwargs)
|
||||||
|
|
||||||
def urlq(self, add={}, rm=[]):
|
def urlq(self, add, rm):
|
||||||
"""
|
"""
|
||||||
generates url query based on uparam (b, pw, all others)
|
generates url query based on uparam (b, pw, all others)
|
||||||
removing anything in rm, adding pairs in add
|
removing anything in rm, adding pairs in add
|
||||||
@@ -326,6 +342,9 @@ class HttpCli(object):
|
|||||||
if "tree" in self.uparam:
|
if "tree" in self.uparam:
|
||||||
return self.tx_tree()
|
return self.tx_tree()
|
||||||
|
|
||||||
|
if "stack" in self.uparam:
|
||||||
|
return self.tx_stack()
|
||||||
|
|
||||||
# conditional redirect to single volumes
|
# conditional redirect to single volumes
|
||||||
if self.vpath == "" and not self.ouparam:
|
if self.vpath == "" and not self.ouparam:
|
||||||
nread = len(self.rvol)
|
nread = len(self.rvol)
|
||||||
@@ -355,9 +374,6 @@ class HttpCli(object):
|
|||||||
if "scan" in self.uparam:
|
if "scan" in self.uparam:
|
||||||
return self.scanvol()
|
return self.scanvol()
|
||||||
|
|
||||||
if "stack" in self.uparam:
|
|
||||||
return self.tx_stack()
|
|
||||||
|
|
||||||
return self.tx_browser()
|
return self.tx_browser()
|
||||||
|
|
||||||
def handle_options(self):
|
def handle_options(self):
|
||||||
@@ -463,12 +479,14 @@ class HttpCli(object):
|
|||||||
addr = self.ip.replace(":", ".")
|
addr = self.ip.replace(":", ".")
|
||||||
fn = "put-{:.6f}-{}.bin".format(time.time(), addr)
|
fn = "put-{:.6f}-{}.bin".format(time.time(), addr)
|
||||||
path = os.path.join(fdir, fn)
|
path = os.path.join(fdir, fn)
|
||||||
|
if self.args.nw:
|
||||||
|
path = os.devnull
|
||||||
|
|
||||||
with open(fsenc(path), "wb", 512 * 1024) as f:
|
with open(fsenc(path), "wb", 512 * 1024) as f:
|
||||||
post_sz, _, sha_b64 = hashcopy(self.conn, reader, f)
|
post_sz, _, sha_b64 = hashcopy(reader, f)
|
||||||
|
|
||||||
|
if not self.args.nw:
|
||||||
vfs, vrem = vfs.get_dbv(rem)
|
vfs, vrem = vfs.get_dbv(rem)
|
||||||
|
|
||||||
self.conn.hsrv.broker.put(
|
self.conn.hsrv.broker.put(
|
||||||
False, "up2k.hash_file", vfs.realpath, vfs.flags, vrem, fn
|
False, "up2k.hash_file", vfs.realpath, vfs.flags, vrem, fn
|
||||||
)
|
)
|
||||||
@@ -488,7 +506,7 @@ class HttpCli(object):
|
|||||||
|
|
||||||
spd1 = get_spd(nbytes, self.t0)
|
spd1 = get_spd(nbytes, self.t0)
|
||||||
spd2 = get_spd(self.conn.nbyte, self.conn.t0)
|
spd2 = get_spd(self.conn.nbyte, self.conn.t0)
|
||||||
return spd1 + " " + spd2
|
return "{} {} n{}".format(spd1, spd2, self.conn.nreq)
|
||||||
|
|
||||||
def handle_post_multipart(self):
|
def handle_post_multipart(self):
|
||||||
self.parser = MultipartParser(self.log, self.sr, self.headers)
|
self.parser = MultipartParser(self.log, self.sr, self.headers)
|
||||||
@@ -592,6 +610,7 @@ class HttpCli(object):
|
|||||||
os.makedirs(fsenc(dst))
|
os.makedirs(fsenc(dst))
|
||||||
except OSError as ex:
|
except OSError as ex:
|
||||||
self.log("makedirs failed [{}]".format(dst))
|
self.log("makedirs failed [{}]".format(dst))
|
||||||
|
if not os.path.isdir(fsenc(dst)):
|
||||||
if ex.errno == 13:
|
if ex.errno == 13:
|
||||||
raise Pebkac(500, "the server OS denied write-access")
|
raise Pebkac(500, "the server OS denied write-access")
|
||||||
|
|
||||||
@@ -630,7 +649,7 @@ class HttpCli(object):
|
|||||||
penalty = 0.7
|
penalty = 0.7
|
||||||
t_idle = t0 - idx.p_end
|
t_idle = t0 - idx.p_end
|
||||||
if idx.p_dur > 0.7 and t_idle < penalty:
|
if idx.p_dur > 0.7 and t_idle < penalty:
|
||||||
m = "rate-limit ({:.1f} sec), cost {:.2f}, idle {:.2f}"
|
m = "rate-limit {:.1f} sec, cost {:.2f}, idle {:.2f}"
|
||||||
raise Pebkac(429, m.format(penalty, idx.p_dur, t_idle))
|
raise Pebkac(429, m.format(penalty, idx.p_dur, t_idle))
|
||||||
|
|
||||||
if "srch" in body:
|
if "srch" in body:
|
||||||
@@ -696,7 +715,7 @@ class HttpCli(object):
|
|||||||
|
|
||||||
with open(fsenc(path), "rb+", 512 * 1024) as f:
|
with open(fsenc(path), "rb+", 512 * 1024) as f:
|
||||||
f.seek(cstart[0])
|
f.seek(cstart[0])
|
||||||
post_sz, _, sha_b64 = hashcopy(self.conn, reader, f)
|
post_sz, _, sha_b64 = hashcopy(reader, f)
|
||||||
|
|
||||||
if sha_b64 != chash:
|
if sha_b64 != chash:
|
||||||
raise Pebkac(
|
raise Pebkac(
|
||||||
@@ -750,6 +769,12 @@ class HttpCli(object):
|
|||||||
pwd = self.parser.require("cppwd", 64)
|
pwd = self.parser.require("cppwd", 64)
|
||||||
self.parser.drop()
|
self.parser.drop()
|
||||||
|
|
||||||
|
ck, msg = self.get_pwd_cookie(pwd)
|
||||||
|
html = self.j2("msg", h1=msg, h2='<a href="/">ack</a>', redir="/")
|
||||||
|
self.reply(html.encode("utf-8"), headers={"Set-Cookie": ck})
|
||||||
|
return True
|
||||||
|
|
||||||
|
def get_pwd_cookie(self, pwd):
|
||||||
if pwd in self.asrv.iuser:
|
if pwd in self.asrv.iuser:
|
||||||
msg = "login ok"
|
msg = "login ok"
|
||||||
dt = datetime.utcfromtimestamp(time.time() + 60 * 60 * 24 * 365)
|
dt = datetime.utcfromtimestamp(time.time() + 60 * 60 * 24 * 365)
|
||||||
@@ -760,9 +785,7 @@ class HttpCli(object):
|
|||||||
exp = "Fri, 15 Aug 1997 01:00:00 GMT"
|
exp = "Fri, 15 Aug 1997 01:00:00 GMT"
|
||||||
|
|
||||||
ck = "cppwd={}; Path=/; Expires={}; SameSite=Lax".format(pwd, exp)
|
ck = "cppwd={}; Path=/; Expires={}; SameSite=Lax".format(pwd, exp)
|
||||||
html = self.j2("msg", h1=msg, h2='<a href="/">ack</a>', redir="/")
|
return [ck, msg]
|
||||||
self.reply(html.encode("utf-8"), headers={"Set-Cookie": ck})
|
|
||||||
return True
|
|
||||||
|
|
||||||
def handle_mkdir(self):
|
def handle_mkdir(self):
|
||||||
new_dir = self.parser.require("name", 512)
|
new_dir = self.parser.require("name", 512)
|
||||||
@@ -772,7 +795,7 @@ class HttpCli(object):
|
|||||||
vfs, rem = self.asrv.vfs.get(self.vpath, self.uname, False, True)
|
vfs, rem = self.asrv.vfs.get(self.vpath, self.uname, False, True)
|
||||||
self._assert_safe_rem(rem)
|
self._assert_safe_rem(rem)
|
||||||
|
|
||||||
sanitized = sanitize_fn(new_dir)
|
sanitized = sanitize_fn(new_dir, "", [])
|
||||||
|
|
||||||
if not nullwrite:
|
if not nullwrite:
|
||||||
fdir = os.path.join(vfs.realpath, rem)
|
fdir = os.path.join(vfs.realpath, rem)
|
||||||
@@ -809,7 +832,7 @@ class HttpCli(object):
|
|||||||
if not new_file.endswith(".md"):
|
if not new_file.endswith(".md"):
|
||||||
new_file += ".md"
|
new_file += ".md"
|
||||||
|
|
||||||
sanitized = sanitize_fn(new_file)
|
sanitized = sanitize_fn(new_file, "", [])
|
||||||
|
|
||||||
if not nullwrite:
|
if not nullwrite:
|
||||||
fdir = os.path.join(vfs.realpath, rem)
|
fdir = os.path.join(vfs.realpath, rem)
|
||||||
@@ -842,7 +865,7 @@ class HttpCli(object):
|
|||||||
if p_file and not nullwrite:
|
if p_file and not nullwrite:
|
||||||
fdir = os.path.join(vfs.realpath, rem)
|
fdir = os.path.join(vfs.realpath, rem)
|
||||||
fname = sanitize_fn(
|
fname = sanitize_fn(
|
||||||
p_file, bad=[".prologue.html", ".epilogue.html"]
|
p_file, "", [".prologue.html", ".epilogue.html"]
|
||||||
)
|
)
|
||||||
|
|
||||||
if not os.path.isdir(fsenc(fdir)):
|
if not os.path.isdir(fsenc(fdir)):
|
||||||
@@ -859,7 +882,7 @@ class HttpCli(object):
|
|||||||
with ren_open(fname, "wb", 512 * 1024, **open_args) as f:
|
with ren_open(fname, "wb", 512 * 1024, **open_args) as f:
|
||||||
f, fname = f["orz"]
|
f, fname = f["orz"]
|
||||||
self.log("writing to {}/{}".format(fdir, fname))
|
self.log("writing to {}/{}".format(fdir, fname))
|
||||||
sz, sha512_hex, _ = hashcopy(self.conn, p_data, f)
|
sz, sha512_hex, _ = hashcopy(p_data, f)
|
||||||
if sz == 0:
|
if sz == 0:
|
||||||
raise Pebkac(400, "empty files in post")
|
raise Pebkac(400, "empty files in post")
|
||||||
|
|
||||||
@@ -1042,7 +1065,7 @@ class HttpCli(object):
|
|||||||
raise Pebkac(400, "expected body, got {}".format(p_field))
|
raise Pebkac(400, "expected body, got {}".format(p_field))
|
||||||
|
|
||||||
with open(fsenc(fp), "wb", 512 * 1024) as f:
|
with open(fsenc(fp), "wb", 512 * 1024) as f:
|
||||||
sz, sha512, _ = hashcopy(self.conn, p_data, f)
|
sz, sha512, _ = hashcopy(p_data, f)
|
||||||
|
|
||||||
new_lastmod = os.stat(fsenc(fp)).st_mtime
|
new_lastmod = os.stat(fsenc(fp)).st_mtime
|
||||||
new_lastmod3 = int(new_lastmod * 1000)
|
new_lastmod3 = int(new_lastmod * 1000)
|
||||||
@@ -1232,8 +1255,7 @@ class HttpCli(object):
|
|||||||
if use_sendfile:
|
if use_sendfile:
|
||||||
remains = sendfile_kern(lower, upper, f, self.s)
|
remains = sendfile_kern(lower, upper, f, self.s)
|
||||||
else:
|
else:
|
||||||
actor = self.conn if self.is_mp else None
|
remains = sendfile_py(lower, upper, f, self.s)
|
||||||
remains = sendfile_py(lower, upper, f, self.s, actor)
|
|
||||||
|
|
||||||
if remains > 0:
|
if remains > 0:
|
||||||
logmsg += " \033[31m" + unicode(upper - remains) + "\033[0m"
|
logmsg += " \033[31m" + unicode(upper - remains) + "\033[0m"
|
||||||
@@ -1290,7 +1312,7 @@ class HttpCli(object):
|
|||||||
|
|
||||||
fgen = vn.zipgen(rem, items, self.uname, dots, not self.args.no_scandir)
|
fgen = vn.zipgen(rem, items, self.uname, dots, not self.args.no_scandir)
|
||||||
# for f in fgen: print(repr({k: f[k] for k in ["vp", "ap"]}))
|
# for f in fgen: print(repr({k: f[k] for k in ["vp", "ap"]}))
|
||||||
bgen = packer(fgen, utf8="utf" in uarg, pre_crc="crc" in uarg)
|
bgen = packer(self.log, fgen, utf8="utf" in uarg, pre_crc="crc" in uarg)
|
||||||
bsent = 0
|
bsent = 0
|
||||||
for buf in bgen.gen():
|
for buf in bgen.gen():
|
||||||
if not buf:
|
if not buf:
|
||||||
@@ -1368,6 +1390,7 @@ class HttpCli(object):
|
|||||||
"md_plug": "true" if self.args.emp else "false",
|
"md_plug": "true" if self.args.emp else "false",
|
||||||
"md_chk_rate": self.args.mcr,
|
"md_chk_rate": self.args.mcr,
|
||||||
"md": boundary,
|
"md": boundary,
|
||||||
|
"ts": self.conn.hsrv.cachebuster(),
|
||||||
}
|
}
|
||||||
html = template.render(**targs).encode("utf-8", "replace")
|
html = template.render(**targs).encode("utf-8", "replace")
|
||||||
html = html.split(boundary.encode("utf-8"))
|
html = html.split(boundary.encode("utf-8"))
|
||||||
@@ -1400,7 +1423,7 @@ class HttpCli(object):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def tx_mounts(self):
|
def tx_mounts(self):
|
||||||
suf = self.urlq(rm=["h"])
|
suf = self.urlq({}, ["h"])
|
||||||
rvol, wvol, avol = [
|
rvol, wvol, avol = [
|
||||||
[("/" + x).rstrip("/") + "/" for x in y]
|
[("/" + x).rstrip("/") + "/" for x in y]
|
||||||
for y in [self.rvol, self.wvol, self.avol]
|
for y in [self.rvol, self.wvol, self.avol]
|
||||||
@@ -1450,7 +1473,7 @@ class HttpCli(object):
|
|||||||
raise Pebkac(500, x)
|
raise Pebkac(500, x)
|
||||||
|
|
||||||
def tx_stack(self):
|
def tx_stack(self):
|
||||||
if not self.readable or not self.writable:
|
if not self.avol:
|
||||||
raise Pebkac(403, "not admin")
|
raise Pebkac(403, "not admin")
|
||||||
|
|
||||||
if self.args.no_stack:
|
if self.args.no_stack:
|
||||||
@@ -1540,14 +1563,16 @@ class HttpCli(object):
|
|||||||
raise Pebkac(404)
|
raise Pebkac(404)
|
||||||
|
|
||||||
if self.readable:
|
if self.readable:
|
||||||
if rem.startswith(".hist/up2k."):
|
if rem.startswith(".hist/up2k.") or (
|
||||||
|
rem.endswith("/dir.txt") and rem.startswith(".hist/th/")
|
||||||
|
):
|
||||||
raise Pebkac(403)
|
raise Pebkac(403)
|
||||||
|
|
||||||
is_dir = stat.S_ISDIR(st.st_mode)
|
is_dir = stat.S_ISDIR(st.st_mode)
|
||||||
th_fmt = self.uparam.get("th")
|
th_fmt = self.uparam.get("th")
|
||||||
if th_fmt is not None:
|
if th_fmt is not None:
|
||||||
if is_dir:
|
if is_dir:
|
||||||
for fn in ["folder.png", "folder.jpg"]:
|
for fn in self.args.th_covers.split(","):
|
||||||
fp = os.path.join(abspath, fn)
|
fp = os.path.join(abspath, fn)
|
||||||
if os.path.exists(fp):
|
if os.path.exists(fp):
|
||||||
vrem = "{}/{}".format(vrem.rstrip("/"), fn)
|
vrem = "{}/{}".format(vrem.rstrip("/"), fn)
|
||||||
@@ -1609,9 +1634,8 @@ class HttpCli(object):
|
|||||||
if self.writable:
|
if self.writable:
|
||||||
perms.append("write")
|
perms.append("write")
|
||||||
|
|
||||||
url_suf = self.urlq()
|
url_suf = self.urlq({}, [])
|
||||||
is_ls = "ls" in self.uparam
|
is_ls = "ls" in self.uparam
|
||||||
ts = "" # "?{}".format(time.time())
|
|
||||||
|
|
||||||
tpl = "browser"
|
tpl = "browser"
|
||||||
if "b" in self.uparam:
|
if "b" in self.uparam:
|
||||||
@@ -1636,7 +1660,6 @@ class HttpCli(object):
|
|||||||
"vdir": quotep(self.vpath),
|
"vdir": quotep(self.vpath),
|
||||||
"vpnodes": vpnodes,
|
"vpnodes": vpnodes,
|
||||||
"files": [],
|
"files": [],
|
||||||
"ts": ts,
|
|
||||||
"perms": json.dumps(perms),
|
"perms": json.dumps(perms),
|
||||||
"taglist": [],
|
"taglist": [],
|
||||||
"tag_order": [],
|
"tag_order": [],
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ class HttpConn(object):
|
|||||||
|
|
||||||
self.args = hsrv.args
|
self.args = hsrv.args
|
||||||
self.asrv = hsrv.asrv
|
self.asrv = hsrv.asrv
|
||||||
self.is_mp = hsrv.is_mp
|
|
||||||
self.cert_path = hsrv.cert_path
|
self.cert_path = hsrv.cert_path
|
||||||
|
|
||||||
enth = HAVE_PIL and not self.args.no_thumb
|
enth = HAVE_PIL and not self.args.no_thumb
|
||||||
@@ -43,8 +42,8 @@ class HttpConn(object):
|
|||||||
|
|
||||||
self.t0 = time.time()
|
self.t0 = time.time()
|
||||||
self.stopping = False
|
self.stopping = False
|
||||||
|
self.nreq = 0
|
||||||
self.nbyte = 0
|
self.nbyte = 0
|
||||||
self.workload = 0
|
|
||||||
self.u2idx = None
|
self.u2idx = None
|
||||||
self.log_func = hsrv.log
|
self.log_func = hsrv.log
|
||||||
self.lf_url = re.compile(self.args.lf_url) if self.args.lf_url else None
|
self.lf_url = re.compile(self.args.lf_url) if self.args.lf_url else None
|
||||||
@@ -183,11 +182,7 @@ class HttpConn(object):
|
|||||||
self.sr = Unrecv(self.s)
|
self.sr = Unrecv(self.s)
|
||||||
|
|
||||||
while not self.stopping:
|
while not self.stopping:
|
||||||
if self.is_mp:
|
self.nreq += 1
|
||||||
self.workload += 50
|
|
||||||
if self.workload >= 2 ** 31:
|
|
||||||
self.workload = 100
|
|
||||||
|
|
||||||
cli = HttpCli(self)
|
cli = HttpCli(self)
|
||||||
if not cli.run():
|
if not cli.run():
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ from __future__ import print_function, unicode_literals
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
import math
|
||||||
|
import base64
|
||||||
import socket
|
import socket
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
@@ -24,10 +26,15 @@ except ImportError:
|
|||||||
)
|
)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
from .__init__ import E, MACOS
|
from .__init__ import E, PY2, MACOS
|
||||||
from .authsrv import AuthSrv
|
from .util import spack, min_ex, start_stackmon, start_log_thrs
|
||||||
from .httpconn import HttpConn
|
from .httpconn import HttpConn
|
||||||
|
|
||||||
|
if PY2:
|
||||||
|
import Queue as queue
|
||||||
|
else:
|
||||||
|
import queue
|
||||||
|
|
||||||
|
|
||||||
class HttpSrv(object):
|
class HttpSrv(object):
|
||||||
"""
|
"""
|
||||||
@@ -35,19 +42,28 @@ class HttpSrv(object):
|
|||||||
relying on MpSrv for performance (HttpSrv is just plain threads)
|
relying on MpSrv for performance (HttpSrv is just plain threads)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, broker, is_mp=False):
|
def __init__(self, broker, nid):
|
||||||
self.broker = broker
|
self.broker = broker
|
||||||
self.is_mp = is_mp
|
self.nid = nid
|
||||||
self.args = broker.args
|
self.args = broker.args
|
||||||
self.log = broker.log
|
self.log = broker.log
|
||||||
self.asrv = broker.asrv
|
self.asrv = broker.asrv
|
||||||
|
|
||||||
self.disconnect_func = None
|
self.name = "httpsrv" + ("-n{}-i{:x}".format(nid, os.getpid()) if nid else "")
|
||||||
self.mutex = threading.Lock()
|
self.mutex = threading.Lock()
|
||||||
|
self.stopping = False
|
||||||
|
|
||||||
self.clients = {}
|
self.tp_nthr = 0 # actual
|
||||||
self.workload = 0
|
self.tp_ncli = 0 # fading
|
||||||
self.workload_thr_alive = False
|
self.tp_time = None # latest worker collect
|
||||||
|
self.tp_q = None if self.args.no_htp else queue.LifoQueue()
|
||||||
|
|
||||||
|
self.srvs = []
|
||||||
|
self.ncli = 0 # exact
|
||||||
|
self.clients = {} # laggy
|
||||||
|
self.nclimax = 0
|
||||||
|
self.cb_ts = 0
|
||||||
|
self.cb_v = 0
|
||||||
|
|
||||||
env = jinja2.Environment()
|
env = jinja2.Environment()
|
||||||
env.loader = jinja2.FileSystemLoader(os.path.join(E.mod, "web"))
|
env.loader = jinja2.FileSystemLoader(os.path.join(E.mod, "web"))
|
||||||
@@ -62,24 +78,155 @@ class HttpSrv(object):
|
|||||||
else:
|
else:
|
||||||
self.cert_path = None
|
self.cert_path = None
|
||||||
|
|
||||||
def accept(self, sck, addr):
|
if self.tp_q:
|
||||||
"""takes an incoming tcp connection and creates a thread to handle it"""
|
self.start_threads(4)
|
||||||
if self.args.log_conn:
|
|
||||||
self.log("%s %s" % addr, "|%sC-cthr" % ("-" * 5,), c="1;30")
|
|
||||||
|
|
||||||
|
name = "httpsrv-scaler" + ("-{}".format(nid) if nid else "")
|
||||||
|
t = threading.Thread(target=self.thr_scaler, name=name)
|
||||||
|
t.daemon = True
|
||||||
|
t.start()
|
||||||
|
|
||||||
|
if nid:
|
||||||
|
if self.args.stackmon:
|
||||||
|
start_stackmon(self.args.stackmon, nid)
|
||||||
|
|
||||||
|
if self.args.log_thrs:
|
||||||
|
start_log_thrs(self.log, self.args.log_thrs, nid)
|
||||||
|
|
||||||
|
def start_threads(self, n):
|
||||||
|
self.tp_nthr += n
|
||||||
|
if self.args.log_htp:
|
||||||
|
self.log(self.name, "workers += {} = {}".format(n, self.tp_nthr), 6)
|
||||||
|
|
||||||
|
for _ in range(n):
|
||||||
thr = threading.Thread(
|
thr = threading.Thread(
|
||||||
target=self.thr_client,
|
target=self.thr_poolw,
|
||||||
args=(sck, addr),
|
name=self.name + "-poolw",
|
||||||
name="httpsrv-{}-{}".format(addr[0].split(".", 2)[-1][-6:], addr[1]),
|
|
||||||
)
|
)
|
||||||
thr.daemon = True
|
thr.daemon = True
|
||||||
thr.start()
|
thr.start()
|
||||||
|
|
||||||
def num_clients(self):
|
def stop_threads(self, n):
|
||||||
|
self.tp_nthr -= n
|
||||||
|
if self.args.log_htp:
|
||||||
|
self.log(self.name, "workers -= {} = {}".format(n, self.tp_nthr), 6)
|
||||||
|
|
||||||
|
for _ in range(n):
|
||||||
|
self.tp_q.put(None)
|
||||||
|
|
||||||
|
def thr_scaler(self):
|
||||||
|
while True:
|
||||||
|
time.sleep(2 if self.tp_ncli else 30)
|
||||||
with self.mutex:
|
with self.mutex:
|
||||||
return len(self.clients)
|
self.tp_ncli = max(self.ncli, self.tp_ncli - 2)
|
||||||
|
if self.tp_nthr > self.tp_ncli + 8:
|
||||||
|
self.stop_threads(4)
|
||||||
|
|
||||||
|
def listen(self, sck, nlisteners):
|
||||||
|
ip, port = sck.getsockname()
|
||||||
|
self.srvs.append(sck)
|
||||||
|
self.nclimax = math.ceil(self.args.nc * 1.0 / nlisteners)
|
||||||
|
t = threading.Thread(
|
||||||
|
target=self.thr_listen,
|
||||||
|
args=(sck,),
|
||||||
|
name="httpsrv-n{}-listen-{}-{}".format(self.nid or "0", ip, port),
|
||||||
|
)
|
||||||
|
t.daemon = True
|
||||||
|
t.start()
|
||||||
|
|
||||||
|
def thr_listen(self, srv_sck):
|
||||||
|
"""listens on a shared tcp server"""
|
||||||
|
ip, port = srv_sck.getsockname()
|
||||||
|
fno = srv_sck.fileno()
|
||||||
|
msg = "subscribed @ {}:{} f{}".format(ip, port, fno)
|
||||||
|
self.log(self.name, msg)
|
||||||
|
while not self.stopping:
|
||||||
|
if self.args.log_conn:
|
||||||
|
self.log(self.name, "|%sC-ncli" % ("-" * 1,), c="1;30")
|
||||||
|
|
||||||
|
if self.ncli >= self.nclimax:
|
||||||
|
self.log(self.name, "at connection limit; waiting", 3)
|
||||||
|
while self.ncli >= self.nclimax:
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
if self.args.log_conn:
|
||||||
|
self.log(self.name, "|%sC-acc1" % ("-" * 2,), c="1;30")
|
||||||
|
|
||||||
|
try:
|
||||||
|
sck, addr = srv_sck.accept()
|
||||||
|
except (OSError, socket.error) as ex:
|
||||||
|
self.log(self.name, "accept({}): {}".format(fno, ex), c=6)
|
||||||
|
time.sleep(0.02)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if self.args.log_conn:
|
||||||
|
m = "|{}C-acc2 \033[0;36m{} \033[3{}m{}".format(
|
||||||
|
"-" * 3, ip, port % 8, port
|
||||||
|
)
|
||||||
|
self.log("%s %s" % addr, m, c="1;30")
|
||||||
|
|
||||||
|
self.accept(sck, addr)
|
||||||
|
|
||||||
|
def accept(self, sck, addr):
|
||||||
|
"""takes an incoming tcp connection and creates a thread to handle it"""
|
||||||
|
now = time.time()
|
||||||
|
|
||||||
|
if now - (self.tp_time or now) > 300:
|
||||||
|
self.tp_q = None
|
||||||
|
|
||||||
|
if self.tp_q:
|
||||||
|
self.tp_q.put((sck, addr))
|
||||||
|
with self.mutex:
|
||||||
|
self.ncli += 1
|
||||||
|
self.tp_time = self.tp_time or now
|
||||||
|
self.tp_ncli = max(self.tp_ncli, self.ncli + 1)
|
||||||
|
if self.tp_nthr < self.ncli + 4:
|
||||||
|
self.start_threads(8)
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.args.no_htp:
|
||||||
|
m = "looks like the httpserver threadpool died; please make an issue on github and tell me the story of how you pulled that off, thanks and dog bless\n"
|
||||||
|
self.log(self.name, m, 1)
|
||||||
|
|
||||||
|
with self.mutex:
|
||||||
|
self.ncli += 1
|
||||||
|
|
||||||
|
thr = threading.Thread(
|
||||||
|
target=self.thr_client,
|
||||||
|
args=(sck, addr),
|
||||||
|
name="httpconn-{}-{}".format(addr[0].split(".", 2)[-1][-6:], addr[1]),
|
||||||
|
)
|
||||||
|
thr.daemon = True
|
||||||
|
thr.start()
|
||||||
|
|
||||||
|
def thr_poolw(self):
|
||||||
|
while True:
|
||||||
|
task = self.tp_q.get()
|
||||||
|
if not task:
|
||||||
|
break
|
||||||
|
|
||||||
|
with self.mutex:
|
||||||
|
self.tp_time = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
sck, addr = task
|
||||||
|
me = threading.current_thread()
|
||||||
|
me.name = "httpconn-{}-{}".format(
|
||||||
|
addr[0].split(".", 2)[-1][-6:], addr[1]
|
||||||
|
)
|
||||||
|
self.thr_client(sck, addr)
|
||||||
|
me.name = self.name + "-poolw"
|
||||||
|
except:
|
||||||
|
self.log(self.name, "thr_client: " + min_ex(), 3)
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
|
self.stopping = True
|
||||||
|
for srv in self.srvs:
|
||||||
|
try:
|
||||||
|
srv.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
clients = list(self.clients.keys())
|
clients = list(self.clients.keys())
|
||||||
for cli in clients:
|
for cli in clients:
|
||||||
try:
|
try:
|
||||||
@@ -87,7 +234,14 @@ class HttpSrv(object):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
self.log("httpsrv-n", "ok bye")
|
if self.tp_q:
|
||||||
|
self.stop_threads(self.tp_nthr)
|
||||||
|
for _ in range(10):
|
||||||
|
time.sleep(0.05)
|
||||||
|
if self.tp_q.empty():
|
||||||
|
break
|
||||||
|
|
||||||
|
self.log(self.name, "ok bye")
|
||||||
|
|
||||||
def thr_client(self, sck, addr):
|
def thr_client(self, sck, addr):
|
||||||
"""thread managing one tcp client"""
|
"""thread managing one tcp client"""
|
||||||
@@ -97,25 +251,15 @@ class HttpSrv(object):
|
|||||||
with self.mutex:
|
with self.mutex:
|
||||||
self.clients[cli] = 0
|
self.clients[cli] = 0
|
||||||
|
|
||||||
if self.is_mp:
|
|
||||||
self.workload += 50
|
|
||||||
if not self.workload_thr_alive:
|
|
||||||
self.workload_thr_alive = True
|
|
||||||
thr = threading.Thread(
|
|
||||||
target=self.thr_workload, name="httpsrv-workload"
|
|
||||||
)
|
|
||||||
thr.daemon = True
|
|
||||||
thr.start()
|
|
||||||
|
|
||||||
fno = sck.fileno()
|
fno = sck.fileno()
|
||||||
try:
|
try:
|
||||||
if self.args.log_conn:
|
if self.args.log_conn:
|
||||||
self.log("%s %s" % addr, "|%sC-crun" % ("-" * 6,), c="1;30")
|
self.log("%s %s" % addr, "|%sC-crun" % ("-" * 4,), c="1;30")
|
||||||
|
|
||||||
cli.run()
|
cli.run()
|
||||||
|
|
||||||
except (OSError, socket.error) as ex:
|
except (OSError, socket.error) as ex:
|
||||||
if ex.errno not in [10038, 10054, 107, 57, 9]:
|
if ex.errno not in [10038, 10054, 107, 57, 49, 9]:
|
||||||
self.log(
|
self.log(
|
||||||
"%s %s" % addr,
|
"%s %s" % addr,
|
||||||
"run({}): {}".format(fno, ex),
|
"run({}): {}".format(fno, ex),
|
||||||
@@ -125,7 +269,7 @@ class HttpSrv(object):
|
|||||||
finally:
|
finally:
|
||||||
sck = cli.s
|
sck = cli.s
|
||||||
if self.args.log_conn:
|
if self.args.log_conn:
|
||||||
self.log("%s %s" % addr, "|%sC-cdone" % ("-" * 7,), c="1;30")
|
self.log("%s %s" % addr, "|%sC-cdone" % ("-" * 5,), c="1;30")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
fno = sck.fileno()
|
fno = sck.fileno()
|
||||||
@@ -138,42 +282,37 @@ class HttpSrv(object):
|
|||||||
"shut({}): {}".format(fno, ex),
|
"shut({}): {}".format(fno, ex),
|
||||||
c="1;30",
|
c="1;30",
|
||||||
)
|
)
|
||||||
if ex.errno not in [10038, 10054, 107, 57, 9]:
|
if ex.errno not in [10038, 10054, 107, 57, 49, 9]:
|
||||||
# 10038 No longer considered a socket
|
# 10038 No longer considered a socket
|
||||||
# 10054 Foribly closed by remote
|
# 10054 Foribly closed by remote
|
||||||
# 107 Transport endpoint not connected
|
# 107 Transport endpoint not connected
|
||||||
# 57 Socket is not connected
|
# 57 Socket is not connected
|
||||||
|
# 49 Can't assign requested address (wifi down)
|
||||||
# 9 Bad file descriptor
|
# 9 Bad file descriptor
|
||||||
raise
|
raise
|
||||||
finally:
|
finally:
|
||||||
with self.mutex:
|
with self.mutex:
|
||||||
del self.clients[cli]
|
del self.clients[cli]
|
||||||
|
self.ncli -= 1
|
||||||
|
|
||||||
if self.disconnect_func:
|
def cachebuster(self):
|
||||||
self.disconnect_func(addr) # pylint: disable=not-callable
|
if time.time() - self.cb_ts < 1:
|
||||||
|
return self.cb_v
|
||||||
|
|
||||||
def thr_workload(self):
|
|
||||||
"""indicates the python interpreter workload caused by this HttpSrv"""
|
|
||||||
# avoid locking in extract_filedata by tracking difference here
|
|
||||||
while True:
|
|
||||||
time.sleep(0.2)
|
|
||||||
with self.mutex:
|
with self.mutex:
|
||||||
if not self.clients:
|
if time.time() - self.cb_ts < 1:
|
||||||
# no clients rn, termiante thread
|
return self.cb_v
|
||||||
self.workload_thr_alive = False
|
|
||||||
self.workload = 0
|
|
||||||
return
|
|
||||||
|
|
||||||
total = 0
|
v = E.t0
|
||||||
with self.mutex:
|
try:
|
||||||
for cli in self.clients.keys():
|
with os.scandir(os.path.join(E.mod, "web")) as dh:
|
||||||
now = cli.workload
|
for fh in dh:
|
||||||
delta = now - self.clients[cli]
|
inf = fh.stat(follow_symlinks=False)
|
||||||
if delta < 0:
|
v = max(v, inf.st_mtime)
|
||||||
# was reset in HttpCli to prevent overflow
|
except:
|
||||||
delta = now
|
pass
|
||||||
|
|
||||||
total += delta
|
v = base64.urlsafe_b64encode(spack(b">xxL", int(v)))
|
||||||
self.clients[cli] = now
|
self.cb_v = v.decode("ascii")[-4:]
|
||||||
|
self.cb_ts = time.time()
|
||||||
self.workload = total
|
return self.cb_v
|
||||||
|
|||||||
@@ -7,12 +7,9 @@ import json
|
|||||||
import shutil
|
import shutil
|
||||||
import subprocess as sp
|
import subprocess as sp
|
||||||
|
|
||||||
from .__init__ import PY2, WINDOWS
|
from .__init__ import PY2, WINDOWS, unicode
|
||||||
from .util import fsenc, fsdec, uncyg, REKOBO_LKEY
|
from .util import fsenc, fsdec, uncyg, REKOBO_LKEY
|
||||||
|
|
||||||
if not PY2:
|
|
||||||
unicode = str
|
|
||||||
|
|
||||||
|
|
||||||
def have_ff(cmd):
|
def have_ff(cmd):
|
||||||
if PY2:
|
if PY2:
|
||||||
|
|||||||
@@ -33,10 +33,11 @@ class QFile(object):
|
|||||||
class StreamTar(object):
|
class StreamTar(object):
|
||||||
"""construct in-memory tar file from the given path"""
|
"""construct in-memory tar file from the given path"""
|
||||||
|
|
||||||
def __init__(self, fgen, **kwargs):
|
def __init__(self, log, fgen, **kwargs):
|
||||||
self.ci = 0
|
self.ci = 0
|
||||||
self.co = 0
|
self.co = 0
|
||||||
self.qfile = QFile()
|
self.qfile = QFile()
|
||||||
|
self.log = log
|
||||||
self.fgen = fgen
|
self.fgen = fgen
|
||||||
self.errf = None
|
self.errf = None
|
||||||
|
|
||||||
@@ -91,7 +92,8 @@ class StreamTar(object):
|
|||||||
errors.append([f["vp"], repr(ex)])
|
errors.append([f["vp"], repr(ex)])
|
||||||
|
|
||||||
if errors:
|
if errors:
|
||||||
self.errf = errdesc(errors)
|
self.errf, txt = errdesc(errors)
|
||||||
|
self.log("\n".join(([repr(self.errf)] + txt[1:])))
|
||||||
self.ser(self.errf)
|
self.ser(self.errf)
|
||||||
|
|
||||||
self.tar.close()
|
self.tar.close()
|
||||||
|
|||||||
@@ -25,4 +25,4 @@ def errdesc(errors):
|
|||||||
"vp": "archive-errors-{}.txt".format(dt),
|
"vp": "archive-errors-{}.txt".format(dt),
|
||||||
"ap": tf_path,
|
"ap": tf_path,
|
||||||
"st": os.stat(tf_path),
|
"st": os.stat(tf_path),
|
||||||
}
|
}, report
|
||||||
|
|||||||
@@ -5,12 +5,13 @@ import re
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
import shlex
|
||||||
import threading
|
import threading
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import calendar
|
import calendar
|
||||||
|
|
||||||
from .__init__ import PY2, WINDOWS, MACOS, VT100
|
from .__init__ import E, PY2, WINDOWS, MACOS, VT100
|
||||||
from .util import mp
|
from .util import mp, start_log_thrs, start_stackmon
|
||||||
from .authsrv import AuthSrv
|
from .authsrv import AuthSrv
|
||||||
from .tcpsrv import TcpSrv
|
from .tcpsrv import TcpSrv
|
||||||
from .up2k import Up2k
|
from .up2k import Up2k
|
||||||
@@ -28,14 +29,24 @@ class SvcHub(object):
|
|||||||
put() can return a queue (if want_reply=True) which has a blocking get() with the response.
|
put() can return a queue (if want_reply=True) which has a blocking get() with the response.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, args):
|
def __init__(self, args, argv, printed):
|
||||||
self.args = args
|
self.args = args
|
||||||
|
self.argv = argv
|
||||||
|
self.logf = None
|
||||||
|
|
||||||
self.ansi_re = re.compile("\033\\[[^m]*m")
|
self.ansi_re = re.compile("\033\\[[^m]*m")
|
||||||
self.log_mutex = threading.Lock()
|
self.log_mutex = threading.Lock()
|
||||||
self.next_day = 0
|
self.next_day = 0
|
||||||
|
|
||||||
self.log = self._log_disabled if args.q else self._log_enabled
|
self.log = self._log_disabled if args.q else self._log_enabled
|
||||||
|
if args.lo:
|
||||||
|
self._setup_logfile(printed)
|
||||||
|
|
||||||
|
if args.stackmon:
|
||||||
|
start_stackmon(args.stackmon, 0)
|
||||||
|
|
||||||
|
if args.log_thrs:
|
||||||
|
start_log_thrs(self.log, args.log_thrs, 0)
|
||||||
|
|
||||||
# initiate all services to manage
|
# initiate all services to manage
|
||||||
self.asrv = AuthSrv(self.args, self.log, False)
|
self.asrv = AuthSrv(self.args, self.log, False)
|
||||||
@@ -69,6 +80,52 @@ class SvcHub(object):
|
|||||||
|
|
||||||
self.broker = Broker(self)
|
self.broker = Broker(self)
|
||||||
|
|
||||||
|
def _logname(self):
|
||||||
|
dt = datetime.utcfromtimestamp(time.time())
|
||||||
|
fn = self.args.lo
|
||||||
|
for fs in "YmdHMS":
|
||||||
|
fs = "%" + fs
|
||||||
|
if fs in fn:
|
||||||
|
fn = fn.replace(fs, dt.strftime(fs))
|
||||||
|
|
||||||
|
return fn
|
||||||
|
|
||||||
|
def _setup_logfile(self, printed):
|
||||||
|
base_fn = fn = sel_fn = self._logname()
|
||||||
|
if fn != self.args.lo:
|
||||||
|
ctr = 0
|
||||||
|
# yup this is a race; if started sufficiently concurrently, two
|
||||||
|
# copyparties can grab the same logfile (considered and ignored)
|
||||||
|
while os.path.exists(sel_fn):
|
||||||
|
ctr += 1
|
||||||
|
sel_fn = "{}.{}".format(fn, ctr)
|
||||||
|
|
||||||
|
fn = sel_fn
|
||||||
|
|
||||||
|
try:
|
||||||
|
import lzma
|
||||||
|
|
||||||
|
lh = lzma.open(fn, "wt", encoding="utf-8", errors="replace", preset=0)
|
||||||
|
|
||||||
|
except:
|
||||||
|
import codecs
|
||||||
|
|
||||||
|
lh = codecs.open(fn, "w", encoding="utf-8", errors="replace")
|
||||||
|
|
||||||
|
lh.base_fn = base_fn
|
||||||
|
|
||||||
|
argv = [sys.executable] + self.argv
|
||||||
|
if hasattr(shlex, "quote"):
|
||||||
|
argv = [shlex.quote(x) for x in argv]
|
||||||
|
else:
|
||||||
|
argv = ['"{}"'.format(x) for x in argv]
|
||||||
|
|
||||||
|
msg = "[+] opened logfile [{}]\n".format(fn)
|
||||||
|
printed += msg
|
||||||
|
lh.write("t0: {:.3f}\nargv: {}\n\n{}".format(E.t0, " ".join(argv), printed))
|
||||||
|
self.logf = lh
|
||||||
|
print(msg, end="")
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
thr = threading.Thread(target=self.tcpsrv.run, name="svchub-main")
|
thr = threading.Thread(target=self.tcpsrv.run, name="svchub-main")
|
||||||
thr.daemon = True
|
thr.daemon = True
|
||||||
@@ -99,17 +156,28 @@ class SvcHub(object):
|
|||||||
print("nailed it", end="")
|
print("nailed it", end="")
|
||||||
finally:
|
finally:
|
||||||
print("\033[0m")
|
print("\033[0m")
|
||||||
|
if self.logf:
|
||||||
|
self.logf.close()
|
||||||
|
|
||||||
def _log_disabled(self, src, msg, c=0):
|
def _log_disabled(self, src, msg, c=0):
|
||||||
pass
|
if not self.logf:
|
||||||
|
return
|
||||||
|
|
||||||
def _log_enabled(self, src, msg, c=0):
|
|
||||||
"""handles logging from all components"""
|
|
||||||
with self.log_mutex:
|
with self.log_mutex:
|
||||||
|
ts = datetime.utcfromtimestamp(time.time())
|
||||||
|
ts = ts.strftime("%Y-%m%d-%H%M%S.%f")[:-3]
|
||||||
|
self.logf.write("@{} [{}] {}\n".format(ts, src, msg))
|
||||||
|
|
||||||
now = time.time()
|
now = time.time()
|
||||||
if now >= self.next_day:
|
if now >= self.next_day:
|
||||||
dt = datetime.utcfromtimestamp(now)
|
self._set_next_day()
|
||||||
print("\033[36m{}\033[0m\n".format(dt.strftime("%Y-%m-%d")), end="")
|
|
||||||
|
def _set_next_day(self):
|
||||||
|
if self.next_day and self.logf and self.logf.base_fn != self._logname():
|
||||||
|
self.logf.close()
|
||||||
|
self._setup_logfile("")
|
||||||
|
|
||||||
|
dt = datetime.utcfromtimestamp(time.time())
|
||||||
|
|
||||||
# unix timestamp of next 00:00:00 (leap-seconds safe)
|
# unix timestamp of next 00:00:00 (leap-seconds safe)
|
||||||
day_now = dt.day
|
day_now = dt.day
|
||||||
@@ -119,6 +187,15 @@ class SvcHub(object):
|
|||||||
dt = dt.replace(hour=0, minute=0, second=0)
|
dt = dt.replace(hour=0, minute=0, second=0)
|
||||||
self.next_day = calendar.timegm(dt.utctimetuple())
|
self.next_day = calendar.timegm(dt.utctimetuple())
|
||||||
|
|
||||||
|
def _log_enabled(self, src, msg, c=0):
|
||||||
|
"""handles logging from all components"""
|
||||||
|
with self.log_mutex:
|
||||||
|
now = time.time()
|
||||||
|
if now >= self.next_day:
|
||||||
|
dt = datetime.utcfromtimestamp(now)
|
||||||
|
print("\033[36m{}\033[0m\n".format(dt.strftime("%Y-%m-%d")), end="")
|
||||||
|
self._set_next_day()
|
||||||
|
|
||||||
fmt = "\033[36m{} \033[33m{:21} \033[0m{}\n"
|
fmt = "\033[36m{} \033[33m{:21} \033[0m{}\n"
|
||||||
if not VT100:
|
if not VT100:
|
||||||
fmt = "{} {:21} {}\n"
|
fmt = "{} {:21} {}\n"
|
||||||
@@ -144,20 +221,20 @@ class SvcHub(object):
|
|||||||
except:
|
except:
|
||||||
print(msg.encode("ascii", "replace").decode(), end="")
|
print(msg.encode("ascii", "replace").decode(), end="")
|
||||||
|
|
||||||
|
if self.logf:
|
||||||
|
self.logf.write(msg)
|
||||||
|
|
||||||
def check_mp_support(self):
|
def check_mp_support(self):
|
||||||
vmin = sys.version_info[1]
|
vmin = sys.version_info[1]
|
||||||
if WINDOWS:
|
if WINDOWS:
|
||||||
msg = "need python 3.3 or newer for multiprocessing;"
|
msg = "need python 3.3 or newer for multiprocessing;"
|
||||||
if PY2:
|
if PY2 or vmin < 3:
|
||||||
# py2 pickler doesn't support winsock
|
|
||||||
return msg
|
|
||||||
elif vmin < 3:
|
|
||||||
return msg
|
return msg
|
||||||
elif MACOS:
|
elif MACOS:
|
||||||
return "multiprocessing is wonky on mac osx;"
|
return "multiprocessing is wonky on mac osx;"
|
||||||
else:
|
else:
|
||||||
msg = "need python 2.7 or 3.3+ for multiprocessing;"
|
msg = "need python 3.3+ for multiprocessing;"
|
||||||
if not PY2 and vmin < 3:
|
if PY2 or vmin < 3:
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -189,5 +266,5 @@ class SvcHub(object):
|
|||||||
if not err:
|
if not err:
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
self.log("root", err)
|
self.log("svchub", err)
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -4,15 +4,14 @@ from __future__ import print_function, unicode_literals
|
|||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import zlib
|
import zlib
|
||||||
import struct
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from .sutil import errdesc
|
from .sutil import errdesc
|
||||||
from .util import yieldfile, sanitize_fn
|
from .util import yieldfile, sanitize_fn, spack, sunpack
|
||||||
|
|
||||||
|
|
||||||
def dostime2unix(buf):
|
def dostime2unix(buf):
|
||||||
t, d = struct.unpack("<HH", buf)
|
t, d = sunpack(b"<HH", buf)
|
||||||
|
|
||||||
ts = (t & 0x1F) * 2
|
ts = (t & 0x1F) * 2
|
||||||
tm = (t >> 5) & 0x3F
|
tm = (t >> 5) & 0x3F
|
||||||
@@ -36,13 +35,13 @@ def unixtime2dos(ts):
|
|||||||
|
|
||||||
bd = ((dy - 1980) << 9) + (dm << 5) + dd
|
bd = ((dy - 1980) << 9) + (dm << 5) + dd
|
||||||
bt = (th << 11) + (tm << 5) + ts // 2
|
bt = (th << 11) + (tm << 5) + ts // 2
|
||||||
return struct.pack("<HH", bt, bd)
|
return spack(b"<HH", bt, bd)
|
||||||
|
|
||||||
|
|
||||||
def gen_fdesc(sz, crc32, z64):
|
def gen_fdesc(sz, crc32, z64):
|
||||||
ret = b"\x50\x4b\x07\x08"
|
ret = b"\x50\x4b\x07\x08"
|
||||||
fmt = "<LQQ" if z64 else "<LLL"
|
fmt = b"<LQQ" if z64 else b"<LLL"
|
||||||
ret += struct.pack(fmt, crc32, sz, sz)
|
ret += spack(fmt, crc32, sz, sz)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
@@ -66,7 +65,7 @@ def gen_hdr(h_pos, fn, sz, lastmod, utf8, crc32, pre_crc):
|
|||||||
req_ver = b"\x2d\x00" if z64 else b"\x0a\x00"
|
req_ver = b"\x2d\x00" if z64 else b"\x0a\x00"
|
||||||
|
|
||||||
if crc32:
|
if crc32:
|
||||||
crc32 = struct.pack("<L", crc32)
|
crc32 = spack(b"<L", crc32)
|
||||||
else:
|
else:
|
||||||
crc32 = b"\x00" * 4
|
crc32 = b"\x00" * 4
|
||||||
|
|
||||||
@@ -87,14 +86,14 @@ def gen_hdr(h_pos, fn, sz, lastmod, utf8, crc32, pre_crc):
|
|||||||
# however infozip does actual sz and it even works on winxp
|
# however infozip does actual sz and it even works on winxp
|
||||||
# (same reasning for z64 extradata later)
|
# (same reasning for z64 extradata later)
|
||||||
vsz = 0xFFFFFFFF if z64 else sz
|
vsz = 0xFFFFFFFF if z64 else sz
|
||||||
ret += struct.pack("<LL", vsz, vsz)
|
ret += spack(b"<LL", vsz, vsz)
|
||||||
|
|
||||||
# windows support (the "?" replace below too)
|
# windows support (the "?" replace below too)
|
||||||
fn = sanitize_fn(fn, ok="/")
|
fn = sanitize_fn(fn, "/", [])
|
||||||
bfn = fn.encode("utf-8" if utf8 else "cp437", "replace").replace(b"?", b"_")
|
bfn = fn.encode("utf-8" if utf8 else "cp437", "replace").replace(b"?", b"_")
|
||||||
|
|
||||||
z64_len = len(z64v) * 8 + 4 if z64v else 0
|
z64_len = len(z64v) * 8 + 4 if z64v else 0
|
||||||
ret += struct.pack("<HH", len(bfn), z64_len)
|
ret += spack(b"<HH", len(bfn), z64_len)
|
||||||
|
|
||||||
if h_pos is not None:
|
if h_pos is not None:
|
||||||
# 2b comment, 2b diskno
|
# 2b comment, 2b diskno
|
||||||
@@ -106,12 +105,12 @@ def gen_hdr(h_pos, fn, sz, lastmod, utf8, crc32, pre_crc):
|
|||||||
ret += b"\x01\x00\x00\x00\xa4\x81"
|
ret += b"\x01\x00\x00\x00\xa4\x81"
|
||||||
|
|
||||||
# 4b local-header-ofs
|
# 4b local-header-ofs
|
||||||
ret += struct.pack("<L", min(h_pos, 0xFFFFFFFF))
|
ret += spack(b"<L", min(h_pos, 0xFFFFFFFF))
|
||||||
|
|
||||||
ret += bfn
|
ret += bfn
|
||||||
|
|
||||||
if z64v:
|
if z64v:
|
||||||
ret += struct.pack("<HH" + "Q" * len(z64v), 1, len(z64v) * 8, *z64v)
|
ret += spack(b"<HH" + b"Q" * len(z64v), 1, len(z64v) * 8, *z64v)
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
@@ -136,7 +135,7 @@ def gen_ecdr(items, cdir_pos, cdir_end):
|
|||||||
need_64 = nitems == 0xFFFF or 0xFFFFFFFF in [csz, cpos]
|
need_64 = nitems == 0xFFFF or 0xFFFFFFFF in [csz, cpos]
|
||||||
|
|
||||||
# 2b tnfiles, 2b dnfiles, 4b dir sz, 4b dir pos
|
# 2b tnfiles, 2b dnfiles, 4b dir sz, 4b dir pos
|
||||||
ret += struct.pack("<HHLL", nitems, nitems, csz, cpos)
|
ret += spack(b"<HHLL", nitems, nitems, csz, cpos)
|
||||||
|
|
||||||
# 2b comment length
|
# 2b comment length
|
||||||
ret += b"\x00\x00"
|
ret += b"\x00\x00"
|
||||||
@@ -163,7 +162,7 @@ def gen_ecdr64(items, cdir_pos, cdir_end):
|
|||||||
|
|
||||||
# 8b tnfiles, 8b dnfiles, 8b dir sz, 8b dir pos
|
# 8b tnfiles, 8b dnfiles, 8b dir sz, 8b dir pos
|
||||||
cdir_sz = cdir_end - cdir_pos
|
cdir_sz = cdir_end - cdir_pos
|
||||||
ret += struct.pack("<QQQQ", len(items), len(items), cdir_sz, cdir_pos)
|
ret += spack(b"<QQQQ", len(items), len(items), cdir_sz, cdir_pos)
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
@@ -178,13 +177,14 @@ def gen_ecdr64_loc(ecdr64_pos):
|
|||||||
ret = b"\x50\x4b\x06\x07"
|
ret = b"\x50\x4b\x06\x07"
|
||||||
|
|
||||||
# 4b cdisk, 8b start of ecdr64, 4b ndisks
|
# 4b cdisk, 8b start of ecdr64, 4b ndisks
|
||||||
ret += struct.pack("<LQL", 0, ecdr64_pos, 1)
|
ret += spack(b"<LQL", 0, ecdr64_pos, 1)
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
class StreamZip(object):
|
class StreamZip(object):
|
||||||
def __init__(self, fgen, utf8=False, pre_crc=False):
|
def __init__(self, log, fgen, utf8=False, pre_crc=False):
|
||||||
|
self.log = log
|
||||||
self.fgen = fgen
|
self.fgen = fgen
|
||||||
self.utf8 = utf8
|
self.utf8 = utf8
|
||||||
self.pre_crc = pre_crc
|
self.pre_crc = pre_crc
|
||||||
@@ -247,8 +247,8 @@ class StreamZip(object):
|
|||||||
errors.append([f["vp"], repr(ex)])
|
errors.append([f["vp"], repr(ex)])
|
||||||
|
|
||||||
if errors:
|
if errors:
|
||||||
errf = errdesc(errors)
|
errf, txt = errdesc(errors)
|
||||||
print(repr(errf))
|
self.log("\n".join(([repr(errf)] + txt[1:])))
|
||||||
for x in self.ser(errf):
|
for x in self.ser(errf):
|
||||||
yield x
|
yield x
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,9 @@
|
|||||||
from __future__ import print_function, unicode_literals
|
from __future__ import print_function, unicode_literals
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import time
|
|
||||||
import socket
|
import socket
|
||||||
import select
|
|
||||||
|
|
||||||
from .util import chkcmd, Counter
|
from .util import chkcmd
|
||||||
|
|
||||||
|
|
||||||
class TcpSrv(object):
|
class TcpSrv(object):
|
||||||
@@ -20,7 +18,6 @@ class TcpSrv(object):
|
|||||||
self.args = hub.args
|
self.args = hub.args
|
||||||
self.log = hub.log
|
self.log = hub.log
|
||||||
|
|
||||||
self.num_clients = Counter()
|
|
||||||
self.stopping = False
|
self.stopping = False
|
||||||
|
|
||||||
ip = "127.0.0.1"
|
ip = "127.0.0.1"
|
||||||
@@ -66,44 +63,13 @@ class TcpSrv(object):
|
|||||||
for srv in self.srv:
|
for srv in self.srv:
|
||||||
srv.listen(self.args.nc)
|
srv.listen(self.args.nc)
|
||||||
ip, port = srv.getsockname()
|
ip, port = srv.getsockname()
|
||||||
self.log("tcpsrv", "listening @ {0}:{1}".format(ip, port))
|
fno = srv.fileno()
|
||||||
|
msg = "listening @ {}:{} f{}".format(ip, port, fno)
|
||||||
|
self.log("tcpsrv", msg)
|
||||||
|
if self.args.q:
|
||||||
|
print(msg)
|
||||||
|
|
||||||
while not self.stopping:
|
self.hub.broker.put(False, "listen", srv)
|
||||||
if self.args.log_conn:
|
|
||||||
self.log("tcpsrv", "|%sC-ncli" % ("-" * 1,), c="1;30")
|
|
||||||
|
|
||||||
if self.num_clients.v >= self.args.nc:
|
|
||||||
time.sleep(0.1)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if self.args.log_conn:
|
|
||||||
self.log("tcpsrv", "|%sC-acc1" % ("-" * 2,), c="1;30")
|
|
||||||
|
|
||||||
try:
|
|
||||||
# macos throws bad-fd
|
|
||||||
ready, _, _ = select.select(self.srv, [], [])
|
|
||||||
except:
|
|
||||||
ready = []
|
|
||||||
if not self.stopping:
|
|
||||||
raise
|
|
||||||
|
|
||||||
for srv in ready:
|
|
||||||
if self.stopping:
|
|
||||||
break
|
|
||||||
|
|
||||||
sck, addr = srv.accept()
|
|
||||||
sip, sport = srv.getsockname()
|
|
||||||
if self.args.log_conn:
|
|
||||||
self.log(
|
|
||||||
"%s %s" % addr,
|
|
||||||
"|{}C-acc2 \033[0;36m{} \033[3{}m{}".format(
|
|
||||||
"-" * 3, sip, sport % 8, sport
|
|
||||||
),
|
|
||||||
c="1;30",
|
|
||||||
)
|
|
||||||
|
|
||||||
self.num_clients.add()
|
|
||||||
self.hub.broker.put(False, "httpconn", sck, addr)
|
|
||||||
|
|
||||||
def shutdown(self):
|
def shutdown(self):
|
||||||
self.stopping = True
|
self.stopping = True
|
||||||
|
|||||||
@@ -9,15 +9,11 @@ import hashlib
|
|||||||
import threading
|
import threading
|
||||||
import subprocess as sp
|
import subprocess as sp
|
||||||
|
|
||||||
from .__init__ import PY2
|
from .__init__ import PY2, unicode
|
||||||
from .util import fsenc, runcmd, Queue, Cooldown, BytesIO, min_ex
|
from .util import fsenc, runcmd, Queue, Cooldown, BytesIO, min_ex
|
||||||
from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, ffprobe
|
from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, ffprobe
|
||||||
|
|
||||||
|
|
||||||
if not PY2:
|
|
||||||
unicode = str
|
|
||||||
|
|
||||||
|
|
||||||
HAVE_PIL = False
|
HAVE_PIL = False
|
||||||
HAVE_HEIF = False
|
HAVE_HEIF = False
|
||||||
HAVE_AVIF = False
|
HAVE_AVIF = False
|
||||||
@@ -53,7 +49,7 @@ except:
|
|||||||
# https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html
|
# https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html
|
||||||
# ffmpeg -formats
|
# ffmpeg -formats
|
||||||
FMT_PIL = "bmp dib gif icns ico jpg jpeg jp2 jpx pcx png pbm pgm ppm pnm sgi tga tif tiff webp xbm dds xpm"
|
FMT_PIL = "bmp dib gif icns ico jpg jpeg jp2 jpx pcx png pbm pgm ppm pnm sgi tga tif tiff webp xbm dds xpm"
|
||||||
FMT_FF = "av1 asf avi flv m4v mkv mjpeg mjpg mpg mpeg mpg2 mpeg2 h264 avc h265 hevc mov 3gp mp4 ts mpegts nut ogv ogm rm vob webm wmv"
|
FMT_FF = "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"
|
||||||
|
|
||||||
if HAVE_HEIF:
|
if HAVE_HEIF:
|
||||||
FMT_PIL += " heif heifs heic heics"
|
FMT_PIL += " heif heifs heic heics"
|
||||||
@@ -134,6 +130,7 @@ class ThumbSrv(object):
|
|||||||
msg += ", ".join(missing)
|
msg += ", ".join(missing)
|
||||||
self.log(msg, c=3)
|
self.log(msg, c=3)
|
||||||
|
|
||||||
|
if self.args.th_clean:
|
||||||
t = threading.Thread(target=self.cleaner, name="thumb-cleaner")
|
t = threading.Thread(target=self.cleaner, name="thumb-cleaner")
|
||||||
t.daemon = True
|
t.daemon = True
|
||||||
t.start()
|
t.start()
|
||||||
@@ -263,7 +260,7 @@ class ThumbSrv(object):
|
|||||||
pass # default q = 75
|
pass # default q = 75
|
||||||
|
|
||||||
if im.mode not in fmts:
|
if im.mode not in fmts:
|
||||||
print("conv {}".format(im.mode))
|
# print("conv {}".format(im.mode))
|
||||||
im = im.convert("RGB")
|
im = im.convert("RGB")
|
||||||
|
|
||||||
im.save(tpath, quality=40, method=6)
|
im.save(tpath, quality=40, method=6)
|
||||||
|
|||||||
@@ -103,13 +103,15 @@ class Up2k(object):
|
|||||||
self.deferred_init()
|
self.deferred_init()
|
||||||
else:
|
else:
|
||||||
t = threading.Thread(
|
t = threading.Thread(
|
||||||
target=self.deferred_init,
|
target=self.deferred_init, name="up2k-deferred-init", args=(0.5,)
|
||||||
name="up2k-deferred-init",
|
|
||||||
)
|
)
|
||||||
t.daemon = True
|
t.daemon = True
|
||||||
t.start()
|
t.start()
|
||||||
|
|
||||||
def deferred_init(self):
|
def deferred_init(self, wait=0):
|
||||||
|
if wait:
|
||||||
|
time.sleep(wait)
|
||||||
|
|
||||||
all_vols = self.asrv.vfs.all_vols
|
all_vols = self.asrv.vfs.all_vols
|
||||||
have_e2d = self.init_indexes(all_vols)
|
have_e2d = self.init_indexes(all_vols)
|
||||||
|
|
||||||
@@ -193,7 +195,7 @@ class Up2k(object):
|
|||||||
|
|
||||||
return True, ret
|
return True, ret
|
||||||
|
|
||||||
def init_indexes(self, all_vols, scan_vols=[]):
|
def init_indexes(self, all_vols, scan_vols=None):
|
||||||
self.pp = ProgressPrinter()
|
self.pp = ProgressPrinter()
|
||||||
vols = all_vols.values()
|
vols = all_vols.values()
|
||||||
t0 = time.time()
|
t0 = time.time()
|
||||||
@@ -342,7 +344,15 @@ class Up2k(object):
|
|||||||
for k, v in flags.items()
|
for k, v in flags.items()
|
||||||
]
|
]
|
||||||
if a:
|
if a:
|
||||||
self.log(" ".join(sorted(a)) + "\033[0m")
|
vpath = "?"
|
||||||
|
for k, v in self.asrv.vfs.all_vols.items():
|
||||||
|
if v.realpath == ptop:
|
||||||
|
vpath = k
|
||||||
|
|
||||||
|
if vpath:
|
||||||
|
vpath += "/"
|
||||||
|
|
||||||
|
self.log("/{} {}".format(vpath, " ".join(sorted(a))), "35")
|
||||||
|
|
||||||
reg = {}
|
reg = {}
|
||||||
path = os.path.join(histpath, "up2k.snap")
|
path = os.path.join(histpath, "up2k.snap")
|
||||||
@@ -401,7 +411,7 @@ class Up2k(object):
|
|||||||
if WINDOWS:
|
if WINDOWS:
|
||||||
excl = [x.replace("/", "\\") for x in excl]
|
excl = [x.replace("/", "\\") for x in excl]
|
||||||
|
|
||||||
n_add = self._build_dir(dbw, top, set(excl), top, nohash)
|
n_add = self._build_dir(dbw, top, set(excl), top, nohash, [])
|
||||||
n_rm = self._drop_lost(dbw[0], top)
|
n_rm = self._drop_lost(dbw[0], top)
|
||||||
if dbw[1]:
|
if dbw[1]:
|
||||||
self.log("commit {} new files".format(dbw[1]))
|
self.log("commit {} new files".format(dbw[1]))
|
||||||
@@ -409,11 +419,25 @@ class Up2k(object):
|
|||||||
|
|
||||||
return True, n_add or n_rm or do_vac
|
return True, n_add or n_rm or do_vac
|
||||||
|
|
||||||
def _build_dir(self, dbw, top, excl, cdir, nohash):
|
def _build_dir(self, dbw, top, excl, cdir, nohash, seen):
|
||||||
|
rcdir = cdir
|
||||||
|
if not ANYWIN:
|
||||||
|
try:
|
||||||
|
# a bit expensive but worth
|
||||||
|
rcdir = os.path.realpath(cdir)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if rcdir in seen:
|
||||||
|
m = "bailing from symlink loop,\n prev: {}\n curr: {}\n from: {}"
|
||||||
|
self.log(m.format(seen[-1], rcdir, cdir), 3)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
seen = seen + [cdir]
|
||||||
self.pp.msg = "a{} {}".format(self.pp.n, cdir)
|
self.pp.msg = "a{} {}".format(self.pp.n, cdir)
|
||||||
histpath = self.asrv.vfs.histtab[top]
|
histpath = self.asrv.vfs.histtab[top]
|
||||||
ret = 0
|
ret = 0
|
||||||
g = statdir(self.log, not self.args.no_scandir, False, cdir)
|
g = statdir(self.log_func, not self.args.no_scandir, False, cdir)
|
||||||
for iname, inf in sorted(g):
|
for iname, inf in sorted(g):
|
||||||
abspath = os.path.join(cdir, iname)
|
abspath = os.path.join(cdir, iname)
|
||||||
lmod = int(inf.st_mtime)
|
lmod = int(inf.st_mtime)
|
||||||
@@ -422,7 +446,7 @@ class Up2k(object):
|
|||||||
if abspath in excl or abspath == histpath:
|
if abspath in excl or abspath == histpath:
|
||||||
continue
|
continue
|
||||||
# self.log(" dir: {}".format(abspath))
|
# self.log(" dir: {}".format(abspath))
|
||||||
ret += self._build_dir(dbw, top, excl, abspath, nohash)
|
ret += self._build_dir(dbw, top, excl, abspath, nohash, seen)
|
||||||
else:
|
else:
|
||||||
# self.log("file: {}".format(abspath))
|
# self.log("file: {}".format(abspath))
|
||||||
rp = abspath[len(top) + 1 :]
|
rp = abspath[len(top) + 1 :]
|
||||||
@@ -967,7 +991,7 @@ class Up2k(object):
|
|||||||
if cj["ptop"] not in self.registry:
|
if cj["ptop"] not in self.registry:
|
||||||
raise Pebkac(410, "location unavailable")
|
raise Pebkac(410, "location unavailable")
|
||||||
|
|
||||||
cj["name"] = sanitize_fn(cj["name"], bad=[".prologue.html", ".epilogue.html"])
|
cj["name"] = sanitize_fn(cj["name"], "", [".prologue.html", ".epilogue.html"])
|
||||||
cj["poke"] = time.time()
|
cj["poke"] = time.time()
|
||||||
wark = self._get_wark(cj)
|
wark = self._get_wark(cj)
|
||||||
now = time.time()
|
now = time.time()
|
||||||
@@ -1019,6 +1043,7 @@ class Up2k(object):
|
|||||||
break
|
break
|
||||||
except:
|
except:
|
||||||
# missing; restart
|
# missing; restart
|
||||||
|
if not self.args.nw:
|
||||||
job = None
|
job = None
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
@@ -1046,6 +1071,7 @@ class Up2k(object):
|
|||||||
pdir = os.path.join(cj["ptop"], cj["prel"])
|
pdir = os.path.join(cj["ptop"], cj["prel"])
|
||||||
job["name"] = self._untaken(pdir, cj["name"], now, cj["addr"])
|
job["name"] = self._untaken(pdir, cj["name"], now, cj["addr"])
|
||||||
dst = os.path.join(job["ptop"], job["prel"], job["name"])
|
dst = os.path.join(job["ptop"], job["prel"], job["name"])
|
||||||
|
if not self.args.nw:
|
||||||
os.unlink(fsenc(dst)) # TODO ed pls
|
os.unlink(fsenc(dst)) # TODO ed pls
|
||||||
self._symlink(src, dst)
|
self._symlink(src, dst)
|
||||||
|
|
||||||
@@ -1089,6 +1115,9 @@ class Up2k(object):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def _untaken(self, fdir, fname, ts, ip):
|
def _untaken(self, fdir, fname, ts, ip):
|
||||||
|
if self.args.nw:
|
||||||
|
return fname
|
||||||
|
|
||||||
# TODO broker which avoid this race and
|
# TODO broker which avoid this race and
|
||||||
# provides a new filename if taken (same as bup)
|
# provides a new filename if taken (same as bup)
|
||||||
suffix = ".{:.6f}-{}".format(ts, ip)
|
suffix = ".{:.6f}-{}".format(ts, ip)
|
||||||
@@ -1098,6 +1127,9 @@ class Up2k(object):
|
|||||||
def _symlink(self, src, dst):
|
def _symlink(self, src, dst):
|
||||||
# TODO store this in linktab so we never delete src if there are links to it
|
# TODO store this in linktab so we never delete src if there are links to it
|
||||||
self.log("linking dupe:\n {0}\n {1}".format(src, dst))
|
self.log("linking dupe:\n {0}\n {1}".format(src, dst))
|
||||||
|
if self.args.nw:
|
||||||
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
lsrc = src
|
lsrc = src
|
||||||
ldst = dst
|
ldst = dst
|
||||||
@@ -1175,6 +1207,10 @@ class Up2k(object):
|
|||||||
if ret > 0:
|
if ret > 0:
|
||||||
return ret, src
|
return ret, src
|
||||||
|
|
||||||
|
if self.args.nw:
|
||||||
|
# del self.registry[ptop][wark]
|
||||||
|
return ret, dst
|
||||||
|
|
||||||
atomic_move(src, dst)
|
atomic_move(src, dst)
|
||||||
|
|
||||||
if ANYWIN:
|
if ANYWIN:
|
||||||
@@ -1284,6 +1320,10 @@ class Up2k(object):
|
|||||||
if self.args.dotpart:
|
if self.args.dotpart:
|
||||||
tnam = "." + tnam
|
tnam = "." + tnam
|
||||||
|
|
||||||
|
if self.args.nw:
|
||||||
|
job["tnam"] = tnam
|
||||||
|
return
|
||||||
|
|
||||||
suffix = ".{:.6f}-{}".format(job["t0"], job["addr"])
|
suffix = ".{:.6f}-{}".format(job["t0"], job["addr"])
|
||||||
with ren_open(tnam, "wb", fdir=pdir, suffix=suffix) as f:
|
with ren_open(tnam, "wb", fdir=pdir, suffix=suffix) as f:
|
||||||
f, job["tnam"] = f["orz"]
|
f, job["tnam"] = f["orz"]
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import mimetypes
|
|||||||
import contextlib
|
import contextlib
|
||||||
import subprocess as sp # nosec
|
import subprocess as sp # nosec
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from collections import Counter
|
||||||
|
|
||||||
from .__init__ import PY2, WINDOWS, ANYWIN
|
from .__init__ import PY2, WINDOWS, ANYWIN
|
||||||
from .stolen import surrogateescape
|
from .stolen import surrogateescape
|
||||||
@@ -42,6 +43,20 @@ else:
|
|||||||
from Queue import Queue # pylint: disable=import-error,no-name-in-module
|
from Queue import Queue # pylint: disable=import-error,no-name-in-module
|
||||||
from StringIO import StringIO as BytesIO
|
from StringIO import StringIO as BytesIO
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
struct.unpack(b">i", b"idgi")
|
||||||
|
spack = struct.pack
|
||||||
|
sunpack = struct.unpack
|
||||||
|
except:
|
||||||
|
|
||||||
|
def spack(f, *a, **ka):
|
||||||
|
return struct.pack(f.decode("ascii"), *a, **ka)
|
||||||
|
|
||||||
|
def sunpack(f, *a, **ka):
|
||||||
|
return struct.unpack(f.decode("ascii"), *a, **ka)
|
||||||
|
|
||||||
|
|
||||||
surrogateescape.register_surrogateescape()
|
surrogateescape.register_surrogateescape()
|
||||||
FS_ENCODING = sys.getfilesystemencoding()
|
FS_ENCODING = sys.getfilesystemencoding()
|
||||||
if WINDOWS and PY2:
|
if WINDOWS and PY2:
|
||||||
@@ -123,20 +138,6 @@ REKOBO_KEY = {
|
|||||||
REKOBO_LKEY = {k.lower(): v for k, v in REKOBO_KEY.items()}
|
REKOBO_LKEY = {k.lower(): v for k, v in REKOBO_KEY.items()}
|
||||||
|
|
||||||
|
|
||||||
class Counter(object):
|
|
||||||
def __init__(self, v=0):
|
|
||||||
self.v = v
|
|
||||||
self.mutex = threading.Lock()
|
|
||||||
|
|
||||||
def add(self, delta=1):
|
|
||||||
with self.mutex:
|
|
||||||
self.v += delta
|
|
||||||
|
|
||||||
def set(self, absval):
|
|
||||||
with self.mutex:
|
|
||||||
self.v = absval
|
|
||||||
|
|
||||||
|
|
||||||
class Cooldown(object):
|
class Cooldown(object):
|
||||||
def __init__(self, maxage):
|
def __init__(self, maxage):
|
||||||
self.maxage = maxage
|
self.maxage = maxage
|
||||||
@@ -231,7 +232,7 @@ def nuprint(msg):
|
|||||||
|
|
||||||
def rice_tid():
|
def rice_tid():
|
||||||
tid = threading.current_thread().ident
|
tid = threading.current_thread().ident
|
||||||
c = struct.unpack(b"B" * 5, struct.pack(b">Q", tid)[-5:])
|
c = sunpack(b"B" * 5, spack(b">Q", tid)[-5:])
|
||||||
return "".join("\033[1;37;48;5;{}m{:02x}".format(x, x) for x in c) + "\033[0m"
|
return "".join("\033[1;37;48;5;{}m{:02x}".format(x, x) for x in c) + "\033[0m"
|
||||||
|
|
||||||
|
|
||||||
@@ -282,15 +283,69 @@ def alltrace():
|
|||||||
return "\n".join(rret + bret)
|
return "\n".join(rret + bret)
|
||||||
|
|
||||||
|
|
||||||
|
def start_stackmon(arg_str, nid):
|
||||||
|
suffix = "-{}".format(nid) if nid else ""
|
||||||
|
fp, f = arg_str.rsplit(",", 1)
|
||||||
|
f = int(f)
|
||||||
|
t = threading.Thread(
|
||||||
|
target=stackmon,
|
||||||
|
args=(fp, f, suffix),
|
||||||
|
name="stackmon" + suffix,
|
||||||
|
)
|
||||||
|
t.daemon = True
|
||||||
|
t.start()
|
||||||
|
|
||||||
|
|
||||||
|
def stackmon(fp, ival, suffix):
|
||||||
|
ctr = 0
|
||||||
|
while True:
|
||||||
|
ctr += 1
|
||||||
|
time.sleep(ival)
|
||||||
|
st = "{}, {}\n{}".format(ctr, time.time(), alltrace())
|
||||||
|
with open(fp + suffix, "wb") as f:
|
||||||
|
f.write(st.encode("utf-8", "replace"))
|
||||||
|
|
||||||
|
|
||||||
|
def start_log_thrs(logger, ival, nid):
|
||||||
|
ival = int(ival)
|
||||||
|
tname = lname = "log-thrs"
|
||||||
|
if nid:
|
||||||
|
tname = "logthr-n{}-i{:x}".format(nid, os.getpid())
|
||||||
|
lname = tname[3:]
|
||||||
|
|
||||||
|
t = threading.Thread(
|
||||||
|
target=log_thrs,
|
||||||
|
args=(logger, ival, lname),
|
||||||
|
name=tname,
|
||||||
|
)
|
||||||
|
t.daemon = True
|
||||||
|
t.start()
|
||||||
|
|
||||||
|
|
||||||
|
def log_thrs(log, ival, name):
|
||||||
|
while True:
|
||||||
|
time.sleep(ival)
|
||||||
|
tv = [x.name for x in threading.enumerate()]
|
||||||
|
tv = [
|
||||||
|
x.split("-")[0]
|
||||||
|
if x.startswith("httpconn-") or x.startswith("thumb-")
|
||||||
|
else "listen"
|
||||||
|
if "-listen-" in x
|
||||||
|
else x
|
||||||
|
for x in tv
|
||||||
|
if not x.startswith("pydevd.")
|
||||||
|
]
|
||||||
|
tv = ["{}\033[36m{}".format(v, k) for k, v in sorted(Counter(tv).items())]
|
||||||
|
log(name, "\033[0m \033[33m".join(tv), 3)
|
||||||
|
|
||||||
|
|
||||||
def min_ex():
|
def min_ex():
|
||||||
et, ev, tb = sys.exc_info()
|
et, ev, tb = sys.exc_info()
|
||||||
tb = traceback.extract_tb(tb, 2)
|
tb = traceback.extract_tb(tb)
|
||||||
ex = [
|
fmt = "{} @ {} <{}>: {}"
|
||||||
"{} @ {} <{}>: {}".format(fp.split(os.sep)[-1], ln, fun, txt)
|
ex = [fmt.format(fp.split(os.sep)[-1], ln, fun, txt) for fp, ln, fun, txt in tb]
|
||||||
for fp, ln, fun, txt in tb
|
ex.append("[{}] {}".format(et.__name__, ev))
|
||||||
]
|
return "\n".join(ex[-8:])
|
||||||
ex.append("{}: {}".format(et.__name__, ev))
|
|
||||||
return "\n".join(ex)
|
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
@@ -674,7 +729,7 @@ def undot(path):
|
|||||||
return "/".join(ret)
|
return "/".join(ret)
|
||||||
|
|
||||||
|
|
||||||
def sanitize_fn(fn, ok="", bad=[]):
|
def sanitize_fn(fn, ok, bad):
|
||||||
if "/" not in ok:
|
if "/" not in ok:
|
||||||
fn = fn.replace("\\", "/").split("/")[-1]
|
fn = fn.replace("\\", "/").split("/")[-1]
|
||||||
|
|
||||||
@@ -904,16 +959,10 @@ def yieldfile(fn):
|
|||||||
yield buf
|
yield buf
|
||||||
|
|
||||||
|
|
||||||
def hashcopy(actor, fin, fout):
|
def hashcopy(fin, fout):
|
||||||
is_mp = actor.is_mp
|
|
||||||
hashobj = hashlib.sha512()
|
hashobj = hashlib.sha512()
|
||||||
tlen = 0
|
tlen = 0
|
||||||
for buf in fin:
|
for buf in fin:
|
||||||
if is_mp:
|
|
||||||
actor.workload += 1
|
|
||||||
if actor.workload > 2 ** 31:
|
|
||||||
actor.workload = 100
|
|
||||||
|
|
||||||
tlen += len(buf)
|
tlen += len(buf)
|
||||||
hashobj.update(buf)
|
hashobj.update(buf)
|
||||||
fout.write(buf)
|
fout.write(buf)
|
||||||
@@ -924,15 +973,10 @@ def hashcopy(actor, fin, fout):
|
|||||||
return tlen, hashobj.hexdigest(), digest_b64
|
return tlen, hashobj.hexdigest(), digest_b64
|
||||||
|
|
||||||
|
|
||||||
def sendfile_py(lower, upper, f, s, actor=None):
|
def sendfile_py(lower, upper, f, s):
|
||||||
remains = upper - lower
|
remains = upper - lower
|
||||||
f.seek(lower)
|
f.seek(lower)
|
||||||
while remains > 0:
|
while remains > 0:
|
||||||
if actor:
|
|
||||||
actor.workload += 1
|
|
||||||
if actor.workload > 2 ** 31:
|
|
||||||
actor.workload = 100
|
|
||||||
|
|
||||||
# time.sleep(0.01)
|
# time.sleep(0.01)
|
||||||
buf = f.read(min(1024 * 32, remains))
|
buf = f.read(min(1024 * 32, remains))
|
||||||
if not buf:
|
if not buf:
|
||||||
@@ -979,8 +1023,7 @@ def statdir(logger, scandir, lstat, top):
|
|||||||
try:
|
try:
|
||||||
yield [fsdec(fh.name), fh.stat(follow_symlinks=not lstat)]
|
yield [fsdec(fh.name), fh.stat(follow_symlinks=not lstat)]
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
msg = "scan-stat: \033[36m{} @ {}"
|
logger(src, "[s] {} @ {}".format(repr(ex), fsdec(fh.path)), 6)
|
||||||
logger(msg.format(repr(ex), fsdec(fh.path)))
|
|
||||||
else:
|
else:
|
||||||
src = "listdir"
|
src = "listdir"
|
||||||
fun = os.lstat if lstat else os.stat
|
fun = os.lstat if lstat else os.stat
|
||||||
@@ -989,11 +1032,10 @@ def statdir(logger, scandir, lstat, top):
|
|||||||
try:
|
try:
|
||||||
yield [fsdec(name), fun(abspath)]
|
yield [fsdec(name), fun(abspath)]
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
msg = "list-stat: \033[36m{} @ {}"
|
logger(src, "[s] {} @ {}".format(repr(ex), fsdec(abspath)), 6)
|
||||||
logger(msg.format(repr(ex), fsdec(abspath)))
|
|
||||||
|
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
logger("{}: \033[31m{} @ {}".format(src, repr(ex), top))
|
logger(src, "{} @ {}".format(repr(ex), top), 1)
|
||||||
|
|
||||||
|
|
||||||
def unescape_cookie(orig):
|
def unescape_cookie(orig):
|
||||||
@@ -1030,7 +1072,13 @@ def guess_mime(url, fallback="application/octet-stream"):
|
|||||||
except:
|
except:
|
||||||
return fallback
|
return fallback
|
||||||
|
|
||||||
return MIMES.get(ext) or mimetypes.guess_type(url)[0] or fallback
|
ret = MIMES.get(ext) or mimetypes.guess_type(url)[0] or fallback
|
||||||
|
|
||||||
|
if ";" not in ret:
|
||||||
|
if ret.startswith("text/") or ret.endswith("/javascript"):
|
||||||
|
ret += "; charset=UTF-8"
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
def runcmd(*argv):
|
def runcmd(*argv):
|
||||||
@@ -1064,10 +1112,7 @@ def gzip_orig_sz(fn):
|
|||||||
with open(fsenc(fn), "rb") as f:
|
with open(fsenc(fn), "rb") as f:
|
||||||
f.seek(-4, 2)
|
f.seek(-4, 2)
|
||||||
rv = f.read(4)
|
rv = f.read(4)
|
||||||
try:
|
return sunpack(b"I", rv)[0]
|
||||||
return struct.unpack(b"I", rv)[0]
|
|
||||||
except:
|
|
||||||
return struct.unpack("I", rv)[0]
|
|
||||||
|
|
||||||
|
|
||||||
def py_desc():
|
def py_desc():
|
||||||
|
|||||||
@@ -28,10 +28,16 @@ window.baguetteBox = (function () {
|
|||||||
isOverlayVisible = false,
|
isOverlayVisible = false,
|
||||||
touch = {}, // start-pos
|
touch = {}, // start-pos
|
||||||
touchFlag = false, // busy
|
touchFlag = false, // busy
|
||||||
regex = /.+\.(gif|jpe?g|png|webp)/i,
|
re_i = /.+\.(gif|jpe?g|png|webp)(\?|$)/i,
|
||||||
|
re_v = /.+\.(webm|mp4)(\?|$)/i,
|
||||||
data = {}, // all galleries
|
data = {}, // all galleries
|
||||||
imagesElements = [],
|
imagesElements = [],
|
||||||
documentLastFocus = null;
|
documentLastFocus = null,
|
||||||
|
isFullscreen = false;
|
||||||
|
|
||||||
|
var onFSC = function (e) {
|
||||||
|
isFullscreen = !!document.fullscreenElement;
|
||||||
|
};
|
||||||
|
|
||||||
var overlayClickHandler = function (event) {
|
var overlayClickHandler = function (event) {
|
||||||
if (event.target.id.indexOf('baguette-img') !== -1) {
|
if (event.target.id.indexOf('baguette-img') !== -1) {
|
||||||
@@ -96,10 +102,6 @@ window.baguetteBox = (function () {
|
|||||||
data[selector] = selectorData;
|
data[selector] = selectorData;
|
||||||
|
|
||||||
[].forEach.call(galleryNodeList, function (galleryElement) {
|
[].forEach.call(galleryNodeList, function (galleryElement) {
|
||||||
if (userOptions && userOptions.filter) {
|
|
||||||
regex = userOptions.filter;
|
|
||||||
}
|
|
||||||
|
|
||||||
var tagsNodeList = [];
|
var tagsNodeList = [];
|
||||||
if (galleryElement.tagName === 'A') {
|
if (galleryElement.tagName === 'A') {
|
||||||
tagsNodeList = [galleryElement];
|
tagsNodeList = [galleryElement];
|
||||||
@@ -109,7 +111,7 @@ window.baguetteBox = (function () {
|
|||||||
|
|
||||||
tagsNodeList = [].filter.call(tagsNodeList, function (element) {
|
tagsNodeList = [].filter.call(tagsNodeList, function (element) {
|
||||||
if (element.className.indexOf(userOptions && userOptions.ignoreClass) === -1) {
|
if (element.className.indexOf(userOptions && userOptions.ignoreClass) === -1) {
|
||||||
return regex.test(element.href);
|
return re_i.test(element.href) || re_v.test(element.href);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (tagsNodeList.length === 0) {
|
if (tagsNodeList.length === 0) {
|
||||||
@@ -119,7 +121,7 @@ window.baguetteBox = (function () {
|
|||||||
var gallery = [];
|
var gallery = [];
|
||||||
[].forEach.call(tagsNodeList, function (imageElement, imageIndex) {
|
[].forEach.call(tagsNodeList, function (imageElement, imageIndex) {
|
||||||
var imageElementClickHandler = function (event) {
|
var imageElementClickHandler = function (event) {
|
||||||
if (event && event.ctrlKey)
|
if (event && (event.ctrlKey || event.metaKey))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
event.preventDefault ? event.preventDefault() : event.returnValue = false;
|
event.preventDefault ? event.preventDefault() : event.returnValue = false;
|
||||||
@@ -209,24 +211,46 @@ window.baguetteBox = (function () {
|
|||||||
bindEvents();
|
bindEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
function keyDownHandler(event) {
|
function keyDownHandler(e) {
|
||||||
switch (event.keyCode) {
|
if (e.ctrlKey || e.altKey || e.metaKey || e.isComposing)
|
||||||
case 37: // Left
|
return;
|
||||||
|
|
||||||
|
var k = e.code + '';
|
||||||
|
|
||||||
|
if (k == "ArrowLeft" || k == "KeyJ")
|
||||||
showPreviousImage();
|
showPreviousImage();
|
||||||
break;
|
else if (k == "ArrowRight" || k == "KeyL")
|
||||||
case 39: // Right
|
|
||||||
showNextImage();
|
showNextImage();
|
||||||
break;
|
else if (k == "Escape")
|
||||||
case 27: // Esc
|
|
||||||
hideOverlay();
|
hideOverlay();
|
||||||
break;
|
else if (k == "Home")
|
||||||
case 36: // Home
|
showFirstImage(e);
|
||||||
showFirstImage(event);
|
else if (k == "End")
|
||||||
break;
|
showLastImage(e);
|
||||||
case 35: // End
|
else if (k == "Space" || k == "KeyP" || k == "KeyK")
|
||||||
showLastImage(event);
|
playpause();
|
||||||
break;
|
else if (k == "KeyU" || k == "KeyO")
|
||||||
|
relseek(k == "KeyU" ? -10 : 10);
|
||||||
|
else if (k == "KeyM" && vid())
|
||||||
|
vid().muted = !vid().muted;
|
||||||
|
else if (k == "KeyF")
|
||||||
|
try {
|
||||||
|
if (isFullscreen)
|
||||||
|
document.exitFullscreen();
|
||||||
|
else
|
||||||
|
vid().requestFullscreen();
|
||||||
}
|
}
|
||||||
|
catch (ex) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
function keyUpHandler(e) {
|
||||||
|
if (e.ctrlKey || e.altKey || e.metaKey || e.isComposing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var k = e.code + '';
|
||||||
|
|
||||||
|
if (k == "Space")
|
||||||
|
ev(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
var passiveSupp = false;
|
var passiveSupp = false;
|
||||||
@@ -325,6 +349,8 @@ window.baguetteBox = (function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bind(document, 'keydown', keyDownHandler);
|
bind(document, 'keydown', keyDownHandler);
|
||||||
|
bind(document, 'keyup', keyUpHandler);
|
||||||
|
bind(document, 'fullscreenchange', onFSC);
|
||||||
currentIndex = chosenImageIndex;
|
currentIndex = chosenImageIndex;
|
||||||
touch = {
|
touch = {
|
||||||
count: 0,
|
count: 0,
|
||||||
@@ -366,6 +392,7 @@ window.baguetteBox = (function () {
|
|||||||
|
|
||||||
function hideOverlay(e) {
|
function hideOverlay(e) {
|
||||||
ev(e);
|
ev(e);
|
||||||
|
playvid(false);
|
||||||
if (options.noScrollbars) {
|
if (options.noScrollbars) {
|
||||||
document.documentElement.style.overflowY = 'auto';
|
document.documentElement.style.overflowY = 'auto';
|
||||||
document.body.style.overflowY = 'auto';
|
document.body.style.overflowY = 'auto';
|
||||||
@@ -375,6 +402,8 @@ window.baguetteBox = (function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
unbind(document, 'keydown', keyDownHandler);
|
unbind(document, 'keydown', keyDownHandler);
|
||||||
|
unbind(document, 'keyup', keyUpHandler);
|
||||||
|
unbind(document, 'fullscreenchange', onFSC);
|
||||||
// Fade out and hide the overlay
|
// Fade out and hide the overlay
|
||||||
overlay.className = '';
|
overlay.className = '';
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
@@ -398,8 +427,8 @@ window.baguetteBox = (function () {
|
|||||||
return; // out-of-bounds or gallery dirty
|
return; // out-of-bounds or gallery dirty
|
||||||
}
|
}
|
||||||
|
|
||||||
if (imageContainer.getElementsByTagName('img')[0]) {
|
if (imageContainer.querySelector('img, video')) {
|
||||||
// image is loaded, cb and bail
|
// was loaded, cb and bail
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
@@ -408,7 +437,7 @@ window.baguetteBox = (function () {
|
|||||||
|
|
||||||
var imageElement = galleryItem.imageElement,
|
var imageElement = galleryItem.imageElement,
|
||||||
imageSrc = imageElement.href,
|
imageSrc = imageElement.href,
|
||||||
thumbnailElement = imageElement.getElementsByTagName('img')[0],
|
thumbnailElement = imageElement.querySelector('img, video'),
|
||||||
imageCaption = typeof options.captions === 'function' ?
|
imageCaption = typeof options.captions === 'function' ?
|
||||||
options.captions.call(currentGallery, imageElement) :
|
options.captions.call(currentGallery, imageElement) :
|
||||||
imageElement.getAttribute('data-caption') || imageElement.title;
|
imageElement.getAttribute('data-caption') || imageElement.title;
|
||||||
@@ -428,16 +457,20 @@ window.baguetteBox = (function () {
|
|||||||
}
|
}
|
||||||
imageContainer.appendChild(figure);
|
imageContainer.appendChild(figure);
|
||||||
|
|
||||||
var image = mknod('img');
|
var is_vid = re_v.test(imageSrc),
|
||||||
image.onload = function () {
|
image = mknod(is_vid ? 'video' : 'img');
|
||||||
|
|
||||||
|
clmod(imageContainer, 'vid', is_vid);
|
||||||
|
|
||||||
|
image.addEventListener(is_vid ? 'loadedmetadata' : 'load', function () {
|
||||||
// Remove loader element
|
// Remove loader element
|
||||||
var spinner = document.querySelector('#baguette-img-' + index + ' .baguetteBox-spinner');
|
var spinner = document.querySelector('#baguette-img-' + index + ' .baguetteBox-spinner');
|
||||||
figure.removeChild(spinner);
|
figure.removeChild(spinner);
|
||||||
if (!options.async && callback) {
|
if (!options.async && callback)
|
||||||
callback();
|
callback();
|
||||||
}
|
});
|
||||||
};
|
|
||||||
image.setAttribute('src', imageSrc);
|
image.setAttribute('src', imageSrc);
|
||||||
|
image.setAttribute('controls', 'controls');
|
||||||
image.alt = thumbnailElement ? thumbnailElement.alt || '' : '';
|
image.alt = thumbnailElement ? thumbnailElement.alt || '' : '';
|
||||||
if (options.titleTag && imageCaption) {
|
if (options.titleTag && imageCaption) {
|
||||||
image.title = imageCaption;
|
image.title = imageCaption;
|
||||||
@@ -498,6 +531,7 @@ window.baguetteBox = (function () {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
playvid(false);
|
||||||
currentIndex = index;
|
currentIndex = index;
|
||||||
loadImage(currentIndex, function () {
|
loadImage(currentIndex, function () {
|
||||||
preloadNext(currentIndex);
|
preloadNext(currentIndex);
|
||||||
@@ -512,6 +546,26 @@ window.baguetteBox = (function () {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function vid() {
|
||||||
|
return imagesElements[currentIndex].querySelector('video');
|
||||||
|
}
|
||||||
|
|
||||||
|
function playvid(play) {
|
||||||
|
if (vid())
|
||||||
|
vid()[play ? 'play' : 'pause']();
|
||||||
|
}
|
||||||
|
|
||||||
|
function playpause() {
|
||||||
|
var v = vid();
|
||||||
|
if (v)
|
||||||
|
v[v.paused ? "play" : "pause"]();
|
||||||
|
}
|
||||||
|
|
||||||
|
function relseek(sec) {
|
||||||
|
if (vid())
|
||||||
|
vid().currentTime += sec;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Triggers the bounce animation
|
* Triggers the bounce animation
|
||||||
* @param {('left'|'right')} direction - Direction of the movement
|
* @param {('left'|'right')} direction - Direction of the movement
|
||||||
@@ -534,6 +588,8 @@ window.baguetteBox = (function () {
|
|||||||
} else {
|
} else {
|
||||||
slider.style.transform = 'translate3d(' + offset + ',0,0)';
|
slider.style.transform = 'translate3d(' + offset + ',0,0)';
|
||||||
}
|
}
|
||||||
|
playvid(false);
|
||||||
|
playvid(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function preloadNext(index) {
|
function preloadNext(index) {
|
||||||
@@ -566,6 +622,7 @@ window.baguetteBox = (function () {
|
|||||||
unbindEvents();
|
unbindEvents();
|
||||||
clearCachedData();
|
clearCachedData();
|
||||||
unbind(document, 'keydown', keyDownHandler);
|
unbind(document, 'keydown', keyDownHandler);
|
||||||
|
unbind(document, 'keyup', keyUpHandler);
|
||||||
document.getElementsByTagName('body')[0].removeChild(ebi('baguetteBox-overlay'));
|
document.getElementsByTagName('body')[0].removeChild(ebi('baguetteBox-overlay'));
|
||||||
data = {};
|
data = {};
|
||||||
currentGallery = [];
|
currentGallery = [];
|
||||||
@@ -577,6 +634,8 @@ window.baguetteBox = (function () {
|
|||||||
show: show,
|
show: show,
|
||||||
showNext: showNextImage,
|
showNext: showNextImage,
|
||||||
showPrevious: showPreviousImage,
|
showPrevious: showPreviousImage,
|
||||||
|
relseek: relseek,
|
||||||
|
playpause: playpause,
|
||||||
hide: hideOverlay,
|
hide: hideOverlay,
|
||||||
destroy: destroyPlugin
|
destroy: destroyPlugin
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -29,10 +29,10 @@ body {
|
|||||||
position: fixed;
|
position: fixed;
|
||||||
max-width: 34em;
|
max-width: 34em;
|
||||||
background: #222;
|
background: #222;
|
||||||
border: 0 solid #555;
|
border: 0 solid #777;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
padding: 0 1em;
|
padding: 0 1.3em;
|
||||||
height: 0;
|
height: 0;
|
||||||
opacity: .1;
|
opacity: .1;
|
||||||
transition: opacity 0.14s, height 0.14s, padding 0.14s;
|
transition: opacity 0.14s, height 0.14s, padding 0.14s;
|
||||||
@@ -40,19 +40,31 @@ body {
|
|||||||
border-radius: .4em;
|
border-radius: .4em;
|
||||||
z-index: 9001;
|
z-index: 9001;
|
||||||
}
|
}
|
||||||
|
#tt.b {
|
||||||
|
padding: 0 2em;
|
||||||
|
border-radius: .5em;
|
||||||
|
box-shadow: 0 .2em 1em #000;
|
||||||
|
}
|
||||||
#tt.show {
|
#tt.show {
|
||||||
padding: 1em;
|
padding: 1em 1.3em;
|
||||||
|
border-width: .4em 0;
|
||||||
height: auto;
|
height: auto;
|
||||||
border-width: .2em 0;
|
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
#tt.show.b {
|
||||||
|
padding: 1.5em 2em;
|
||||||
|
border-width: .5em 0;
|
||||||
|
}
|
||||||
#tt code {
|
#tt code {
|
||||||
background: #3c3c3c;
|
background: #3c3c3c;
|
||||||
padding: .2em .3em;
|
padding: .1em .3em;
|
||||||
border-top: 1px solid #777;
|
border-top: 1px solid #777;
|
||||||
border-radius: .3em;
|
border-radius: .3em;
|
||||||
font-family: monospace, monospace;
|
font-family: monospace, monospace;
|
||||||
line-height: 2em;
|
line-height: 1.7em;
|
||||||
|
}
|
||||||
|
#tt em {
|
||||||
|
color: #f6a;
|
||||||
}
|
}
|
||||||
#path,
|
#path,
|
||||||
#path * {
|
#path * {
|
||||||
@@ -607,7 +619,7 @@ input.eq_gain {
|
|||||||
#srch_q {
|
#srch_q {
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
color: #f80;
|
color: #f80;
|
||||||
height: 1em;
|
min-height: 1em;
|
||||||
margin: .2em 0 -1em 1.6em;
|
margin: .2em 0 -1em 1.6em;
|
||||||
}
|
}
|
||||||
#tq_raw {
|
#tq_raw {
|
||||||
@@ -812,11 +824,13 @@ input.eq_gain {
|
|||||||
border-bottom: 1px solid #555;
|
border-bottom: 1px solid #555;
|
||||||
}
|
}
|
||||||
#thumbs,
|
#thumbs,
|
||||||
#au_osd_cv {
|
#au_osd_cv,
|
||||||
|
#u2tdate {
|
||||||
opacity: .3;
|
opacity: .3;
|
||||||
}
|
}
|
||||||
#griden.on+#thumbs,
|
#griden.on+#thumbs,
|
||||||
#au_os_ctl.on+#au_osd_cv {
|
#au_os_ctl.on+#au_osd_cv,
|
||||||
|
#u2turbo.on+#u2tdate {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
#ghead {
|
#ghead {
|
||||||
@@ -921,13 +935,16 @@ html.light {
|
|||||||
}
|
}
|
||||||
html.light #tt {
|
html.light #tt {
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-color: #888;
|
border-color: #888 #000 #777 #000;
|
||||||
box-shadow: 0 .3em 1em rgba(0,0,0,0.4);
|
box-shadow: 0 .3em 1em rgba(0,0,0,0.4);
|
||||||
}
|
}
|
||||||
html.light #tt code {
|
html.light #tt code {
|
||||||
background: #060;
|
background: #060;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
html.light #tt em {
|
||||||
|
color: #d38;
|
||||||
|
}
|
||||||
html.light #ops,
|
html.light #ops,
|
||||||
html.light .opbox,
|
html.light .opbox,
|
||||||
html.light #srch_form {
|
html.light #srch_form {
|
||||||
@@ -1157,22 +1174,27 @@ html.light #tree::-webkit-scrollbar {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
#baguetteBox-overlay .full-image img {
|
#baguetteBox-overlay .full-image img,
|
||||||
|
#baguetteBox-overlay .full-image video {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: auto;
|
width: auto;
|
||||||
height: auto;
|
height: auto;
|
||||||
max-height: 100%;
|
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
max-height: calc(100% - 1.4em);
|
||||||
|
margin-bottom: 1.4em;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
box-shadow: 0 0 8px rgba(0, 0, 0, 0.6);
|
box-shadow: 0 0 8px rgba(0, 0, 0, 0.6);
|
||||||
}
|
}
|
||||||
|
#baguetteBox-overlay .full-image video {
|
||||||
|
background: #333;
|
||||||
|
}
|
||||||
#baguetteBox-overlay .full-image figcaption {
|
#baguetteBox-overlay .full-image figcaption {
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: fixed;
|
||||||
bottom: 0;
|
bottom: .1em;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 1.8;
|
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
<title>⇆🎉 {{ title }}</title>
|
<title>⇆🎉 {{ title }}</title>
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
||||||
<link rel="stylesheet" type="text/css" media="screen" href="/.cpr/browser.css{{ ts }}">
|
<link rel="stylesheet" type="text/css" media="screen" href="/.cpr/browser.css?_={{ ts }}">
|
||||||
<link rel="stylesheet" type="text/css" media="screen" href="/.cpr/upload.css{{ ts }}">
|
<link rel="stylesheet" type="text/css" media="screen" href="/.cpr/upload.css?_={{ ts }}">
|
||||||
{%- if css %}
|
{%- if css %}
|
||||||
<link rel="stylesheet" type="text/css" media="screen" href="{{ css }}{{ ts }}">
|
<link rel="stylesheet" type="text/css" media="screen" href="{{ css }}?_={{ ts }}">
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
@@ -110,7 +110,7 @@
|
|||||||
|
|
||||||
<div id="epi" class="logue">{{ logues[1] }}</div>
|
<div id="epi" class="logue">{{ logues[1] }}</div>
|
||||||
|
|
||||||
<h2><a href="?h">control-panel</a></h2>
|
<h2><a href="/?h">control-panel</a></h2>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -127,9 +127,9 @@
|
|||||||
have_tags_idx = {{ have_tags_idx|tojson }},
|
have_tags_idx = {{ have_tags_idx|tojson }},
|
||||||
have_zip = {{ have_zip|tojson }};
|
have_zip = {{ have_zip|tojson }};
|
||||||
</script>
|
</script>
|
||||||
<script src="/.cpr/util.js{{ ts }}"></script>
|
<script src="/.cpr/util.js?_={{ ts }}"></script>
|
||||||
<script src="/.cpr/browser.js{{ ts }}"></script>
|
<script src="/.cpr/browser.js?_={{ ts }}"></script>
|
||||||
<script src="/.cpr/up2k.js{{ ts }}"></script>
|
<script src="/.cpr/up2k.js?_={{ ts }}"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ ebi('op_up2k').innerHTML = (
|
|||||||
' <tr>\n' +
|
' <tr>\n' +
|
||||||
' <td>\n' +
|
' <td>\n' +
|
||||||
' <a href="#" id="nthread_sub">–</a><input\n' +
|
' <a href="#" id="nthread_sub">–</a><input\n' +
|
||||||
' class="txtbox" id="nthread" value="2"/><a\n' +
|
' class="txtbox" id="nthread" value="2" tt="pause uploads by setting it to 0"/><a\n' +
|
||||||
' href="#" id="nthread_add">+</a><br /> \n' +
|
' href="#" id="nthread_add">+</a><br /> \n' +
|
||||||
' </td>\n' +
|
' </td>\n' +
|
||||||
' </tr>\n' +
|
' </tr>\n' +
|
||||||
@@ -133,6 +133,13 @@ ebi('op_cfg').innerHTML = (
|
|||||||
(have_zip ? (
|
(have_zip ? (
|
||||||
'<div><h3>folder download</h3><div id="arc_fmt"></div></div>\n'
|
'<div><h3>folder download</h3><div id="arc_fmt"></div></div>\n'
|
||||||
) : '') +
|
) : '') +
|
||||||
|
'<div>\n' +
|
||||||
|
' <h3>up2k switches</h3>\n' +
|
||||||
|
' <div>\n' +
|
||||||
|
' <a id="u2turbo" class="tgl btn ttb" href="#" tt="the yolo button, you probably DO NOT want to enable this:$N$Nuse this if you were uploading a huge amount of files and had to restart for some reason, and want to continue the upload ASAP$N$Nthis replaces the hash-check with a simple <em>"does this have the same filesize on the server?"</em> so if the file contents are different it will NOT be uploaded$N$Nyou should turn this off when the upload is done, and then "upload" the same files again to let the client verify them">turbo</a>\n' +
|
||||||
|
' <a id="u2tdate" class="tgl btn ttb" href="#" tt="has no effect unless the turbo button is enabled$N$Nreduces the yolo factor by a tiny amount; checks whether the file timestamps on the server matches yours$N$Nshould <em>theoretically</em> catch most unfinished/corrupted uploads, but is not a substitute for doing a verification pass with turbo disabled afterwards">date-chk</a>\n' +
|
||||||
|
' </div>\n' +
|
||||||
|
'</div>\n' +
|
||||||
'<div><h3>key notation</h3><div id="key_notation"></div></div>\n' +
|
'<div><h3>key notation</h3><div id="key_notation"></div></div>\n' +
|
||||||
'<div class="fill"><h3>hidden columns</h3><div id="hcols"></div></div>'
|
'<div class="fill"><h3>hidden columns</h3><div id="hcols"></div></div>'
|
||||||
);
|
);
|
||||||
@@ -237,13 +244,17 @@ var mpl = (function () {
|
|||||||
'<a href="#" class="tgl btn" tt="load the next folder and continue">📂 next-folder</a>' +
|
'<a href="#" class="tgl btn" tt="load the next folder and continue">📂 next-folder</a>' +
|
||||||
'</div></div>' +
|
'</div></div>' +
|
||||||
|
|
||||||
|
'<div><h3>tint</h3><div>' +
|
||||||
|
'<input type="text" id="pb_tint" size="3" value="0" tt="background level (0-100) on the seekbar$Nto make buffering less distracting" />' +
|
||||||
|
'</div></div>' +
|
||||||
|
|
||||||
'<div><h3>audio equalizer</h3><div id="audio_eq"></div></div>');
|
'<div><h3>audio equalizer</h3><div id="audio_eq"></div></div>');
|
||||||
|
|
||||||
var r = {
|
var r = {
|
||||||
"pb_mode": sread('pb_mode') || 'loop-folder',
|
"pb_mode": sread('pb_mode') || 'loop-folder',
|
||||||
"preload": bcfg_get('au_preload', true),
|
"preload": bcfg_get('au_preload', true),
|
||||||
"clip": bcfg_get('au_npclip', false),
|
"clip": bcfg_get('au_npclip', false),
|
||||||
"os_ctl": bcfg_get('au_os_ctl', false) && have_mctl,
|
"os_ctl": bcfg_get('au_os_ctl', have_mctl) && have_mctl,
|
||||||
"osd_cv": bcfg_get('au_osd_cv', true),
|
"osd_cv": bcfg_get('au_osd_cv', true),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -290,6 +301,26 @@ var mpl = (function () {
|
|||||||
draw_pb_mode();
|
draw_pb_mode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function set_tint() {
|
||||||
|
var tint = icfg_get('pb_tint', 0);
|
||||||
|
if (!tint)
|
||||||
|
ebi('barbuf').style.removeProperty('background');
|
||||||
|
else
|
||||||
|
ebi('barbuf').style.background = 'rgba(126,163,75,' + (tint / 100.0) + ')';
|
||||||
|
}
|
||||||
|
ebi('pb_tint').oninput = function (e) {
|
||||||
|
swrite('pb_tint', this.value);
|
||||||
|
set_tint();
|
||||||
|
};
|
||||||
|
set_tint();
|
||||||
|
|
||||||
|
r.pp = function () {
|
||||||
|
if (!r.os_ctl)
|
||||||
|
return;
|
||||||
|
|
||||||
|
navigator.mediaSession.playbackState = mp.au && !mp.au.paused ? "playing" : "paused";
|
||||||
|
};
|
||||||
|
|
||||||
r.announce = function () {
|
r.announce = function () {
|
||||||
if (!r.os_ctl)
|
if (!r.os_ctl)
|
||||||
return;
|
return;
|
||||||
@@ -330,13 +361,21 @@ var mpl = (function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
navigator.mediaSession.metadata = new MediaMetadata(tags);
|
navigator.mediaSession.metadata = new MediaMetadata(tags);
|
||||||
navigator.mediaSession.playbackState = mp.au.paused ? "paused" : "playing";
|
|
||||||
navigator.mediaSession.setActionHandler('play', playpause);
|
navigator.mediaSession.setActionHandler('play', playpause);
|
||||||
navigator.mediaSession.setActionHandler('pause', playpause);
|
navigator.mediaSession.setActionHandler('pause', playpause);
|
||||||
navigator.mediaSession.setActionHandler('seekbackward', function () { seek_au_rel(-10); });
|
navigator.mediaSession.setActionHandler('seekbackward', function () { seek_au_rel(-10); });
|
||||||
navigator.mediaSession.setActionHandler('seekforward', function () { seek_au_rel(10); });
|
navigator.mediaSession.setActionHandler('seekforward', function () { seek_au_rel(10); });
|
||||||
navigator.mediaSession.setActionHandler('previoustrack', prev_song);
|
navigator.mediaSession.setActionHandler('previoustrack', prev_song);
|
||||||
navigator.mediaSession.setActionHandler('nexttrack', next_song);
|
navigator.mediaSession.setActionHandler('nexttrack', next_song);
|
||||||
|
r.pp();
|
||||||
|
};
|
||||||
|
|
||||||
|
r.stop = function () {
|
||||||
|
if (!r.os_ctl || !navigator.mediaSession.metadata)
|
||||||
|
return;
|
||||||
|
|
||||||
|
navigator.mediaSession.metadata = null;
|
||||||
|
navigator.mediaSession.playbackState = "paused";
|
||||||
};
|
};
|
||||||
|
|
||||||
return r;
|
return r;
|
||||||
@@ -345,14 +384,15 @@ var mpl = (function () {
|
|||||||
|
|
||||||
// extract songs + add play column
|
// extract songs + add play column
|
||||||
function MPlayer() {
|
function MPlayer() {
|
||||||
this.id = Date.now();
|
var r = this;
|
||||||
this.au = null;
|
r.id = Date.now();
|
||||||
this.au_native = null;
|
r.au = null;
|
||||||
this.au_native2 = null;
|
r.au_native = null;
|
||||||
this.au_ogvjs = null;
|
r.au_native2 = null;
|
||||||
this.au_ogvjs2 = null;
|
r.au_ogvjs = null;
|
||||||
this.tracks = {};
|
r.au_ogvjs2 = null;
|
||||||
this.order = [];
|
r.tracks = {};
|
||||||
|
r.order = [];
|
||||||
|
|
||||||
var re_audio = /\.(opus|ogg|m4a|aac|mp3|wav|flac)$/i,
|
var re_audio = /\.(opus|ogg|m4a|aac|mp3|wav|flac)$/i,
|
||||||
trs = QSA('#files tbody tr');
|
trs = QSA('#files tbody tr');
|
||||||
@@ -367,32 +407,33 @@ function MPlayer() {
|
|||||||
|
|
||||||
if (m) {
|
if (m) {
|
||||||
var tid = link.getAttribute('id');
|
var tid = link.getAttribute('id');
|
||||||
this.order.push(tid);
|
r.order.push(tid);
|
||||||
this.tracks[tid] = url;
|
r.tracks[tid] = url;
|
||||||
tds[0].innerHTML = '<a id="a' + tid + '" href="#a' + tid + '" class="play">play</a></td>';
|
tds[0].innerHTML = '<a id="a' + tid + '" href="#a' + tid + '" class="play">play</a></td>';
|
||||||
ebi('a' + tid).onclick = ev_play;
|
ebi('a' + tid).onclick = ev_play;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.vol = sread('vol');
|
r.vol = sread('vol');
|
||||||
if (this.vol !== null)
|
if (r.vol !== null)
|
||||||
this.vol = parseFloat(this.vol);
|
r.vol = parseFloat(r.vol);
|
||||||
else
|
else
|
||||||
this.vol = 0.5;
|
r.vol = 0.5;
|
||||||
|
|
||||||
this.expvol = function () {
|
r.expvol = function (v) {
|
||||||
return 0.5 * this.vol + 0.5 * this.vol * this.vol;
|
return 0.5 * v + 0.5 * v * v;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.setvol = function (vol) {
|
r.setvol = function (vol) {
|
||||||
this.vol = Math.max(Math.min(vol, 1), 0);
|
r.vol = Math.max(Math.min(vol, 1), 0);
|
||||||
swrite('vol', vol);
|
swrite('vol', vol);
|
||||||
|
r.stopfade(true);
|
||||||
|
|
||||||
if (this.au)
|
if (r.au)
|
||||||
this.au.volume = this.expvol();
|
r.au.volume = r.expvol(r.vol);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.read_order = function () {
|
r.read_order = function () {
|
||||||
var order = [],
|
var order = [],
|
||||||
links = QSA('#files>tbody>tr>td:nth-child(1)>a');
|
links = QSA('#files>tbody>tr>td:nth-child(1)>a');
|
||||||
|
|
||||||
@@ -403,24 +444,71 @@ function MPlayer() {
|
|||||||
|
|
||||||
order.push(tid.slice(1));
|
order.push(tid.slice(1));
|
||||||
}
|
}
|
||||||
this.order = order;
|
r.order = order;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.preload = function (url) {
|
r.fdir = 0;
|
||||||
|
r.fvol = -1;
|
||||||
|
r.ftid = -1;
|
||||||
|
r.ftimer = null;
|
||||||
|
r.fade_in = function () {
|
||||||
|
r.fvol = 0;
|
||||||
|
r.fdir = 0.025;
|
||||||
|
if (r.au) {
|
||||||
|
r.ftid = r.au.tid;
|
||||||
|
r.au.play();
|
||||||
|
mpl.pp();
|
||||||
|
fader();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
r.fade_out = function () {
|
||||||
|
r.fvol = r.vol;
|
||||||
|
r.fdir = -0.05;
|
||||||
|
r.ftid = r.au.tid;
|
||||||
|
fader();
|
||||||
|
};
|
||||||
|
r.stopfade = function (hard) {
|
||||||
|
clearTimeout(r.ftimer);
|
||||||
|
if (hard)
|
||||||
|
r.ftid = -1;
|
||||||
|
}
|
||||||
|
function fader() {
|
||||||
|
r.stopfade();
|
||||||
|
if (!r.au || r.au.tid !== r.ftid)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var done = true;
|
||||||
|
r.fvol += r.fdir;
|
||||||
|
if (r.fvol < 0) {
|
||||||
|
r.fvol = 0;
|
||||||
|
r.au.pause();
|
||||||
|
mpl.pp();
|
||||||
|
}
|
||||||
|
else if (r.fvol > r.vol)
|
||||||
|
r.fvol = r.vol;
|
||||||
|
else
|
||||||
|
done = false;
|
||||||
|
|
||||||
|
r.au.volume = r.expvol(r.fvol);
|
||||||
|
if (!done)
|
||||||
|
setTimeout(fader, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
r.preload = function (url) {
|
||||||
var au = null;
|
var au = null;
|
||||||
if (need_ogv_for(url)) {
|
if (need_ogv_for(url)) {
|
||||||
au = mp.au_ogvjs2;
|
au = mp.au_ogvjs2;
|
||||||
if (!au && window['OGVPlayer']) {
|
if (!au && window['OGVPlayer']) {
|
||||||
au = new OGVPlayer();
|
au = new OGVPlayer();
|
||||||
au.preload = "auto";
|
au.preload = "auto";
|
||||||
this.au_ogvjs2 = au;
|
r.au_ogvjs2 = au;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
au = mp.au_native2;
|
au = mp.au_native2;
|
||||||
if (!au) {
|
if (!au) {
|
||||||
au = new Audio();
|
au = new Audio();
|
||||||
au.preload = "auto";
|
au.preload = "auto";
|
||||||
this.au_native2 = au;
|
r.au_native2 = au;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (au) {
|
if (au) {
|
||||||
@@ -458,37 +546,38 @@ function get_np() {
|
|||||||
|
|
||||||
// toggle player widget
|
// toggle player widget
|
||||||
var widget = (function () {
|
var widget = (function () {
|
||||||
var ret = {},
|
var r = {},
|
||||||
widget = ebi('widget'),
|
widget = ebi('widget'),
|
||||||
wtico = ebi('wtico'),
|
wtico = ebi('wtico'),
|
||||||
nptxt = ebi('nptxt'),
|
nptxt = ebi('nptxt'),
|
||||||
npirc = ebi('npirc'),
|
npirc = ebi('npirc'),
|
||||||
touchmode = false,
|
touchmode = false,
|
||||||
side_open = false,
|
|
||||||
was_paused = true;
|
was_paused = true;
|
||||||
|
|
||||||
ret.open = function () {
|
r.is_open = false;
|
||||||
if (side_open)
|
|
||||||
|
r.open = function () {
|
||||||
|
if (r.is_open)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
widget.className = 'open';
|
widget.className = 'open';
|
||||||
side_open = true;
|
r.is_open = true;
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
ret.close = function () {
|
r.close = function () {
|
||||||
if (!side_open)
|
if (!r.is_open)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
widget.className = '';
|
widget.className = '';
|
||||||
side_open = false;
|
r.is_open = false;
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
ret.toggle = function (e) {
|
r.toggle = function (e) {
|
||||||
ret.open() || ret.close();
|
r.open() || r.close();
|
||||||
ev(e);
|
ev(e);
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
ret.paused = function (paused) {
|
r.paused = function (paused) {
|
||||||
if (was_paused != paused) {
|
if (was_paused != paused) {
|
||||||
was_paused = paused;
|
was_paused = paused;
|
||||||
ebi('bplay').innerHTML = paused ? '▶' : '⏸';
|
ebi('bplay').innerHTML = paused ? '▶' : '⏸';
|
||||||
@@ -496,7 +585,7 @@ var widget = (function () {
|
|||||||
};
|
};
|
||||||
wtico.onclick = function (e) {
|
wtico.onclick = function (e) {
|
||||||
if (!touchmode)
|
if (!touchmode)
|
||||||
ret.toggle(e);
|
r.toggle(e);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
@@ -527,7 +616,7 @@ var widget = (function () {
|
|||||||
document.body.removeChild(o);
|
document.body.removeChild(o);
|
||||||
}, 500);
|
}, 500);
|
||||||
};
|
};
|
||||||
return ret;
|
return r;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
||||||
@@ -573,12 +662,15 @@ var pbar = (function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
r.drawbuf = function () {
|
r.drawbuf = function () {
|
||||||
|
var bc = r.buf,
|
||||||
|
bctx = bc.ctx;
|
||||||
|
|
||||||
|
bctx.clearRect(0, 0, bc.w, bc.h);
|
||||||
|
|
||||||
if (!mp.au)
|
if (!mp.au)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var bc = r.buf,
|
var sm = bc.w * 1.0 / mp.au.duration,
|
||||||
bctx = bc.ctx,
|
|
||||||
sm = bc.w * 1.0 / mp.au.duration,
|
|
||||||
gk = bc.h + '' + light;
|
gk = bc.h + '' + light;
|
||||||
|
|
||||||
if (gradh != gk) {
|
if (gradh != gk) {
|
||||||
@@ -586,7 +678,6 @@ var pbar = (function () {
|
|||||||
grad = glossy_grad(bc, 85, [35, 40, 37, 35], light ? [45, 56, 50, 45] : [42, 51, 47, 42]);
|
grad = glossy_grad(bc, 85, [35, 40, 37, 35], light ? [45, 56, 50, 45] : [42, 51, 47, 42]);
|
||||||
}
|
}
|
||||||
bctx.fillStyle = grad;
|
bctx.fillStyle = grad;
|
||||||
bctx.clearRect(0, 0, bc.w, bc.h);
|
|
||||||
for (var a = 0; a < mp.au.buffered.length; a++) {
|
for (var a = 0; a < mp.au.buffered.length; a++) {
|
||||||
var x1 = sm * mp.au.buffered.start(a),
|
var x1 = sm * mp.au.buffered.start(a),
|
||||||
x2 = sm * mp.au.buffered.end(a);
|
x2 = sm * mp.au.buffered.end(a);
|
||||||
@@ -596,15 +687,17 @@ var pbar = (function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
r.drawpos = function () {
|
r.drawpos = function () {
|
||||||
|
var bc = r.buf,
|
||||||
|
pc = r.pos,
|
||||||
|
pctx = pc.ctx;
|
||||||
|
|
||||||
|
pctx.clearRect(0, 0, pc.w, pc.h);
|
||||||
|
|
||||||
if (!mp.au || isNaN(mp.au.duration) || isNaN(mp.au.currentTime))
|
if (!mp.au || isNaN(mp.au.duration) || isNaN(mp.au.currentTime))
|
||||||
return; // not-init || unsupp-codec
|
return; // not-init || unsupp-codec
|
||||||
|
|
||||||
var bc = r.buf,
|
var sm = bc.w * 1.0 / mp.au.duration;
|
||||||
pc = r.pos,
|
|
||||||
pctx = pc.ctx,
|
|
||||||
sm = bc.w * 1.0 / mp.au.duration;
|
|
||||||
|
|
||||||
pctx.clearRect(0, 0, pc.w, pc.h);
|
|
||||||
pctx.fillStyle = light ? 'rgba(0,64,0,0.15)' : 'rgba(204,255,128,0.15)';
|
pctx.fillStyle = light ? 'rgba(0,64,0,0.15)' : 'rgba(204,255,128,0.15)';
|
||||||
for (var p = 1, mins = mp.au.duration / 10; p <= mins; p++)
|
for (var p = 1, mins = mp.au.duration / 10; p <= mins; p++)
|
||||||
pctx.fillRect(Math.floor(sm * p * 10), 0, 2, pc.h);
|
pctx.fillRect(Math.floor(sm * p * 10), 0, 2, pc.h);
|
||||||
@@ -735,9 +828,8 @@ function seek_au_sec(seek) {
|
|||||||
|
|
||||||
mp.au.currentTime = seek;
|
mp.au.currentTime = seek;
|
||||||
|
|
||||||
// ogv.js breaks on .play() during playback
|
if (mp.au.paused)
|
||||||
if (mp.au === mp.au_native)
|
mp.fade_in();
|
||||||
mp.au.play();
|
|
||||||
|
|
||||||
mpui.progress_updater();
|
mpui.progress_updater();
|
||||||
}
|
}
|
||||||
@@ -759,25 +851,29 @@ function next_song(e) {
|
|||||||
}
|
}
|
||||||
function prev_song(e) {
|
function prev_song(e) {
|
||||||
ev(e);
|
ev(e);
|
||||||
|
|
||||||
|
if (mp.au && !mp.au.paused && mp.au.currentTime > 3)
|
||||||
|
return seek_au_sec(0);
|
||||||
|
|
||||||
return song_skip(-1);
|
return song_skip(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function playpause(e) {
|
function playpause(e) {
|
||||||
|
// must be event-chain
|
||||||
ev(e);
|
ev(e);
|
||||||
if (mp.au) {
|
if (mp.au) {
|
||||||
if (mp.au.paused)
|
if (mp.au.paused)
|
||||||
mp.au.play();
|
mp.fade_in();
|
||||||
else
|
else
|
||||||
mp.au.pause();
|
mp.fade_out();
|
||||||
|
|
||||||
mpui.progress_updater();
|
mpui.progress_updater();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
play(0);
|
play(0, true);
|
||||||
|
|
||||||
if (navigator.mediaSession)
|
mpl.pp();
|
||||||
navigator.mediaSession.playbackState = mp.au.paused ? "paused" : "playing";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -786,9 +882,13 @@ function playpause(e) {
|
|||||||
ebi('bplay').onclick = playpause;
|
ebi('bplay').onclick = playpause;
|
||||||
ebi('bprev').onclick = prev_song;
|
ebi('bprev').onclick = prev_song;
|
||||||
ebi('bnext').onclick = next_song;
|
ebi('bnext').onclick = next_song;
|
||||||
ebi('barpos').onclick = function (e) {
|
|
||||||
|
var bar = ebi('barpos');
|
||||||
|
|
||||||
|
bar.onclick = function (e) {
|
||||||
if (!mp.au) {
|
if (!mp.au) {
|
||||||
return play(0);
|
play(0, true);
|
||||||
|
return mp.fade_in();
|
||||||
}
|
}
|
||||||
|
|
||||||
var rect = pbar.buf.can.getBoundingClientRect(),
|
var rect = pbar.buf.can.getBoundingClientRect(),
|
||||||
@@ -796,6 +896,19 @@ function playpause(e) {
|
|||||||
|
|
||||||
seek_au_mul(x * 1.0 / rect.width);
|
seek_au_mul(x * 1.0 / rect.width);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!is_touch)
|
||||||
|
bar.onwheel = function (e) {
|
||||||
|
var dist = Math.sign(e.deltaY) * 10;
|
||||||
|
if (Math.abs(e.deltaY) < 30 && !e.deltaMode)
|
||||||
|
dist = e.deltaY;
|
||||||
|
|
||||||
|
if (!dist || !mp.au)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
seek_au_rel(dist);
|
||||||
|
ev(e);
|
||||||
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
||||||
@@ -863,7 +976,12 @@ var mpui = (function () {
|
|||||||
// event from play button next to a file in the list
|
// event from play button next to a file in the list
|
||||||
function ev_play(e) {
|
function ev_play(e) {
|
||||||
ev(e);
|
ev(e);
|
||||||
play(this.getAttribute('id').slice(1));
|
|
||||||
|
var fade = !mp.au || mp.au.paused;
|
||||||
|
play(this.getAttribute('id').slice(1), true);
|
||||||
|
if (fade)
|
||||||
|
mp.fade_in();
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1102,13 +1220,18 @@ var audio_eq = (function () {
|
|||||||
|
|
||||||
|
|
||||||
// plays the tid'th audio file on the page
|
// plays the tid'th audio file on the page
|
||||||
function play(tid, seek, call_depth) {
|
function play(tid, is_ev, seek, call_depth) {
|
||||||
if (mp.order.length == 0)
|
if (mp.order.length == 0)
|
||||||
return console.log('no audio found wait what');
|
return console.log('no audio found wait what');
|
||||||
|
|
||||||
|
mp.stopfade(true);
|
||||||
|
|
||||||
var tn = tid;
|
var tn = tid;
|
||||||
if ((tn + '').indexOf('f-') === 0)
|
if ((tn + '').indexOf('f-') === 0) {
|
||||||
tn = mp.order.indexOf(tn);
|
tn = mp.order.indexOf(tn);
|
||||||
|
if (tn < 0)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (tn >= mp.order.length) {
|
if (tn >= mp.order.length) {
|
||||||
if (mpl.pb_mode == 'loop-folder') {
|
if (mpl.pb_mode == 'loop-folder') {
|
||||||
@@ -1147,7 +1270,7 @@ function play(tid, seek, call_depth) {
|
|||||||
}
|
}
|
||||||
else if (window['OGVPlayer']) {
|
else if (window['OGVPlayer']) {
|
||||||
mp.au = mp.au_ogvjs = new OGVPlayer();
|
mp.au = mp.au_ogvjs = new OGVPlayer();
|
||||||
attempt_play = false;
|
attempt_play = is_ev;
|
||||||
mp.au.addEventListener('error', evau_error, true);
|
mp.au.addEventListener('error', evau_error, true);
|
||||||
mp.au.addEventListener('progress', pbar.drawpos);
|
mp.au.addEventListener('progress', pbar.drawpos);
|
||||||
mp.au.addEventListener('ended', next_song);
|
mp.au.addEventListener('ended', next_song);
|
||||||
@@ -1160,7 +1283,7 @@ function play(tid, seek, call_depth) {
|
|||||||
show_modal('<h1>loading ogv.js</h1><h2>thanks apple</h2>');
|
show_modal('<h1>loading ogv.js</h1><h2>thanks apple</h2>');
|
||||||
|
|
||||||
import_js('/.cpr/deps/ogv.js', function () {
|
import_js('/.cpr/deps/ogv.js', function () {
|
||||||
play(tid, seek, 1);
|
play(tid, false, seek, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -1181,7 +1304,7 @@ function play(tid, seek, call_depth) {
|
|||||||
|
|
||||||
mp.au.tid = tid;
|
mp.au.tid = tid;
|
||||||
mp.au.src = url + (url.indexOf('?') < 0 ? '?cache' : '&cache');
|
mp.au.src = url + (url.indexOf('?') < 0 ? '?cache' : '&cache');
|
||||||
mp.au.volume = mp.expvol();
|
mp.au.volume = mp.expvol(mp.vol);
|
||||||
var oid = 'a' + tid;
|
var oid = 'a' + tid;
|
||||||
setclass(oid, 'play act');
|
setclass(oid, 'play act');
|
||||||
var trs = ebi('files').getElementsByTagName('tbody')[0].getElementsByTagName('tr');
|
var trs = ebi('files').getElementsByTagName('tbody')[0].getElementsByTagName('tr');
|
||||||
@@ -1272,7 +1395,8 @@ function show_modal(html) {
|
|||||||
|
|
||||||
|
|
||||||
// hide fullscreen message
|
// hide fullscreen message
|
||||||
function unblocked() {
|
function unblocked(e) {
|
||||||
|
ev(e);
|
||||||
var dom = ebi('blocked');
|
var dom = ebi('blocked');
|
||||||
if (dom)
|
if (dom)
|
||||||
dom.parentNode.removeChild(dom);
|
dom.parentNode.removeChild(dom);
|
||||||
@@ -1287,28 +1411,25 @@ function autoplay_blocked(seek) {
|
|||||||
|
|
||||||
var go = ebi('blk_go'),
|
var go = ebi('blk_go'),
|
||||||
na = ebi('blk_na'),
|
na = ebi('blk_na'),
|
||||||
fn = mp.tracks[mp.au.tid].split(/\//).pop();
|
tid = mp.au.tid,
|
||||||
|
fn = mp.tracks[tid].split(/\//).pop();
|
||||||
|
|
||||||
fn = uricom_dec(fn.replace(/\+/g, ' '))[0];
|
fn = uricom_dec(fn.replace(/\+/g, ' '))[0];
|
||||||
|
|
||||||
go.textContent = 'Play "' + fn + '"';
|
go.textContent = 'Play "' + fn + '"';
|
||||||
go.onclick = function (e) {
|
go.onclick = function (e) {
|
||||||
if (e) e.preventDefault();
|
unblocked(e);
|
||||||
unblocked();
|
// chrome 91 may permanently taint on a failed play()
|
||||||
mp.au.play();
|
// depending on win10 settings or something? idk
|
||||||
if (seek)
|
mp.au_native = mp.au_ogvjs = null;
|
||||||
seek_au_sec(seek);
|
play(tid, true, seek);
|
||||||
else
|
mp.fade_in();
|
||||||
mpui.progress_updater();
|
|
||||||
|
|
||||||
mpl.announce();
|
|
||||||
};
|
};
|
||||||
na.onclick = unblocked;
|
na.onclick = unblocked;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// autoplay linked track
|
function play_linked() {
|
||||||
(function () {
|
|
||||||
var v = location.hash;
|
var v = location.hash;
|
||||||
if (v && v.indexOf('#af-') === 0) {
|
if (v && v.indexOf('#af-') === 0) {
|
||||||
var id = v.slice(2).split('&');
|
var id = v.slice(2).split('&');
|
||||||
@@ -1322,9 +1443,9 @@ function autoplay_blocked(seek) {
|
|||||||
if (!m)
|
if (!m)
|
||||||
return play(id[0]);
|
return play(id[0]);
|
||||||
|
|
||||||
return play(id[0], parseInt(m[1] || 0) * 60 + parseInt(m[2] || 0));
|
return play(id[0], false, parseInt(m[1] || 0) * 60 + parseInt(m[2] || 0));
|
||||||
}
|
}
|
||||||
})();
|
};
|
||||||
|
|
||||||
|
|
||||||
var thegrid = (function () {
|
var thegrid = (function () {
|
||||||
@@ -1430,35 +1551,57 @@ var thegrid = (function () {
|
|||||||
}
|
}
|
||||||
setsz();
|
setsz();
|
||||||
|
|
||||||
function seltgl(e) {
|
function gclick(e) {
|
||||||
if (e && e.ctrlKey)
|
if (e && (e.ctrlKey || e.metaKey))
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
ev(e);
|
|
||||||
var oth = ebi(this.getAttribute('ref')),
|
var oth = ebi(this.getAttribute('ref')),
|
||||||
td = oth.parentNode.nextSibling,
|
href = this.getAttribute('href'),
|
||||||
|
aplay = ebi('a' + oth.getAttribute('id')),
|
||||||
|
is_img = /\.(gif|jpe?g|png|webp)(\?|$)/i.test(href),
|
||||||
|
in_tree = null,
|
||||||
|
have_sel = QS('#files tr.sel'),
|
||||||
|
td = oth.closest('td').nextSibling,
|
||||||
tr = td.parentNode;
|
tr = td.parentNode;
|
||||||
|
|
||||||
|
if (/\/(\?|$)/.test(href)) {
|
||||||
|
var ta = QSA('#treeul a.hl+ul>li>a+a'),
|
||||||
|
txt = oth.textContent.slice(0, -1);
|
||||||
|
|
||||||
|
for (var a = 0, aa = ta.length; a < aa; a++) {
|
||||||
|
if (ta[a].textContent == txt) {
|
||||||
|
in_tree = ta[a];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r.sel) {
|
||||||
td.click();
|
td.click();
|
||||||
this.setAttribute('class', tr.getAttribute('class'));
|
this.setAttribute('class', tr.getAttribute('class'));
|
||||||
}
|
}
|
||||||
|
else if (widget.is_open && aplay)
|
||||||
|
aplay.click();
|
||||||
|
|
||||||
function bgopen(e) {
|
else if (in_tree && !have_sel)
|
||||||
|
in_tree.click();
|
||||||
|
|
||||||
|
else if (!is_img && have_sel)
|
||||||
|
window.open(href, '_blank');
|
||||||
|
|
||||||
|
else return true;
|
||||||
ev(e);
|
ev(e);
|
||||||
var url = this.getAttribute('href');
|
|
||||||
window.open(url, '_blank');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
r.loadsel = function () {
|
r.loadsel = function () {
|
||||||
if (r.dirty)
|
if (r.dirty)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var ths = QSA('#ggrid>a'),
|
var ths = QSA('#ggrid>a');
|
||||||
have_sel = !!QS('#files tr.sel');
|
|
||||||
|
|
||||||
for (var a = 0, aa = ths.length; a < aa; a++) {
|
for (var a = 0, aa = ths.length; a < aa; a++) {
|
||||||
ths[a].onclick = r.sel ? seltgl : have_sel ? bgopen : null;
|
var tr = ebi(ths[a].getAttribute('ref')).closest('tr');
|
||||||
ths[a].setAttribute('class', ebi(ths[a].getAttribute('ref')).parentNode.parentNode.getAttribute('class'));
|
ths[a].setAttribute('class', tr.getAttribute('class'));
|
||||||
}
|
}
|
||||||
var uns = QS('#ggrid a[ref="unsearch"]');
|
var uns = QS('#ggrid a[ref="unsearch"]');
|
||||||
if (uns)
|
if (uns)
|
||||||
@@ -1489,6 +1632,8 @@ var thegrid = (function () {
|
|||||||
|
|
||||||
if (r.thumbs) {
|
if (r.thumbs) {
|
||||||
ihref += (ihref.indexOf('?') === -1 ? '?' : '&') + 'th=' + (have_webp ? 'w' : 'j');
|
ihref += (ihref.indexOf('?') === -1 ? '?' : '&') + 'th=' + (have_webp ? 'w' : 'j');
|
||||||
|
if (href == "#")
|
||||||
|
ihref = '/.cpr/ico/⏏️';
|
||||||
}
|
}
|
||||||
else if (isdir) {
|
else if (isdir) {
|
||||||
ihref = '/.cpr/ico/folder';
|
ihref = '/.cpr/ico/folder';
|
||||||
@@ -1516,6 +1661,11 @@ var thegrid = (function () {
|
|||||||
ihref + '" /><span' + ac + '>' + ao.innerHTML + '</span></a>');
|
ihref + '" /><span' + ac + '>' + ao.innerHTML + '</span></a>');
|
||||||
}
|
}
|
||||||
ebi('ggrid').innerHTML = html.join('\n');
|
ebi('ggrid').innerHTML = html.join('\n');
|
||||||
|
|
||||||
|
var ths = QSA('#ggrid>a');
|
||||||
|
for (var a = 0, aa = ths.length; a < aa; a++)
|
||||||
|
ths[a].onclick = gclick;
|
||||||
|
|
||||||
r.dirty = false;
|
r.dirty = false;
|
||||||
r.bagit();
|
r.bagit();
|
||||||
r.loadsel();
|
r.loadsel();
|
||||||
@@ -1603,19 +1753,25 @@ document.onkeydown = function (e) {
|
|||||||
if (!document.activeElement || document.activeElement != document.body && document.activeElement.nodeName.toLowerCase() != 'a')
|
if (!document.activeElement || document.activeElement != document.body && document.activeElement.nodeName.toLowerCase() != 'a')
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (e.ctrlKey || e.altKey || e.shiftKey || e.metaKey || e.isComposing)
|
if (e.ctrlKey || e.altKey || e.metaKey || e.isComposing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var k = e.code + '', pos = -1, n;
|
||||||
|
|
||||||
|
if (e.shiftKey && k != 'KeyA' && k != 'KeyD')
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var k = (e.code + ''), pos = -1;
|
|
||||||
if (k.indexOf('Digit') === 0)
|
if (k.indexOf('Digit') === 0)
|
||||||
pos = parseInt(k.slice(-1)) * 0.1;
|
pos = parseInt(k.slice(-1)) * 0.1;
|
||||||
|
|
||||||
if (pos !== -1)
|
if (pos !== -1)
|
||||||
return seek_au_mul(pos) || true;
|
return seek_au_mul(pos) || true;
|
||||||
|
|
||||||
var n = k == 'KeyJ' ? -1 : k == 'KeyL' ? 1 : 0;
|
if (k == 'KeyJ')
|
||||||
if (n !== 0)
|
return prev_song() || true;
|
||||||
return song_skip(n) || true;
|
|
||||||
|
if (k == 'KeyL')
|
||||||
|
return next_song() || true;
|
||||||
|
|
||||||
if (k == 'KeyP')
|
if (k == 'KeyP')
|
||||||
return playpause() || true;
|
return playpause() || true;
|
||||||
@@ -1640,6 +1796,14 @@ document.onkeydown = function (e) {
|
|||||||
if (k == 'KeyT')
|
if (k == 'KeyT')
|
||||||
return ebi('thumbs').click();
|
return ebi('thumbs').click();
|
||||||
|
|
||||||
|
if (!treectl.hidden && (!e.shiftKey || !thegrid.en)) {
|
||||||
|
if (k == 'KeyA')
|
||||||
|
return QS('#twig').click();
|
||||||
|
|
||||||
|
if (k == 'KeyD')
|
||||||
|
return QS('#twobytwo').click();
|
||||||
|
}
|
||||||
|
|
||||||
if (thegrid.en) {
|
if (thegrid.en) {
|
||||||
if (k == 'KeyS')
|
if (k == 'KeyS')
|
||||||
return ebi('gridsel').click();
|
return ebi('gridsel').click();
|
||||||
@@ -1722,6 +1886,7 @@ document.onkeydown = function (e) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var search_timeout,
|
var search_timeout,
|
||||||
|
defer_timeout,
|
||||||
search_in_progress = 0;
|
search_in_progress = 0;
|
||||||
|
|
||||||
function ev_search_input() {
|
function ev_search_input() {
|
||||||
@@ -1736,10 +1901,30 @@ document.onkeydown = function (e) {
|
|||||||
if (id != "q_raw")
|
if (id != "q_raw")
|
||||||
encode_query();
|
encode_query();
|
||||||
|
|
||||||
|
set_vq();
|
||||||
|
|
||||||
|
clearTimeout(defer_timeout);
|
||||||
|
defer_timeout = setTimeout(try_search, 2000);
|
||||||
|
try_search();
|
||||||
|
}
|
||||||
|
|
||||||
|
function try_search() {
|
||||||
|
if (Date.now() - search_in_progress > 30 * 1000) {
|
||||||
|
clearTimeout(defer_timeout);
|
||||||
clearTimeout(search_timeout);
|
clearTimeout(search_timeout);
|
||||||
if (Date.now() - search_in_progress > 30 * 1000)
|
|
||||||
search_timeout = setTimeout(do_search, 200);
|
search_timeout = setTimeout(do_search, 200);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function set_vq() {
|
||||||
|
if (search_in_progress)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var q = ebi('q_raw').value,
|
||||||
|
vq = ebi('files').getAttribute('q_raw');
|
||||||
|
|
||||||
|
srch_msg(false, (q == vq) ? '' : 'search results below are from a previous query:\n ' + (vq ? vq : '(*)'));
|
||||||
|
}
|
||||||
|
|
||||||
function encode_query() {
|
function encode_query() {
|
||||||
var q = '';
|
var q = '';
|
||||||
@@ -1808,7 +1993,8 @@ document.onkeydown = function (e) {
|
|||||||
xhr.setRequestHeader('Content-Type', 'text/plain');
|
xhr.setRequestHeader('Content-Type', 'text/plain');
|
||||||
xhr.onreadystatechange = xhr_search_results;
|
xhr.onreadystatechange = xhr_search_results;
|
||||||
xhr.ts = Date.now();
|
xhr.ts = Date.now();
|
||||||
xhr.send(JSON.stringify({ "q": ebi('q_raw').value }));
|
xhr.q_raw = ebi('q_raw').value;
|
||||||
|
xhr.send(JSON.stringify({ "q": xhr.q_raw }));
|
||||||
}
|
}
|
||||||
|
|
||||||
function xhr_search_results() {
|
function xhr_search_results() {
|
||||||
@@ -1879,6 +2065,8 @@ document.onkeydown = function (e) {
|
|||||||
|
|
||||||
ofiles.innerHTML = html.join('\n');
|
ofiles.innerHTML = html.join('\n');
|
||||||
ofiles.setAttribute("ts", this.ts);
|
ofiles.setAttribute("ts", this.ts);
|
||||||
|
ofiles.setAttribute("q_raw", this.q_raw);
|
||||||
|
set_vq();
|
||||||
mukey.render();
|
mukey.render();
|
||||||
reload_browser();
|
reload_browser();
|
||||||
filecols.set_style(['File Name']);
|
filecols.set_style(['File Name']);
|
||||||
@@ -1890,6 +2078,7 @@ document.onkeydown = function (e) {
|
|||||||
ev(e);
|
ev(e);
|
||||||
treectl.show();
|
treectl.show();
|
||||||
ebi('files').innerHTML = orig_html;
|
ebi('files').innerHTML = orig_html;
|
||||||
|
ebi('files').removeAttribute('q_raw');
|
||||||
orig_html = null;
|
orig_html = null;
|
||||||
msel.render();
|
msel.render();
|
||||||
reload_browser();
|
reload_browser();
|
||||||
@@ -2108,6 +2297,9 @@ var treectl = (function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function treego(e) {
|
function treego(e) {
|
||||||
|
if (e && (e.ctrlKey || e.metaKey))
|
||||||
|
return true;
|
||||||
|
|
||||||
ev(e);
|
ev(e);
|
||||||
if (this.getAttribute('class') == 'hl' &&
|
if (this.getAttribute('class') == 'hl' &&
|
||||||
this.previousSibling.textContent == '-') {
|
this.previousSibling.textContent == '-') {
|
||||||
@@ -2838,8 +3030,10 @@ function reload_mp() {
|
|||||||
mp.au.pause();
|
mp.au.pause();
|
||||||
mp.au = null;
|
mp.au = null;
|
||||||
}
|
}
|
||||||
|
mpl.stop();
|
||||||
widget.close();
|
widget.close();
|
||||||
mp = new MPlayer();
|
mp = new MPlayer();
|
||||||
|
setTimeout(pbar.onresize, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -2883,3 +3077,4 @@ function reload_browser(not_mp) {
|
|||||||
reload_browser(true);
|
reload_browser(true);
|
||||||
mukey.render();
|
mukey.render();
|
||||||
msel.render();
|
msel.render();
|
||||||
|
play_linked();
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
<div>{{ logues[1] }}</div><br />
|
<div>{{ logues[1] }}</div><br />
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
<h2><a href="{{ url_suf }}{{ url_suf and '&' or '?' }}h">control-panel</a></h2>
|
<h2><a href="/{{ url_suf }}{{ url_suf and '&' or '?' }}h">control-panel</a></h2>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
<title>📝🎉 {{ title }}</title> <!-- 📜 -->
|
<title>📝🎉 {{ title }}</title> <!-- 📜 -->
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=0.7">
|
<meta name="viewport" content="width=device-width, initial-scale=0.7">
|
||||||
<link href="/.cpr/md.css" rel="stylesheet">
|
<link href="/.cpr/md.css?_={{ ts }}" rel="stylesheet">
|
||||||
{%- if edit %}
|
{%- if edit %}
|
||||||
<link href="/.cpr/md2.css" rel="stylesheet">
|
<link href="/.cpr/md2.css?_={{ ts }}" rel="stylesheet">
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -146,10 +146,10 @@ var md_opt = {
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<script src="/.cpr/util.js"></script>
|
<script src="/.cpr/util.js?_={{ ts }}"></script>
|
||||||
<script src="/.cpr/deps/marked.js"></script>
|
<script src="/.cpr/deps/marked.js?_={{ ts }}"></script>
|
||||||
<script src="/.cpr/md.js"></script>
|
<script src="/.cpr/md.js?_={{ ts }}"></script>
|
||||||
{%- if edit %}
|
{%- if edit %}
|
||||||
<script src="/.cpr/md2.js"></script>
|
<script src="/.cpr/md2.js?_={{ ts }}"></script>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
</body></html>
|
</body></html>
|
||||||
|
|||||||
@@ -3,9 +3,9 @@
|
|||||||
<title>📝🎉 {{ title }}</title>
|
<title>📝🎉 {{ title }}</title>
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=0.7">
|
<meta name="viewport" content="width=device-width, initial-scale=0.7">
|
||||||
<link href="/.cpr/mde.css" rel="stylesheet">
|
<link href="/.cpr/mde.css?_={{ ts }}" rel="stylesheet">
|
||||||
<link href="/.cpr/deps/mini-fa.css" rel="stylesheet">
|
<link href="/.cpr/deps/mini-fa.css?_={{ ts }}" rel="stylesheet">
|
||||||
<link href="/.cpr/deps/easymde.css" rel="stylesheet">
|
<link href="/.cpr/deps/easymde.css?_={{ ts }}" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="mw">
|
<div id="mw">
|
||||||
@@ -43,7 +43,7 @@ var lightswitch = (function () {
|
|||||||
})();
|
})();
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
<script src="/.cpr/util.js"></script>
|
<script src="/.cpr/util.js?_={{ ts }}"></script>
|
||||||
<script src="/.cpr/deps/easymde.js"></script>
|
<script src="/.cpr/deps/easymde.js?_={{ ts }}"></script>
|
||||||
<script src="/.cpr/mde.js"></script>
|
<script src="/.cpr/mde.js?_={{ ts }}"></script>
|
||||||
</body></html>
|
</body></html>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<title>copyparty</title>
|
<title>copyparty</title>
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
||||||
<link rel="stylesheet" type="text/css" media="screen" href="/.cpr/msg.css">
|
<link rel="stylesheet" type="text/css" media="screen" href="/.cpr/msg.css?_={{ ts }}">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<title>copyparty</title>
|
<title>copyparty</title>
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
<meta name="viewport" content="width=device-width, initial-scale=0.8">
|
||||||
<link rel="stylesheet" type="text/css" media="screen" href="/.cpr/splash.css">
|
<link rel="stylesheet" type="text/css" media="screen" href="/.cpr/splash.css?_={{ ts }}">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
</table>
|
</table>
|
||||||
</td></tr></table>
|
</td></tr></table>
|
||||||
<div class="btns">
|
<div class="btns">
|
||||||
<a href="{{ avol[0] }}?stack">dump stack</a>
|
<a href="/?stack">dump stack</a>
|
||||||
</div>
|
</div>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
window.onerror = vis_exh;
|
|
||||||
|
|
||||||
|
|
||||||
function goto_up2k() {
|
function goto_up2k() {
|
||||||
if (up2k === false)
|
if (up2k === false)
|
||||||
@@ -16,17 +14,19 @@ function goto_up2k() {
|
|||||||
|
|
||||||
// chrome requires https to use crypto.subtle,
|
// chrome requires https to use crypto.subtle,
|
||||||
// usually it's undefined but some chromes throw on invoke
|
// usually it's undefined but some chromes throw on invoke
|
||||||
var up2k = null;
|
var up2k = null,
|
||||||
var sha_js = window.WebAssembly ? 'hw' : 'ac'; // ff53,c57,sa11
|
sha_js = window.WebAssembly ? 'hw' : 'ac', // ff53,c57,sa11
|
||||||
|
m = 'will use ' + sha_js + ' instead of native sha512 due to';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var cf = crypto.subtle || crypto.webkitSubtle;
|
var cf = crypto.subtle || crypto.webkitSubtle;
|
||||||
cf.digest('SHA-512', new Uint8Array(1)).then(
|
cf.digest('SHA-512', new Uint8Array(1)).then(
|
||||||
function (x) { console.log('sha-ok'); up2k = up2k_init(cf); },
|
function (x) { console.log('sha-ok'); up2k = up2k_init(cf); },
|
||||||
function (x) { console.log('sha-ng:', x); up2k = up2k_init(false); }
|
function (x) { console.log(m, x); up2k = up2k_init(false); }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
catch (ex) {
|
catch (ex) {
|
||||||
console.log('sha-na:', ex);
|
console.log(m, ex);
|
||||||
try {
|
try {
|
||||||
up2k = up2k_init(false);
|
up2k = up2k_init(false);
|
||||||
}
|
}
|
||||||
@@ -142,7 +142,7 @@ function U2pvis(act, btns) {
|
|||||||
this.tail = -1;
|
this.tail = -1;
|
||||||
this.wsz = 3;
|
this.wsz = 3;
|
||||||
|
|
||||||
this.addfile = function (entry, sz) {
|
this.addfile = function (entry, sz, draw) {
|
||||||
this.tab.push({
|
this.tab.push({
|
||||||
"hn": entry[0],
|
"hn": entry[0],
|
||||||
"ht": entry[1],
|
"ht": entry[1],
|
||||||
@@ -156,6 +156,9 @@ function U2pvis(act, btns) {
|
|||||||
"bd0": 0 // upload start
|
"bd0": 0 // upload start
|
||||||
});
|
});
|
||||||
this.ctr["q"]++;
|
this.ctr["q"]++;
|
||||||
|
if (!draw)
|
||||||
|
return;
|
||||||
|
|
||||||
this.drawcard("q");
|
this.drawcard("q");
|
||||||
if (this.act == "q") {
|
if (this.act == "q") {
|
||||||
this.addrow(this.tab.length - 1);
|
this.addrow(this.tab.length - 1);
|
||||||
@@ -222,7 +225,7 @@ function U2pvis(act, btns) {
|
|||||||
this.hashed = function (fobj) {
|
this.hashed = function (fobj) {
|
||||||
var fo = this.tab[fobj.n],
|
var fo = this.tab[fobj.n],
|
||||||
nb = fo.bt * (++fo.nh / fo.cb.length),
|
nb = fo.bt * (++fo.nh / fo.cb.length),
|
||||||
p = this.perc(nb, 0, fobj.size, fobj.t1);
|
p = this.perc(nb, 0, fobj.size, fobj.t_hashing);
|
||||||
|
|
||||||
fo.hp = '{0}%, {1}, {2} MB/s'.format(
|
fo.hp = '{0}%, {1}, {2} MB/s'.format(
|
||||||
p[0].toFixed(2), p[1], p[2].toFixed(2)
|
p[0].toFixed(2), p[1], p[2].toFixed(2)
|
||||||
@@ -245,7 +248,7 @@ function U2pvis(act, btns) {
|
|||||||
fo.cb[nchunk] = cbd;
|
fo.cb[nchunk] = cbd;
|
||||||
fo.bd += delta;
|
fo.bd += delta;
|
||||||
|
|
||||||
var p = this.perc(fo.bd, fo.bd0, fo.bt, fobj.t3);
|
var p = this.perc(fo.bd, fo.bd0, fo.bt, fobj.t_uploading);
|
||||||
fo.hp = '{0}%, {1}, {2} MB/s'.format(
|
fo.hp = '{0}%, {1}, {2} MB/s'.format(
|
||||||
p[0].toFixed(2), p[1], p[2].toFixed(2)
|
p[0].toFixed(2), p[1], p[2].toFixed(2)
|
||||||
);
|
);
|
||||||
@@ -256,6 +259,41 @@ function U2pvis(act, btns) {
|
|||||||
var obj = ebi('f{0}p'.format(fobj.n)),
|
var obj = ebi('f{0}p'.format(fobj.n)),
|
||||||
o1 = p[0] - 2, o2 = p[0] - 0.1, o3 = p[0];
|
o1 = p[0] - 2, o2 = p[0] - 0.1, o3 = p[0];
|
||||||
|
|
||||||
|
if (!obj) { //} || true) {
|
||||||
|
var msg = [
|
||||||
|
"act", this.act,
|
||||||
|
"in", fo.in,
|
||||||
|
"is_act", this.is_act(fo.in),
|
||||||
|
"head", this.head,
|
||||||
|
"tail", this.tail,
|
||||||
|
"nfile", fobj.n,
|
||||||
|
"name", fobj.name,
|
||||||
|
"sz", fobj.size,
|
||||||
|
"bytesDelta", delta,
|
||||||
|
"bytesDone", fo.bd,
|
||||||
|
],
|
||||||
|
m2 = '',
|
||||||
|
ds = QSA("#u2tab>tbody>tr>td:first-child>a:last-child");
|
||||||
|
|
||||||
|
for (var a = 0; a < msg.length; a += 2)
|
||||||
|
m2 += msg[a] + '=' + msg[a + 1] + ', ';
|
||||||
|
|
||||||
|
console.log(m2);
|
||||||
|
|
||||||
|
for (var a = 0, aa = ds.length; a < aa; a++) {
|
||||||
|
var id = ds[a].parentNode.getAttribute('id').slice(1, -1);
|
||||||
|
console.log("dom %d/%d = [%s] in(%s) is_act(%s) %s",
|
||||||
|
a, aa, id, this.tab[id].in, this.is_act(fo.in), ds[a].textContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var a = 0, aa = this.tab.length; a < aa; a++)
|
||||||
|
if (this.is_act(this.tab[a].in))
|
||||||
|
console.log("tab %d/%d = sz %s", a, aa, this.tab[a].bt);
|
||||||
|
|
||||||
|
console.log("a");
|
||||||
|
throw 42;
|
||||||
|
}
|
||||||
|
|
||||||
obj.innerHTML = fo.hp;
|
obj.innerHTML = fo.hp;
|
||||||
obj.style.color = '#fff';
|
obj.style.color = '#fff';
|
||||||
obj.style.background = 'linear-gradient(90deg, #050, #270 ' + o1 + '%, #4b0 ' + o2 + '%, #333 ' + o3 + '%, #333 99%, #777)';
|
obj.style.background = 'linear-gradient(90deg, #050, #270 ' + o1 + '%, #4b0 ' + o2 + '%, #333 ' + o3 + '%, #333 99%, #777)';
|
||||||
@@ -270,26 +308,35 @@ function U2pvis(act, btns) {
|
|||||||
throw 42;
|
throw 42;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//console.log("oldcat %s %d, newcat %s %d, head=%d, tail=%d, file=%d, act.old=%s, act.new=%s, bz_act=%s",
|
||||||
|
// oldcat, this.ctr[oldcat],
|
||||||
|
// newcat, this.ctr[newcat],
|
||||||
|
// this.head, this.tail, nfile,
|
||||||
|
// this.is_act(oldcat), this.is_act(newcat), bz_act);
|
||||||
|
|
||||||
fo.in = newcat;
|
fo.in = newcat;
|
||||||
this.ctr[oldcat]--;
|
this.ctr[oldcat]--;
|
||||||
this.ctr[newcat]++;
|
this.ctr[newcat]++;
|
||||||
this.drawcard(oldcat);
|
this.drawcard(oldcat);
|
||||||
this.drawcard(newcat);
|
this.drawcard(newcat);
|
||||||
if (this.is_act(newcat)) {
|
if (this.is_act(newcat)) {
|
||||||
this.tail++;
|
this.tail = Math.max(this.tail, nfile + 1);
|
||||||
if (!ebi('f' + nfile))
|
if (!ebi('f' + nfile))
|
||||||
this.addrow(nfile);
|
this.addrow(nfile);
|
||||||
}
|
}
|
||||||
else if (this.is_act(oldcat)) {
|
else if (this.is_act(oldcat)) {
|
||||||
|
while (this.head < Math.min(this.tab.length, this.tail) && this.precard[this.tab[this.head].in])
|
||||||
this.head++;
|
this.head++;
|
||||||
|
|
||||||
if (!bz_act) {
|
if (!bz_act) {
|
||||||
var tr = ebi("f" + nfile);
|
var tr = ebi("f" + nfile);
|
||||||
tr.parentNode.removeChild(tr);
|
tr.parentNode.removeChild(tr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (bz_act) {
|
else return;
|
||||||
|
|
||||||
|
if (bz_act)
|
||||||
this.bzw();
|
this.bzw();
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.bzw = function () {
|
this.bzw = function () {
|
||||||
@@ -303,6 +350,7 @@ function U2pvis(act, btns) {
|
|||||||
|
|
||||||
while (this.head - first > this.wsz) {
|
while (this.head - first > this.wsz) {
|
||||||
var obj = ebi('f' + (first++));
|
var obj = ebi('f' + (first++));
|
||||||
|
if (obj)
|
||||||
obj.parentNode.removeChild(obj);
|
obj.parentNode.removeChild(obj);
|
||||||
}
|
}
|
||||||
while (last - this.tail < this.wsz && last < this.tab.length - 2) {
|
while (last - this.tail < this.wsz && last < this.tab.length - 2) {
|
||||||
@@ -336,6 +384,8 @@ function U2pvis(act, btns) {
|
|||||||
|
|
||||||
this.changecard = function (card) {
|
this.changecard = function (card) {
|
||||||
this.act = card;
|
this.act = card;
|
||||||
|
this.precard = has(["ok", "ng", "done"], this.act) ? {} : this.act == "bz" ? { "ok": 1, "ng": 1 } : { "ok": 1, "ng": 1, "bz": 1 };
|
||||||
|
this.postcard = has(["ok", "ng", "done"], this.act) ? { "bz": 1, "q": 1 } : this.act == "bz" ? { "q": 1 } : {};
|
||||||
this.head = -1;
|
this.head = -1;
|
||||||
this.tail = -1;
|
this.tail = -1;
|
||||||
var html = [];
|
var html = [];
|
||||||
@@ -350,9 +400,23 @@ function U2pvis(act, btns) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.head == -1) {
|
if (this.head == -1) {
|
||||||
this.head = this.tab.length;
|
for (var a = 0; a < this.tab.length; a++) {
|
||||||
this.tail = this.head - 1;
|
var rt = this.tab[a].in;
|
||||||
|
if (this.precard[rt]) {
|
||||||
|
this.head = a + 1;
|
||||||
|
this.tail = a;
|
||||||
}
|
}
|
||||||
|
else if (this.postcard[rt]) {
|
||||||
|
this.head = a;
|
||||||
|
this.tail = a - 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.head < 0)
|
||||||
|
this.head = 0;
|
||||||
|
|
||||||
if (card == "bz") {
|
if (card == "bz") {
|
||||||
for (var a = this.head - 1; a >= this.head - this.wsz && a >= 0; a--) {
|
for (var a = this.head - 1; a >= this.head - this.wsz && a >= 0; a--) {
|
||||||
html.unshift(this.genrow(a, true).replace(/><td>/, "><td>a "));
|
html.unshift(this.genrow(a, true).replace(/><td>/, "><td>a "));
|
||||||
@@ -399,6 +463,8 @@ function U2pvis(act, btns) {
|
|||||||
that.changecard(newtab);
|
that.changecard(newtab);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.changecard(this.act);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -495,17 +561,21 @@ function up2k_init(subtle) {
|
|||||||
ask_up = bcfg_get('ask_up', true),
|
ask_up = bcfg_get('ask_up', true),
|
||||||
flag_en = bcfg_get('flag_en', false),
|
flag_en = bcfg_get('flag_en', false),
|
||||||
fsearch = bcfg_get('fsearch', false),
|
fsearch = bcfg_get('fsearch', false),
|
||||||
|
turbo = bcfg_get('u2turbo', false),
|
||||||
|
datechk = bcfg_get('u2tdate', true),
|
||||||
fdom_ctr = 0,
|
fdom_ctr = 0,
|
||||||
min_filebuf = 0;
|
min_filebuf = 0;
|
||||||
|
|
||||||
var st = {
|
var st = {
|
||||||
"files": [],
|
"files": [],
|
||||||
"todo": {
|
"todo": {
|
||||||
|
"head": [],
|
||||||
"hash": [],
|
"hash": [],
|
||||||
"handshake": [],
|
"handshake": [],
|
||||||
"upload": []
|
"upload": []
|
||||||
},
|
},
|
||||||
"busy": {
|
"busy": {
|
||||||
|
"head": [],
|
||||||
"hash": [],
|
"hash": [],
|
||||||
"handshake": [],
|
"handshake": [],
|
||||||
"upload": []
|
"upload": []
|
||||||
@@ -516,6 +586,15 @@ function up2k_init(subtle) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function push_t(arr, t) {
|
||||||
|
var sort = arr.length && arr[arr.length - 1].n > t.n;
|
||||||
|
arr.push(t);
|
||||||
|
if (sort)
|
||||||
|
arr.sort(function (a, b) {
|
||||||
|
return a.n < b.n ? -1 : 1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
var pvis = new U2pvis("bz", '#u2cards');
|
var pvis = new U2pvis("bz", '#u2cards');
|
||||||
|
|
||||||
var bobslice = null;
|
var bobslice = null;
|
||||||
@@ -559,7 +638,7 @@ function up2k_init(subtle) {
|
|||||||
}
|
}
|
||||||
else files = e.target.files;
|
else files = e.target.files;
|
||||||
|
|
||||||
if (!files || files.length == 0)
|
if (!files || !files.length)
|
||||||
return alert('no files selected??');
|
return alert('no files selected??');
|
||||||
|
|
||||||
more_one_file();
|
more_one_file();
|
||||||
@@ -598,14 +677,50 @@ function up2k_init(subtle) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function read_dirs(rd, pf, dirs, good, bad) {
|
function rd_flatten(pf, dirs) {
|
||||||
|
var ret = jcp(pf);
|
||||||
|
for (var a = 0; a < dirs.length; a++)
|
||||||
|
ret.push(dirs.fullPath || '');
|
||||||
|
|
||||||
|
ret.sort();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
var rd_missing_ref = [];
|
||||||
|
function read_dirs(rd, pf, dirs, good, bad, spins) {
|
||||||
|
spins = spins || 0;
|
||||||
|
if (++spins == 5)
|
||||||
|
rd_missing_ref = rd_flatten(pf, dirs);
|
||||||
|
|
||||||
|
if (spins == 200) {
|
||||||
|
var missing = rd_flatten(pf, dirs),
|
||||||
|
match = rd_missing_ref.length == missing.length,
|
||||||
|
aa = match ? missing.length : 0;
|
||||||
|
|
||||||
|
missing.sort();
|
||||||
|
for (var a = 0; a < aa; a++)
|
||||||
|
if (rd_missing_ref[a] != missing[a])
|
||||||
|
match = false;
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
var msg = ['directory iterator got stuck on the following {0} items; good chance your browser is about to spinlock:'.format(missing.length)];
|
||||||
|
for (var a = 0; a < Math.min(20, missing.length); a++)
|
||||||
|
msg.push(missing[a]);
|
||||||
|
|
||||||
|
alert(msg.join('\n-- '));
|
||||||
|
dirs = [];
|
||||||
|
pf = [];
|
||||||
|
}
|
||||||
|
spins = 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (!dirs.length) {
|
if (!dirs.length) {
|
||||||
if (!pf.length)
|
if (!pf.length)
|
||||||
return gotallfiles(good, bad);
|
return gotallfiles(good, bad);
|
||||||
|
|
||||||
console.log("retry pf, " + pf.length);
|
console.log("retry pf, " + pf.length);
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
read_dirs(rd, pf, dirs, good, bad);
|
read_dirs(rd, pf, dirs, good, bad, spins);
|
||||||
}, 50);
|
}, 50);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -626,8 +741,7 @@ function up2k_init(subtle) {
|
|||||||
|
|
||||||
pf.push(name);
|
pf.push(name);
|
||||||
dn.file(function (fobj) {
|
dn.file(function (fobj) {
|
||||||
var idx = pf.indexOf(name);
|
apop(pf, name);
|
||||||
pf.splice(idx, 1);
|
|
||||||
try {
|
try {
|
||||||
if (fobj.size > 0) {
|
if (fobj.size > 0) {
|
||||||
good.push([fobj, name]);
|
good.push([fobj, name]);
|
||||||
@@ -645,12 +759,12 @@ function up2k_init(subtle) {
|
|||||||
dirs.shift();
|
dirs.shift();
|
||||||
rd = null;
|
rd = null;
|
||||||
}
|
}
|
||||||
return read_dirs(rd, pf, dirs, good, bad);
|
return read_dirs(rd, pf, dirs, good, bad, spins);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function gotallfiles(good_files, bad_files) {
|
function gotallfiles(good_files, bad_files) {
|
||||||
if (bad_files.length > 0) {
|
if (bad_files.length) {
|
||||||
var ntot = bad_files.length + good_files.length,
|
var ntot = bad_files.length + good_files.length,
|
||||||
msg = 'These {0} files (of {1} total) were skipped because they are empty:\n'.format(bad_files.length, ntot);
|
msg = 'These {0} files (of {1} total) were skipped because they are empty:\n'.format(bad_files.length, ntot);
|
||||||
|
|
||||||
@@ -670,40 +784,52 @@ function up2k_init(subtle) {
|
|||||||
if (ask_up && !fsearch && !confirm(msg.join('\n')))
|
if (ask_up && !fsearch && !confirm(msg.join('\n')))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
var seen = {},
|
||||||
|
evpath = get_evpath(),
|
||||||
|
draw_each = good_files.length < 50;
|
||||||
|
|
||||||
|
for (var a = 0; a < st.files.length; a++)
|
||||||
|
seen[st.files[a].name + '\n' + st.files[a].size] = 1;
|
||||||
|
|
||||||
for (var a = 0; a < good_files.length; a++) {
|
for (var a = 0; a < good_files.length; a++) {
|
||||||
var fobj = good_files[a][0],
|
var fobj = good_files[a][0],
|
||||||
now = Date.now(),
|
now = Date.now(),
|
||||||
lmod = fobj.lastModified || now;
|
lmod = fobj.lastModified || now;
|
||||||
|
|
||||||
var entry = {
|
var entry = {
|
||||||
"n": parseInt(st.files.length.toString()),
|
"n": st.files.length,
|
||||||
"t0": now,
|
"t0": now,
|
||||||
"fobj": fobj,
|
"fobj": fobj,
|
||||||
"name": good_files[a][1],
|
"name": good_files[a][1],
|
||||||
"size": fobj.size,
|
"size": fobj.size,
|
||||||
"lmod": lmod / 1000,
|
"lmod": lmod / 1000,
|
||||||
"purl": get_evpath(),
|
"purl": evpath,
|
||||||
"done": false,
|
"done": false,
|
||||||
"hash": []
|
"hash": []
|
||||||
};
|
},
|
||||||
|
key = entry.name + '\n' + entry.size;
|
||||||
|
|
||||||
var skip = false;
|
if (seen[key])
|
||||||
for (var b = 0; b < st.files.length; b++)
|
|
||||||
if (entry.name == st.files[b].name &&
|
|
||||||
entry.size == st.files[b].size)
|
|
||||||
skip = true;
|
|
||||||
|
|
||||||
if (skip)
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
seen[key] = 1;
|
||||||
|
|
||||||
pvis.addfile([
|
pvis.addfile([
|
||||||
fsearch ? esc(entry.name) : linksplit(
|
fsearch ? esc(entry.name) : linksplit(
|
||||||
uricom_dec(entry.purl)[0] + entry.name).join(' '),
|
uricom_dec(entry.purl)[0] + entry.name).join(' '),
|
||||||
'📐 hash',
|
'📐 hash',
|
||||||
''
|
''
|
||||||
], fobj.size);
|
], fobj.size, draw_each);
|
||||||
|
|
||||||
st.files.push(entry);
|
st.files.push(entry);
|
||||||
st.todo.hash.push(entry);
|
if (turbo)
|
||||||
|
push_t(st.todo.head, entry);
|
||||||
|
else
|
||||||
|
push_t(st.todo.hash, entry);
|
||||||
|
}
|
||||||
|
if (!draw_each) {
|
||||||
|
pvis.drawcard("q");
|
||||||
|
pvis.changecard(pvis.act);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ebi('u2btn').addEventListener('drop', gotfile, false);
|
ebi('u2btn').addEventListener('drop', gotfile, false);
|
||||||
@@ -739,10 +865,35 @@ function up2k_init(subtle) {
|
|||||||
//
|
//
|
||||||
|
|
||||||
function handshakes_permitted() {
|
function handshakes_permitted() {
|
||||||
var lim = multitask ? 1 : 0;
|
if (!st.todo.handshake.length)
|
||||||
return lim >=
|
return true;
|
||||||
|
|
||||||
|
var t = st.todo.handshake[0],
|
||||||
|
cd = t.cooldown;
|
||||||
|
|
||||||
|
if (cd && cd - Date.now() > 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// keepalive or verify
|
||||||
|
if (t.keepalive ||
|
||||||
|
t.t_uploaded)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (parallel_uploads <
|
||||||
|
st.busy.handshake.length)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (st.busy.handshake.length)
|
||||||
|
for (var n = t.n - 1; n >= t.n - parallel_uploads && n >= 0; n--)
|
||||||
|
if (st.files[n].t_uploading)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if ((multitask ? 1 : 0) <
|
||||||
st.todo.upload.length +
|
st.todo.upload.length +
|
||||||
st.busy.upload.length;
|
st.busy.upload.length)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function hashing_permitted() {
|
function hashing_permitted() {
|
||||||
@@ -773,11 +924,14 @@ function up2k_init(subtle) {
|
|||||||
|
|
||||||
clearTimeout(tto);
|
clearTimeout(tto);
|
||||||
running = true;
|
running = true;
|
||||||
while (true) {
|
while (window['vis_exh']) {
|
||||||
var is_busy = 0 !=
|
var now = Date.now(),
|
||||||
|
is_busy = 0 !=
|
||||||
|
st.todo.head.length +
|
||||||
st.todo.hash.length +
|
st.todo.hash.length +
|
||||||
st.todo.handshake.length +
|
st.todo.handshake.length +
|
||||||
st.todo.upload.length +
|
st.todo.upload.length +
|
||||||
|
st.busy.head.length +
|
||||||
st.busy.hash.length +
|
st.busy.hash.length +
|
||||||
st.busy.handshake.length +
|
st.busy.handshake.length +
|
||||||
st.busy.upload.length;
|
st.busy.upload.length;
|
||||||
@@ -791,7 +945,6 @@ function up2k_init(subtle) {
|
|||||||
|
|
||||||
if (flag) {
|
if (flag) {
|
||||||
if (is_busy) {
|
if (is_busy) {
|
||||||
var now = Date.now();
|
|
||||||
flag.take(now);
|
flag.take(now);
|
||||||
if (!flag.ours)
|
if (!flag.ours)
|
||||||
return defer();
|
return defer();
|
||||||
@@ -803,43 +956,52 @@ function up2k_init(subtle) {
|
|||||||
|
|
||||||
var mou_ikkai = false;
|
var mou_ikkai = false;
|
||||||
|
|
||||||
if (st.busy.handshake.length > 0 &&
|
if (st.busy.handshake.length &&
|
||||||
st.busy.handshake[0].busied < Date.now() - 30 * 1000
|
st.busy.handshake[0].t_busied < now - 30 * 1000
|
||||||
) {
|
) {
|
||||||
console.log("retrying stuck handshake");
|
console.log("retrying stuck handshake");
|
||||||
var t = st.busy.handshake.shift();
|
var t = st.busy.handshake.shift();
|
||||||
st.todo.handshake.unshift(t);
|
st.todo.handshake.unshift(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (st.todo.handshake.length > 0 &&
|
var nprev = -1;
|
||||||
st.busy.handshake.length == 0 && (
|
for (var a = 0; a < st.todo.upload.length; a++) {
|
||||||
st.todo.handshake[0].t4 || (
|
var nf = st.todo.upload[a].nfile;
|
||||||
handshakes_permitted() &&
|
if (nprev == nf)
|
||||||
st.busy.upload.length < parallel_uploads
|
continue;
|
||||||
)
|
|
||||||
)
|
nprev = nf;
|
||||||
|
var t = st.files[nf];
|
||||||
|
if (now - t.t_busied > 1000 * 30 &&
|
||||||
|
now - t.t_handshake > 1000 * (21600 - 1800)
|
||||||
) {
|
) {
|
||||||
exec_handshake();
|
apop(st.todo.handshake, t);
|
||||||
|
st.todo.handshake.unshift(t);
|
||||||
|
t.keepalive = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (st.todo.head.length &&
|
||||||
|
st.busy.head.length < parallel_uploads) {
|
||||||
|
exec_head();
|
||||||
mou_ikkai = true;
|
mou_ikkai = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (handshakes_permitted() &&
|
if (handshakes_permitted() &&
|
||||||
st.todo.handshake.length > 0 &&
|
st.todo.handshake.length) {
|
||||||
st.busy.handshake.length == 0 &&
|
|
||||||
st.busy.upload.length < parallel_uploads) {
|
|
||||||
exec_handshake();
|
exec_handshake();
|
||||||
mou_ikkai = true;
|
mou_ikkai = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (st.todo.upload.length > 0 &&
|
if (st.todo.upload.length &&
|
||||||
st.busy.upload.length < parallel_uploads) {
|
st.busy.upload.length < parallel_uploads) {
|
||||||
exec_upload();
|
exec_upload();
|
||||||
mou_ikkai = true;
|
mou_ikkai = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hashing_permitted() &&
|
if (hashing_permitted() &&
|
||||||
st.todo.hash.length > 0 &&
|
st.todo.hash.length &&
|
||||||
st.busy.hash.length == 0) {
|
!st.busy.hash.length) {
|
||||||
exec_hash();
|
exec_hash();
|
||||||
mou_ikkai = true;
|
mou_ikkai = true;
|
||||||
}
|
}
|
||||||
@@ -946,7 +1108,7 @@ function up2k_init(subtle) {
|
|||||||
|
|
||||||
bpend += cdr - car;
|
bpend += cdr - car;
|
||||||
|
|
||||||
reader.onload = function (e) {
|
function orz(e) {
|
||||||
if (!min_filebuf && nch == 1) {
|
if (!min_filebuf && nch == 1) {
|
||||||
min_filebuf = 1;
|
min_filebuf = 1;
|
||||||
var td = Date.now() - t0;
|
var td = Date.now() - t0;
|
||||||
@@ -956,9 +1118,30 @@ function up2k_init(subtle) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
hash_calc(nch, e.target.result);
|
hash_calc(nch, e.target.result);
|
||||||
|
}
|
||||||
|
reader.onload = function (e) {
|
||||||
|
try { orz(e); } catch (ex) { vis_exh(ex + '', '', '', '', ex); }
|
||||||
};
|
};
|
||||||
reader.onerror = function () {
|
reader.onerror = function () {
|
||||||
alert('y o u b r o k e i t\nerror: ' + reader.error);
|
var err = reader.error + '';
|
||||||
|
var handled = false;
|
||||||
|
|
||||||
|
if (err.indexOf('NotReadableError') !== -1 || // win10-chrome defender
|
||||||
|
err.indexOf('NotFoundError') !== -1 // macos-firefox permissions
|
||||||
|
) {
|
||||||
|
pvis.seth(t.n, 1, 'OS-error');
|
||||||
|
pvis.seth(t.n, 2, err);
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handled) {
|
||||||
|
pvis.move(t.n, 'ng');
|
||||||
|
apop(st.busy.hash, t);
|
||||||
|
st.bytes.uploaded += t.size;
|
||||||
|
return tasker();
|
||||||
|
}
|
||||||
|
|
||||||
|
alert('y o u b r o k e i t\nfile: ' + t.name + '\nerror: ' + err);
|
||||||
};
|
};
|
||||||
reader.readAsArrayBuffer(
|
reader.readAsArrayBuffer(
|
||||||
bobslice.call(t.fobj, car, cdr));
|
bobslice.call(t.fobj, car, cdr));
|
||||||
@@ -986,15 +1169,15 @@ function up2k_init(subtle) {
|
|||||||
t.hash.push(hashtab[a]);
|
t.hash.push(hashtab[a]);
|
||||||
}
|
}
|
||||||
|
|
||||||
t.t2 = Date.now();
|
t.t_hashed = Date.now();
|
||||||
if (t.n == 0 && window.location.hash == '#dbg') {
|
if (t.n == 0 && window.location.hash == '#dbg') {
|
||||||
var spd = (t.size / ((t.t2 - t.t1) / 1000.)) / (1024 * 1024.);
|
var spd = (t.size / ((t.t_hashed - t.t_hashing) / 1000.)) / (1024 * 1024.);
|
||||||
alert('{0} ms, {1} MB/s\n'.format(t.t2 - t.t1, spd.toFixed(3)) + t.hash.join('\n'));
|
alert('{0} ms, {1} MB/s\n'.format(t.t_hashed - t.t_hashing, spd.toFixed(3)) + t.hash.join('\n'));
|
||||||
}
|
}
|
||||||
|
|
||||||
pvis.seth(t.n, 2, 'hashing done');
|
pvis.seth(t.n, 2, 'hashing done');
|
||||||
pvis.seth(t.n, 1, '📦 wait');
|
pvis.seth(t.n, 1, '📦 wait');
|
||||||
st.busy.hash.splice(st.busy.hash.indexOf(t), 1);
|
apop(st.busy.hash, t);
|
||||||
st.todo.handshake.push(t);
|
st.todo.handshake.push(t);
|
||||||
tasker();
|
tasker();
|
||||||
};
|
};
|
||||||
@@ -1017,10 +1200,57 @@ function up2k_init(subtle) {
|
|||||||
}, 1);
|
}, 1);
|
||||||
};
|
};
|
||||||
|
|
||||||
t.t1 = Date.now();
|
t.t_hashing = Date.now();
|
||||||
segm_next();
|
segm_next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/////
|
||||||
|
////
|
||||||
|
/// head
|
||||||
|
//
|
||||||
|
|
||||||
|
function exec_head() {
|
||||||
|
var t = st.todo.head.shift();
|
||||||
|
st.busy.head.push(t);
|
||||||
|
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.onerror = function () {
|
||||||
|
console.log('head onerror, retrying', t);
|
||||||
|
apop(st.busy.head, t);
|
||||||
|
st.todo.head.unshift(t);
|
||||||
|
tasker();
|
||||||
|
};
|
||||||
|
function orz(e) {
|
||||||
|
var ok = false;
|
||||||
|
if (xhr.status == 200) {
|
||||||
|
var srv_sz = xhr.getResponseHeader('Content-Length'),
|
||||||
|
srv_ts = xhr.getResponseHeader('Last-Modified');
|
||||||
|
|
||||||
|
ok = t.size == srv_sz;
|
||||||
|
if (ok && datechk) {
|
||||||
|
srv_ts = new Date(srv_ts) / 1000;
|
||||||
|
ok = Math.abs(srv_ts - t.lmod) < 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
apop(st.busy.head, t);
|
||||||
|
if (!ok)
|
||||||
|
return push_t(st.todo.hash, t);
|
||||||
|
|
||||||
|
t.done = true;
|
||||||
|
st.bytes.hashed += t.size;
|
||||||
|
st.bytes.uploaded += t.size;
|
||||||
|
pvis.seth(t.n, 1, 'YOLO');
|
||||||
|
pvis.seth(t.n, 2, "turbo'd");
|
||||||
|
pvis.move(t.n, 'ok');
|
||||||
|
};
|
||||||
|
xhr.onload = function (e) {
|
||||||
|
try { orz(e); } catch (ex) { vis_exh(ex + '', '', '', '', ex); }
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.open('HEAD', t.purl + t.name, true);
|
||||||
|
xhr.send();
|
||||||
|
}
|
||||||
|
|
||||||
/////
|
/////
|
||||||
////
|
////
|
||||||
/// handshake
|
/// handshake
|
||||||
@@ -1028,30 +1258,41 @@ function up2k_init(subtle) {
|
|||||||
|
|
||||||
function exec_handshake() {
|
function exec_handshake() {
|
||||||
var t = st.todo.handshake.shift(),
|
var t = st.todo.handshake.shift(),
|
||||||
|
keepalive = t.keepalive,
|
||||||
me = Date.now();
|
me = Date.now();
|
||||||
|
|
||||||
st.busy.handshake.push(t);
|
st.busy.handshake.push(t);
|
||||||
t.busied = me;
|
t.keepalive = undefined;
|
||||||
|
t.t_busied = me;
|
||||||
|
|
||||||
|
if (keepalive)
|
||||||
|
console.log("sending keepalive handshake", t);
|
||||||
|
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
xhr.onerror = function () {
|
xhr.onerror = function () {
|
||||||
if (t.busied != me) {
|
if (t.t_busied != me) {
|
||||||
console.log('zombie handshake onerror,', t);
|
console.log('zombie handshake onerror,', t);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log('handshake onerror, retrying');
|
console.log('handshake onerror, retrying', t);
|
||||||
st.busy.handshake.splice(st.busy.handshake.indexOf(t), 1);
|
apop(st.busy.handshake, t);
|
||||||
st.todo.handshake.unshift(t);
|
st.todo.handshake.unshift(t);
|
||||||
|
t.keepalive = keepalive;
|
||||||
tasker();
|
tasker();
|
||||||
};
|
};
|
||||||
xhr.onload = function (e) {
|
function orz(e) {
|
||||||
if (t.busied != me) {
|
if (t.t_busied != me) {
|
||||||
console.log('zombie handshake onload,', t);
|
console.log('zombie handshake onload,', t);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (xhr.status == 200) {
|
if (xhr.status == 200) {
|
||||||
var response = JSON.parse(xhr.responseText);
|
t.t_handshake = Date.now();
|
||||||
|
if (keepalive) {
|
||||||
|
apop(st.busy.handshake, t);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var response = JSON.parse(xhr.responseText);
|
||||||
if (!response.name) {
|
if (!response.name) {
|
||||||
var msg = '',
|
var msg = '',
|
||||||
smsg = '';
|
smsg = '';
|
||||||
@@ -1075,7 +1316,7 @@ function up2k_init(subtle) {
|
|||||||
pvis.seth(t.n, 2, msg);
|
pvis.seth(t.n, 2, msg);
|
||||||
pvis.seth(t.n, 1, smsg);
|
pvis.seth(t.n, 1, smsg);
|
||||||
pvis.move(t.n, smsg == '404' ? 'ng' : 'ok');
|
pvis.move(t.n, smsg == '404' ? 'ng' : 'ok');
|
||||||
st.busy.handshake.splice(st.busy.handshake.indexOf(t), 1);
|
apop(st.busy.handshake, t);
|
||||||
st.bytes.uploaded += t.size;
|
st.bytes.uploaded += t.size;
|
||||||
t.done = true;
|
t.done = true;
|
||||||
tasker();
|
tasker();
|
||||||
@@ -1084,6 +1325,7 @@ function up2k_init(subtle) {
|
|||||||
|
|
||||||
if (response.name !== t.name) {
|
if (response.name !== t.name) {
|
||||||
// file exists; server renamed us
|
// file exists; server renamed us
|
||||||
|
console.log("server-rename [" + t.name + "] to [" + response.name + "]");
|
||||||
t.name = response.name;
|
t.name = response.name;
|
||||||
pvis.seth(t.n, 0, linksplit(t.purl + t.name).join(' '));
|
pvis.seth(t.n, 0, linksplit(t.purl + t.name).join(' '));
|
||||||
}
|
}
|
||||||
@@ -1116,31 +1358,41 @@ function up2k_init(subtle) {
|
|||||||
var done = true,
|
var done = true,
|
||||||
msg = '🎷🐛';
|
msg = '🎷🐛';
|
||||||
|
|
||||||
if (t.postlist.length > 0) {
|
if (t.postlist.length) {
|
||||||
|
var arr = st.todo.upload,
|
||||||
|
sort = arr.length && arr[arr.length - 1].nfile > t.n;
|
||||||
|
|
||||||
for (var a = 0; a < t.postlist.length; a++)
|
for (var a = 0; a < t.postlist.length; a++)
|
||||||
st.todo.upload.push({
|
arr.push({
|
||||||
'nfile': t.n,
|
'nfile': t.n,
|
||||||
'npart': t.postlist[a]
|
'npart': t.postlist[a]
|
||||||
});
|
});
|
||||||
|
|
||||||
msg = 'uploading';
|
msg = 'uploading';
|
||||||
done = false;
|
done = false;
|
||||||
|
|
||||||
|
if (sort)
|
||||||
|
arr.sort(function (a, b) {
|
||||||
|
return a.nfile < b.nfile ? -1 :
|
||||||
|
/* */ a.nfile > b.nfile ? 1 :
|
||||||
|
a.npart < b.npart ? -1 : 1;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
pvis.seth(t.n, 1, msg);
|
pvis.seth(t.n, 1, msg);
|
||||||
st.busy.handshake.splice(st.busy.handshake.indexOf(t), 1);
|
apop(st.busy.handshake, t);
|
||||||
|
|
||||||
if (done) {
|
if (done) {
|
||||||
t.done = true;
|
t.done = true;
|
||||||
st.bytes.uploaded += t.size - t.bytes_uploaded;
|
st.bytes.uploaded += t.size - t.bytes_uploaded;
|
||||||
var spd1 = (t.size / ((t.t2 - t.t1) / 1000.)) / (1024 * 1024.),
|
var spd1 = (t.size / ((t.t_hashed - t.t_hashing) / 1000.)) / (1024 * 1024.),
|
||||||
spd2 = (t.size / ((t.t4 - t.t3) / 1000.)) / (1024 * 1024.);
|
spd2 = (t.size / ((t.t_uploaded - t.t_uploading) / 1000.)) / (1024 * 1024.);
|
||||||
|
|
||||||
pvis.seth(t.n, 2, 'hash {0}, up {1} MB/s'.format(
|
pvis.seth(t.n, 2, 'hash {0}, up {1} MB/s'.format(
|
||||||
spd1.toFixed(2), spd2.toFixed(2)));
|
spd1.toFixed(2), spd2.toFixed(2)));
|
||||||
|
|
||||||
pvis.move(t.n, 'ok');
|
pvis.move(t.n, 'ok');
|
||||||
}
|
}
|
||||||
else t.t4 = undefined;
|
else t.t_uploaded = undefined;
|
||||||
|
|
||||||
tasker();
|
tasker();
|
||||||
}
|
}
|
||||||
@@ -1155,6 +1407,15 @@ function up2k_init(subtle) {
|
|||||||
if (rsp.indexOf('<pre>') === 0)
|
if (rsp.indexOf('<pre>') === 0)
|
||||||
rsp = rsp.slice(5);
|
rsp = rsp.slice(5);
|
||||||
|
|
||||||
|
if (rsp.indexOf('rate-limit ') !== -1) {
|
||||||
|
var penalty = rsp.replace(/.*rate-limit /, "").split(' ')[0];
|
||||||
|
console.log("rate-limit: " + penalty);
|
||||||
|
t.cooldown = Date.now() + parseFloat(penalty) * 1000;
|
||||||
|
apop(st.busy.handshake, t);
|
||||||
|
st.todo.handshake.unshift(t);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
st.bytes.uploaded += t.size;
|
st.bytes.uploaded += t.size;
|
||||||
if (rsp.indexOf('partial upload exists') !== -1 ||
|
if (rsp.indexOf('partial upload exists') !== -1 ||
|
||||||
rsp.indexOf('file already exists') !== -1) {
|
rsp.indexOf('file already exists') !== -1) {
|
||||||
@@ -1169,7 +1430,7 @@ function up2k_init(subtle) {
|
|||||||
pvis.seth(t.n, 2, err);
|
pvis.seth(t.n, 2, err);
|
||||||
pvis.move(t.n, 'ng');
|
pvis.move(t.n, 'ng');
|
||||||
|
|
||||||
st.busy.handshake.splice(st.busy.handshake.indexOf(t), 1);
|
apop(st.busy.handshake, t);
|
||||||
tasker();
|
tasker();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1179,6 +1440,9 @@ function up2k_init(subtle) {
|
|||||||
(xhr.responseText && xhr.responseText) ||
|
(xhr.responseText && xhr.responseText) ||
|
||||||
"no further information"));
|
"no further information"));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
xhr.onload = function (e) {
|
||||||
|
try { orz(e); } catch (ex) { vis_exh(ex + '', '', '', '', ex); }
|
||||||
};
|
};
|
||||||
|
|
||||||
var req = {
|
var req = {
|
||||||
@@ -1207,8 +1471,8 @@ function up2k_init(subtle) {
|
|||||||
var npart = upt.npart,
|
var npart = upt.npart,
|
||||||
t = st.files[upt.nfile];
|
t = st.files[upt.nfile];
|
||||||
|
|
||||||
if (!t.t3)
|
if (!t.t_uploading)
|
||||||
t.t3 = Date.now();
|
t.t_uploading = Date.now();
|
||||||
|
|
||||||
pvis.seth(t.n, 1, "🚀 send");
|
pvis.seth(t.n, 1, "🚀 send");
|
||||||
|
|
||||||
@@ -1219,30 +1483,44 @@ function up2k_init(subtle) {
|
|||||||
if (cdr >= t.size)
|
if (cdr >= t.size)
|
||||||
cdr = t.size;
|
cdr = t.size;
|
||||||
|
|
||||||
var xhr = new XMLHttpRequest();
|
function orz(xhr) {
|
||||||
xhr.upload.onprogress = function (xev) {
|
var txt = ((xhr.response && xhr.response.err) || xhr.responseText) + '';
|
||||||
pvis.prog(t, npart, xev.loaded);
|
|
||||||
};
|
|
||||||
xhr.onload = function (xev) {
|
|
||||||
if (xhr.status == 200) {
|
if (xhr.status == 200) {
|
||||||
pvis.prog(t, npart, cdr - car);
|
pvis.prog(t, npart, cdr - car);
|
||||||
st.bytes.uploaded += cdr - car;
|
st.bytes.uploaded += cdr - car;
|
||||||
t.bytes_uploaded += cdr - car;
|
t.bytes_uploaded += cdr - car;
|
||||||
st.busy.upload.splice(st.busy.upload.indexOf(upt), 1);
|
}
|
||||||
t.postlist.splice(t.postlist.indexOf(npart), 1);
|
else if (txt.indexOf('already got that') !== -1) {
|
||||||
if (t.postlist.length == 0) {
|
console.log("ignoring dupe-segment error", t);
|
||||||
t.t4 = Date.now();
|
}
|
||||||
|
else {
|
||||||
|
alert("server broke; cu-err {0} on file [{1}]:\n".format(
|
||||||
|
xhr.status, t.name) + (txt || "no further information"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
apop(st.busy.upload, upt);
|
||||||
|
apop(t.postlist, npart);
|
||||||
|
if (!t.postlist.length) {
|
||||||
|
t.t_uploaded = Date.now();
|
||||||
pvis.seth(t.n, 1, 'verifying');
|
pvis.seth(t.n, 1, 'verifying');
|
||||||
st.todo.handshake.unshift(t);
|
st.todo.handshake.unshift(t);
|
||||||
}
|
}
|
||||||
tasker();
|
tasker();
|
||||||
}
|
}
|
||||||
else
|
function do_send() {
|
||||||
alert("server broke; cu-err {0} on file [{1}]:\n".format(
|
var xhr = new XMLHttpRequest();
|
||||||
xhr.status, t.name) + (
|
xhr.upload.onprogress = function (xev) {
|
||||||
(xhr.response && xhr.response.err) ||
|
pvis.prog(t, npart, xev.loaded);
|
||||||
(xhr.responseText && xhr.responseText) ||
|
};
|
||||||
"no further information"));
|
xhr.onload = function (xev) {
|
||||||
|
try { orz(xhr); } catch (ex) { vis_exh(ex + '', '', '', '', ex); }
|
||||||
|
};
|
||||||
|
xhr.onerror = function (xev) {
|
||||||
|
if (!window['vis_exh'])
|
||||||
|
return;
|
||||||
|
|
||||||
|
console.log('chunkpit onerror, retrying', t);
|
||||||
|
do_send();
|
||||||
};
|
};
|
||||||
xhr.open('POST', t.purl + 'chunkpit.php', true);
|
xhr.open('POST', t.purl + 'chunkpit.php', true);
|
||||||
xhr.setRequestHeader("X-Up2k-Hash", t.hash[npart]);
|
xhr.setRequestHeader("X-Up2k-Hash", t.hash[npart]);
|
||||||
@@ -1254,6 +1532,8 @@ function up2k_init(subtle) {
|
|||||||
xhr.responseType = 'text';
|
xhr.responseType = 'text';
|
||||||
xhr.send(bobslice.call(t.fobj, car, cdr));
|
xhr.send(bobslice.call(t.fobj, car, cdr));
|
||||||
}
|
}
|
||||||
|
do_send();
|
||||||
|
}
|
||||||
|
|
||||||
/////
|
/////
|
||||||
////
|
////
|
||||||
@@ -1292,6 +1572,17 @@ function up2k_init(subtle) {
|
|||||||
}
|
}
|
||||||
tt.init();
|
tt.init();
|
||||||
|
|
||||||
|
function bumpthread2(e) {
|
||||||
|
if (e.ctrlKey || e.altKey || e.metaKey || e.isComposing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (e.code == 'ArrowUp')
|
||||||
|
bumpthread(1);
|
||||||
|
|
||||||
|
if (e.code == 'ArrowDown')
|
||||||
|
bumpthread(-1);
|
||||||
|
}
|
||||||
|
|
||||||
function bumpthread(dir) {
|
function bumpthread(dir) {
|
||||||
try {
|
try {
|
||||||
dir.stopPropagation();
|
dir.stopPropagation();
|
||||||
@@ -1302,7 +1593,7 @@ function up2k_init(subtle) {
|
|||||||
if (dir.target) {
|
if (dir.target) {
|
||||||
clmod(obj, 'err', 1);
|
clmod(obj, 'err', 1);
|
||||||
var v = Math.floor(parseInt(obj.value));
|
var v = Math.floor(parseInt(obj.value));
|
||||||
if (v < 1 || v > 8 || v !== v)
|
if (v < 0 || v > 64 || v !== v)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
parallel_uploads = v;
|
parallel_uploads = v;
|
||||||
@@ -1313,11 +1604,11 @@ function up2k_init(subtle) {
|
|||||||
|
|
||||||
parallel_uploads += dir;
|
parallel_uploads += dir;
|
||||||
|
|
||||||
if (parallel_uploads < 1)
|
if (parallel_uploads < 0)
|
||||||
parallel_uploads = 1;
|
parallel_uploads = 0;
|
||||||
|
|
||||||
if (parallel_uploads > 8)
|
if (parallel_uploads > 16)
|
||||||
parallel_uploads = 8;
|
parallel_uploads = 16;
|
||||||
|
|
||||||
obj.value = parallel_uploads;
|
obj.value = parallel_uploads;
|
||||||
bumpthread({ "target": 1 })
|
bumpthread({ "target": 1 })
|
||||||
@@ -1337,6 +1628,35 @@ function up2k_init(subtle) {
|
|||||||
set_fsearch(!fsearch);
|
set_fsearch(!fsearch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function tgl_turbo() {
|
||||||
|
turbo = !turbo;
|
||||||
|
bcfg_set('u2turbo', turbo);
|
||||||
|
draw_turbo();
|
||||||
|
}
|
||||||
|
|
||||||
|
function tgl_datechk() {
|
||||||
|
datechk = !datechk;
|
||||||
|
bcfg_set('u2tdate', datechk);
|
||||||
|
}
|
||||||
|
|
||||||
|
function draw_turbo() {
|
||||||
|
var msgu = '<p class="warn">WARNING: turbo enabled, <span> client may not detect and resume incomplete uploads; see turbo-button tooltip</span></p>',
|
||||||
|
msgs = '<p class="warn">WARNING: turbo enabled, <span> search may give false-positives; see turbo-button tooltip</span></p>',
|
||||||
|
msg = fsearch ? msgs : msgu,
|
||||||
|
omsg = fsearch ? msgu : msgs,
|
||||||
|
html = ebi('u2foot').innerHTML,
|
||||||
|
ohtml = html;
|
||||||
|
|
||||||
|
if (turbo && html.indexOf(msg) === -1)
|
||||||
|
html = html.replace(omsg, '') + msg;
|
||||||
|
else if (!turbo)
|
||||||
|
html = html.replace(msgu, '').replace(msgs, '');
|
||||||
|
|
||||||
|
if (html !== ohtml)
|
||||||
|
ebi('u2foot').innerHTML = html;
|
||||||
|
}
|
||||||
|
draw_turbo();
|
||||||
|
|
||||||
function set_fsearch(new_state) {
|
function set_fsearch(new_state) {
|
||||||
var fixed = false;
|
var fixed = false;
|
||||||
|
|
||||||
@@ -1374,6 +1694,7 @@ function up2k_init(subtle) {
|
|||||||
}
|
}
|
||||||
catch (ex) { }
|
catch (ex) { }
|
||||||
|
|
||||||
|
draw_turbo();
|
||||||
onresize();
|
onresize();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1413,10 +1734,13 @@ function up2k_init(subtle) {
|
|||||||
bumpthread(-1);
|
bumpthread(-1);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ebi('nthread').onkeydown = bumpthread2;
|
||||||
ebi('nthread').addEventListener('input', bumpthread, false);
|
ebi('nthread').addEventListener('input', bumpthread, false);
|
||||||
ebi('multitask').addEventListener('click', tgl_multitask, false);
|
ebi('multitask').addEventListener('click', tgl_multitask, false);
|
||||||
ebi('ask_up').addEventListener('click', tgl_ask_up, false);
|
ebi('ask_up').addEventListener('click', tgl_ask_up, false);
|
||||||
ebi('flag_en').addEventListener('click', tgl_flag_en, false);
|
ebi('flag_en').addEventListener('click', tgl_flag_en, false);
|
||||||
|
ebi('u2turbo').addEventListener('click', tgl_turbo, false);
|
||||||
|
ebi('u2tdate').addEventListener('click', tgl_datechk, false);
|
||||||
var o = ebi('fsearch');
|
var o = ebi('fsearch');
|
||||||
if (o)
|
if (o)
|
||||||
o.addEventListener('click', tgl_fsearch, false);
|
o.addEventListener('click', tgl_fsearch, false);
|
||||||
@@ -1426,7 +1750,10 @@ function up2k_init(subtle) {
|
|||||||
nodes[a].addEventListener('touchend', nop, false);
|
nodes[a].addEventListener('touchend', nop, false);
|
||||||
|
|
||||||
set_fsearch();
|
set_fsearch();
|
||||||
bumpthread({ "target": 1 })
|
bumpthread({ "target": 1 });
|
||||||
|
if (parallel_uploads < 1)
|
||||||
|
bumpthread(1);
|
||||||
|
|
||||||
return { "init_deps": init_deps, "set_fsearch": set_fsearch }
|
return { "init_deps": init_deps, "set_fsearch": set_fsearch }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -215,9 +215,31 @@
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
#u2foot .warn {
|
||||||
|
font-size: 1.3em;
|
||||||
|
padding: .5em .8em;
|
||||||
|
margin: 1em -.6em;
|
||||||
|
color: #f74;
|
||||||
|
background: #322;
|
||||||
|
border: 1px solid #633;
|
||||||
|
border-width: .1em 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#u2foot .warn span {
|
||||||
|
color: #f86;
|
||||||
|
}
|
||||||
|
html.light #u2foot .warn {
|
||||||
|
color: #b00;
|
||||||
|
background: #fca;
|
||||||
|
border-color: #f70;
|
||||||
|
}
|
||||||
|
html.light #u2foot .warn span {
|
||||||
|
color: #930;
|
||||||
|
}
|
||||||
#u2foot span {
|
#u2foot span {
|
||||||
color: #999;
|
color: #999;
|
||||||
font-size: .9em;
|
font-size: .9em;
|
||||||
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
#u2footfoot {
|
#u2footfoot {
|
||||||
margin-bottom: -1em;
|
margin-bottom: -1em;
|
||||||
|
|||||||
@@ -11,16 +11,6 @@ var is_touch = 'ontouchstart' in window,
|
|||||||
|
|
||||||
|
|
||||||
// error handler for mobile devices
|
// error handler for mobile devices
|
||||||
function hcroak(msg) {
|
|
||||||
document.body.innerHTML = msg;
|
|
||||||
window.onerror = undefined;
|
|
||||||
throw 'fatal_err';
|
|
||||||
}
|
|
||||||
function croak(msg) {
|
|
||||||
document.body.textContent = msg;
|
|
||||||
window.onerror = undefined;
|
|
||||||
throw msg;
|
|
||||||
}
|
|
||||||
function esc(txt) {
|
function esc(txt) {
|
||||||
return txt.replace(/[&"<>]/g, function (c) {
|
return txt.replace(/[&"<>]/g, function (c) {
|
||||||
return {
|
return {
|
||||||
@@ -32,9 +22,12 @@ function esc(txt) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
function vis_exh(msg, url, lineNo, columnNo, error) {
|
function vis_exh(msg, url, lineNo, columnNo, error) {
|
||||||
|
if (!window.onerror)
|
||||||
|
return;
|
||||||
|
|
||||||
window.onerror = undefined;
|
window.onerror = undefined;
|
||||||
window['vis_exh'] = null;
|
window['vis_exh'] = null;
|
||||||
var html = ['<h1>you hit a bug!</h1><p>please screenshot this error and send me a copy arigathanks gozaimuch (ed/irc.rizon.net or ed#2644)</p><p>',
|
var html = ['<h1>you hit a bug!</h1><p>please send me a screenshot arigathanks gozaimuch: <code>ed/irc.rizon.net</code> or <code>ed#2644</code><br /> (and if you can, press F12 and include the "Console" tab in the screenshot too)</p><p>',
|
||||||
esc(String(msg)), '</p><p>', esc(url + ' @' + lineNo + ':' + columnNo), '</p>'];
|
esc(String(msg)), '</p><p>', esc(url + ' @' + lineNo + ':' + columnNo), '</p>'];
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
@@ -44,9 +37,14 @@ function vis_exh(msg, url, lineNo, columnNo, error) {
|
|||||||
html.push('<h2>' + find[a] + '</h2>' +
|
html.push('<h2>' + find[a] + '</h2>' +
|
||||||
esc(String(error[find[a]])).replace(/\n/g, '<br />\n'));
|
esc(String(error[find[a]])).replace(/\n/g, '<br />\n'));
|
||||||
}
|
}
|
||||||
document.body.style.fontSize = '0.8em';
|
html.push('<p style="border-top:1px solid #999;margin-top:1.5em;font-size:1.4em">if you are stuck here, try to <a href="#" onclick="localStorage.clear();location.reload();">reset copyparty settings</a></p>');
|
||||||
document.body.style.padding = '0 1em 1em 1em';
|
document.body.innerHTML = html.join('\n');
|
||||||
hcroak(html.join('\n'));
|
|
||||||
|
var s = mknod('style');
|
||||||
|
s.innerHTML = 'body{background:#333;color:#ddd;font-family:sans-serif;font-size:0.8em;padding:0 1em 1em 1em} code{color:#bf7;background:#222;padding:.1em;margin:.2em;font-size:1.1em;font-family:monospace,monospace} *{line-height:1.5em}';
|
||||||
|
document.head.appendChild(s);
|
||||||
|
|
||||||
|
throw 'fatal_err';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -67,6 +65,9 @@ function ev(e) {
|
|||||||
if (e.stopPropagation)
|
if (e.stopPropagation)
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
|
if (e.stopImmediatePropagation)
|
||||||
|
e.stopImmediatePropagation();
|
||||||
|
|
||||||
e.returnValue = false;
|
e.returnValue = false;
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
@@ -389,6 +390,18 @@ function has(haystack, needle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function apop(arr, v) {
|
||||||
|
var ofs = arr.indexOf(v);
|
||||||
|
if (ofs !== -1)
|
||||||
|
arr.splice(ofs, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function jcp(obj) {
|
||||||
|
return JSON.parse(JSON.stringify(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function sread(key) {
|
function sread(key) {
|
||||||
if (window.localStorage)
|
if (window.localStorage)
|
||||||
return localStorage.getItem(key);
|
return localStorage.getItem(key);
|
||||||
@@ -502,8 +515,10 @@ var tt = (function () {
|
|||||||
|
|
||||||
var pos = this.getBoundingClientRect(),
|
var pos = this.getBoundingClientRect(),
|
||||||
left = pos.left < window.innerWidth / 2,
|
left = pos.left < window.innerWidth / 2,
|
||||||
top = pos.top < window.innerHeight / 2;
|
top = pos.top < window.innerHeight / 2,
|
||||||
|
big = this.className.indexOf(' ttb') !== -1;
|
||||||
|
|
||||||
|
clmod(r.tt, 'b', big);
|
||||||
r.tt.style.top = top ? pos.bottom + 'px' : 'auto';
|
r.tt.style.top = top ? pos.bottom + 'px' : 'auto';
|
||||||
r.tt.style.bottom = top ? 'auto' : (window.innerHeight - pos.top) + 'px';
|
r.tt.style.bottom = top ? 'auto' : (window.innerHeight - pos.top) + 'px';
|
||||||
r.tt.style.left = left ? pos.left + 'px' : 'auto';
|
r.tt.style.left = left ? pos.left + 'px' : 'auto';
|
||||||
|
|||||||
@@ -15,11 +15,6 @@
|
|||||||
}
|
}
|
||||||
#ggrid>a[href$="/"]:before {
|
#ggrid>a[href$="/"]:before {
|
||||||
content: '📂';
|
content: '📂';
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
margin: -.1em -.4em;
|
|
||||||
text-shadow: 0 0 .1em #000;
|
|
||||||
font-size: 2em;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -27,8 +22,11 @@
|
|||||||
#ggrid>a:before {
|
#ggrid>a:before {
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin: -.1em -.4em;
|
padding: .3em 0;
|
||||||
|
margin: -.4em;
|
||||||
text-shadow: 0 0 .1em #000;
|
text-shadow: 0 0 .1em #000;
|
||||||
|
background: linear-gradient(135deg,rgba(255,255,255,0) 50%,rgba(255,255,255,0.2));
|
||||||
|
border-radius: .3em;
|
||||||
font-size: 2em;
|
font-size: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
51
docs/hls.html
Normal file
51
docs/hls.html
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<!DOCTYPE html><html lang="en"><head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>hls-test</title>
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
</head><body>
|
||||||
|
|
||||||
|
<video id="vid" controls></video>
|
||||||
|
<script src="hls.light.js"></script>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
var video = document.getElementById('vid');
|
||||||
|
var hls = new Hls({
|
||||||
|
debug: true,
|
||||||
|
autoStartLoad: false
|
||||||
|
});
|
||||||
|
hls.loadSource('live/v.m3u8');
|
||||||
|
hls.attachMedia(video);
|
||||||
|
hls.on(Hls.Events.MANIFEST_PARSED, function() {
|
||||||
|
hls.startLoad(0);
|
||||||
|
});
|
||||||
|
hls.on(Hls.Events.MEDIA_ATTACHED, function() {
|
||||||
|
video.muted = true;
|
||||||
|
video.play();
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
general good news:
|
||||||
|
- doesn't need fixed-length segments; ok to let x264 pick optimal keyframes and slice on those
|
||||||
|
- hls.js polls the m3u8 for new segments, scales the duration accordingly, seeking works great
|
||||||
|
- the sfx will grow by 66 KiB since that's how small hls.js can get, wait thats not good
|
||||||
|
|
||||||
|
# vod, creates m3u8 at the end, fixed keyframes, v bad
|
||||||
|
ffmpeg -hide_banner -threads 0 -flags -global_header -i ..\CowboyBebopMovie-OP1.webm -vf scale=1280:-4,format=yuv420p -ac 2 -c:a libopus -b:a 128k -c:v libx264 -preset slow -crf 24 -maxrate:v 5M -bufsize:v 10M -g 120 -keyint_min 120 -sc_threshold 0 -hls_time 4 -hls_playlist_type vod -hls_segment_filename v%05d.ts v.m3u8
|
||||||
|
|
||||||
|
# live, updates m3u8 as it goes, dynamic keyframes, streamable with hls.js
|
||||||
|
ffmpeg -hide_banner -threads 0 -flags -global_header -i ..\..\CowboyBebopMovie-OP1.webm -vf scale=1280:-4,format=yuv420p -ac 2 -c:a libopus -b:a 128k -c:v libx264 -preset slow -crf 24 -maxrate:v 5M -bufsize:v 10M -f segment -segment_list v.m3u8 -segment_format mpegts -segment_list_flags live v%05d.ts
|
||||||
|
|
||||||
|
# fmp4 (fragmented mp4), doesn't work with hls.js, gets duratoin 149:07:51 (536871s), probably the tkhd/mdhd 0xffffffff (timebase 8000? ok)
|
||||||
|
ffmpeg -re -hide_banner -threads 0 -flags +cgop -i ..\..\CowboyBebopMovie-OP1.webm -vf scale=1280:-4,format=yuv420p -ac 2 -c:a libopus -b:a 128k -c:v libx264 -preset slow -crf 24 -maxrate:v 5M -bufsize:v 10M -f segment -segment_list v.m3u8 -segment_format fmp4 -segment_list_flags live v%05d.mp4
|
||||||
|
|
||||||
|
# try 2, works, uses tempfiles for m3u8 updates, good, 6% smaller
|
||||||
|
ffmpeg -re -hide_banner -threads 0 -flags +cgop -i ..\..\CowboyBebopMovie-OP1.webm -vf scale=1280:-4,format=yuv420p -ac 2 -c:a libopus -b:a 128k -c:v libx264 -preset slow -crf 24 -maxrate:v 5M -bufsize:v 10M -f hls -hls_segment_type fmp4 -hls_list_size 0 -hls_segment_filename v%05d.mp4 v.m3u8
|
||||||
|
|
||||||
|
more notes
|
||||||
|
- adding -hls_flags single_file makes duration wack during playback (for both fmp4 and ts), ok once finalized and refreshed, gives no size reduction anyways
|
||||||
|
- bebop op has good keyframe spacing for testing hls.js, in particular it hops one seg back and immediately resumes if it hits eof with the explicit hls.startLoad(0); otherwise it jumps into the middle of a seg and becomes art
|
||||||
|
- can probably -c:v copy most of the time, is there a way to check for cgop? todo
|
||||||
|
|
||||||
|
*/
|
||||||
|
</script>
|
||||||
|
</body></html>
|
||||||
@@ -106,6 +106,12 @@ find -iname up2k.db | while IFS= read -r x; do sqlite3 "$x" 'select substr(w,1,1
|
|||||||
# unschedule mtp scan for all files somewhere under "enc/"
|
# unschedule mtp scan for all files somewhere under "enc/"
|
||||||
sqlite3 -readonly up2k.db 'select substr(up.w,1,16) from up inner join mt on mt.w = substr(up.w,1,16) where rd like "enc/%" and +mt.k = "t:mtp"' > keys; awk '{printf "delete from mt where w = \"%s\" and +k = \"t:mtp\";\n", $0}' <keys | tee /dev/stderr | sqlite3 up2k.db
|
sqlite3 -readonly up2k.db 'select substr(up.w,1,16) from up inner join mt on mt.w = substr(up.w,1,16) where rd like "enc/%" and +mt.k = "t:mtp"' > keys; awk '{printf "delete from mt where w = \"%s\" and +k = \"t:mtp\";\n", $0}' <keys | tee /dev/stderr | sqlite3 up2k.db
|
||||||
|
|
||||||
|
# compare metadata key "key" between two databases
|
||||||
|
sqlite3 -readonly up2k.db.key-full 'select w, v from mt where k = "key" order by w' > k1; sqlite3 -readonly up2k.db 'select w, v from mt where k = "key" order by w' > k2; ok=0; ng=0; while IFS='|' read w k2; do k1="$(grep -E "^$w" k1 | sed -r 's/.*\|//')"; [ "$k1" = "$k2" ] && ok=$((ok+1)) || { ng=$((ng+1)); printf '%3s %3s %s\n' "$k1" "$k2" "$(sqlite3 -readonly up2k.db.key-full "select * from up where substr(w,1,16) = '$w'" | sed -r 's/\|/ | /g')"; }; done < <(cat k2); echo "match $ok diff $ng"
|
||||||
|
|
||||||
|
# actually this is much better
|
||||||
|
sqlite3 -readonly up2k.db.key-full 'select w, v from mt where k = "key" order by w' > k1; sqlite3 -readonly up2k.db 'select mt.w, mt.v, up.rd, up.fn from mt inner join up on mt.w = substr(up.w,1,16) where mt.k = "key" order by up.rd, up.fn' > k2; ok=0; ng=0; while IFS='|' read w k2 path; do k1="$(grep -E "^$w" k1 | sed -r 's/.*\|//')"; [ "$k1" = "$k2" ] && ok=$((ok+1)) || { ng=$((ng+1)); printf '%3s %3s %s\n' "$k1" "$k2" "$path"; }; done < <(cat k2); echo "match $ok diff $ng"
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
## media
|
## media
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import re, os, sys, time, shutil, signal, threading, tarfile, hashlib, platform,
|
|||||||
import subprocess as sp
|
import subprocess as sp
|
||||||
|
|
||||||
"""
|
"""
|
||||||
run me with any version of python, i will unpack and run copyparty
|
pls don't edit this file with a text editor,
|
||||||
|
it breaks the compressed stuff at the end
|
||||||
|
|
||||||
(but please don't edit this file with a text editor
|
run me with any version of python, i will unpack and run copyparty
|
||||||
since that would probably corrupt the binary stuff at the end)
|
|
||||||
|
|
||||||
there's zero binaries! just plaintext python scripts all the way down
|
there's zero binaries! just plaintext python scripts all the way down
|
||||||
so you can easily unpack the archive and inspect it for shady stuff
|
so you can easily unpack the archive and inspect it for shady stuff
|
||||||
|
|||||||
@@ -23,13 +23,14 @@ def hdr(query):
|
|||||||
|
|
||||||
|
|
||||||
class Cfg(Namespace):
|
class Cfg(Namespace):
|
||||||
def __init__(self, a=[], v=[], c=None):
|
def __init__(self, a=None, v=None, c=None):
|
||||||
super(Cfg, self).__init__(
|
super(Cfg, self).__init__(
|
||||||
a=a,
|
a=a or [],
|
||||||
v=v,
|
v=v or [],
|
||||||
c=c,
|
c=c,
|
||||||
rproxy=0,
|
rproxy=0,
|
||||||
ed=False,
|
ed=False,
|
||||||
|
nw=False,
|
||||||
no_zip=False,
|
no_zip=False,
|
||||||
no_scandir=False,
|
no_scandir=False,
|
||||||
no_sendfile=True,
|
no_sendfile=True,
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ from copyparty import util
|
|||||||
|
|
||||||
|
|
||||||
class Cfg(Namespace):
|
class Cfg(Namespace):
|
||||||
def __init__(self, a=[], v=[], c=None):
|
def __init__(self, a=None, v=None, c=None):
|
||||||
ex = {k: False for k in "e2d e2ds e2dsa e2t e2ts e2tsr".split()}
|
ex = {k: False for k in "nw e2d e2ds e2dsa e2t e2ts e2tsr".split()}
|
||||||
ex2 = {
|
ex2 = {
|
||||||
"mtp": [],
|
"mtp": [],
|
||||||
"mte": "a",
|
"mte": "a",
|
||||||
@@ -27,7 +27,7 @@ class Cfg(Namespace):
|
|||||||
"rproxy": 0,
|
"rproxy": 0,
|
||||||
}
|
}
|
||||||
ex.update(ex2)
|
ex.update(ex2)
|
||||||
super(Cfg, self).__init__(a=a, v=v, c=c, **ex)
|
super(Cfg, self).__init__(a=a or [], v=v or [], c=c, **ex)
|
||||||
|
|
||||||
|
|
||||||
class TestVFS(unittest.TestCase):
|
class TestVFS(unittest.TestCase):
|
||||||
|
|||||||
@@ -108,6 +108,9 @@ class VHttpSrv(object):
|
|||||||
aliases = ["splash", "browser", "browser2", "msg", "md", "mde"]
|
aliases = ["splash", "browser", "browser2", "msg", "md", "mde"]
|
||||||
self.j2 = {x: J2_FILES for x in aliases}
|
self.j2 = {x: J2_FILES for x in aliases}
|
||||||
|
|
||||||
|
def cachebuster(self):
|
||||||
|
return "a"
|
||||||
|
|
||||||
|
|
||||||
class VHttpConn(object):
|
class VHttpConn(object):
|
||||||
def __init__(self, args, asrv, log, buf):
|
def __init__(self, args, asrv, log, buf):
|
||||||
@@ -121,8 +124,8 @@ class VHttpConn(object):
|
|||||||
self.log_src = "a"
|
self.log_src = "a"
|
||||||
self.lf_url = None
|
self.lf_url = None
|
||||||
self.hsrv = VHttpSrv()
|
self.hsrv = VHttpSrv()
|
||||||
|
self.nreq = 0
|
||||||
self.nbyte = 0
|
self.nbyte = 0
|
||||||
self.workload = 0
|
|
||||||
self.ico = None
|
self.ico = None
|
||||||
self.thumbcli = None
|
self.thumbcli = None
|
||||||
self.t0 = time.time()
|
self.t0 = time.time()
|
||||||
Reference in New Issue
Block a user