single authsrv instance per process

This commit is contained in:
ed 2021-06-11 23:01:13 +02:00
parent fbe656957d
commit 60ac68d000
15 changed files with 156 additions and 109 deletions

9
.vscode/launch.json vendored
View file

@ -16,12 +16,9 @@
"-e2ts",
"-mtp",
".bpm=f,bin/mtag/audio-bpm.py",
"-a",
"ed:wark",
"-v",
"srv::r:aed:cnodupe",
"-v",
"dist:dist:r"
"-aed:wark",
"-vsrv::r:aed:cnodupe",
"-vdist:dist:r"
]
},
{

View file

@ -43,7 +43,9 @@ class VFS(object):
)
def get_all_vols(self, outdict):
outdict[self.vpath] = self
if self.realpath:
outdict[self.vpath] = self
for v in self.nodes.values():
v.get_all_vols(outdict)
@ -60,7 +62,7 @@ class VFS(object):
return self.nodes[name].add(src, dst)
vn = VFS(
os.path.join(self.realpath, name) if self.realpath else name,
os.path.join(self.realpath, name) if self.realpath else None,
"{}/{}".format(self.vpath, name).lstrip("/"),
self.uread,
self.uwrite,

View file

@ -36,7 +36,7 @@ class MpWorker(object):
signal.signal(signal.SIGINT, self.signal_handler)
# starting to look like a good idea
self.authsrv = AuthSrv(args, None, False)
self.asrv = AuthSrv(args, None, False)
# instantiate all services here (TODO: inheritance?)
self.httpsrv = HttpSrv(self, True)

View file

@ -15,12 +15,10 @@ class BrokerThr(object):
self.hub = hub
self.log = hub.log
self.args = hub.args
self.asrv = hub.asrv
self.mutex = threading.Lock()
# starting to look like a good idea
self.authsrv = AuthSrv(self.args, None, False)
# instantiate all services here (TODO: inheritance?)
self.httpsrv = HttpSrv(self)
self.httpsrv.disconnect_func = self.httpdrop

View file

@ -42,7 +42,7 @@ class HttpCli(object):
self.addr = conn.addr # type: tuple[str, int]
self.args = conn.args
self.is_mp = conn.is_mp
self.auth = conn.auth # type: AuthSrv
self.asrv = conn.asrv # type: AuthSrv
self.ico = conn.ico
self.thumbcli = conn.thumbcli
self.log_func = conn.log_func
@ -154,9 +154,9 @@ class HttpCli(object):
self.vpath = unquotep(vpath)
pwd = uparam.get("pw")
self.uname = self.auth.iuser.get(pwd, "*")
self.uname = self.asrv.iuser.get(pwd, "*")
self.rvol, self.wvol, self.avol = [[], [], []]
self.auth.vfs.user_tree(self.uname, self.rvol, self.wvol, self.avol)
self.asrv.vfs.user_tree(self.uname, self.rvol, self.wvol, self.avol)
ua = self.headers.get("user-agent", "")
self.is_rclone = ua.startswith("rclone/")
@ -321,9 +321,7 @@ class HttpCli(object):
self.redirect(vpath, flavor="redirecting to", use302=True)
return True
self.readable, self.writable = self.conn.auth.vfs.can_access(
self.vpath, self.uname
)
self.readable, self.writable = self.asrv.vfs.can_access(self.vpath, self.uname)
if not self.readable and not self.writable:
if self.vpath:
self.log("inaccessible: [{}]".format(self.vpath))
@ -440,7 +438,7 @@ class HttpCli(object):
def dump_to_file(self):
reader, remains = self.get_body_reader()
vfs, rem = self.conn.auth.vfs.get(self.vpath, self.uname, False, True)
vfs, rem = self.asrv.vfs.get(self.vpath, self.uname, False, True)
fdir = os.path.join(vfs.realpath, rem)
addr = self.ip.replace(":", ".")
@ -509,7 +507,7 @@ class HttpCli(object):
if v is None:
raise Pebkac(422, "need zip or tar keyword")
vn, rem = self.auth.vfs.get(self.vpath, self.uname, True, False)
vn, rem = self.asrv.vfs.get(self.vpath, self.uname, True, False)
items = self.parser.require("files", 1024 * 1024)
if not items:
raise Pebkac(422, "need files list")
@ -559,7 +557,7 @@ class HttpCli(object):
self.vpath = "/".join([self.vpath, sub]).strip("/")
body["name"] = name
vfs, rem = self.conn.auth.vfs.get(self.vpath, self.uname, False, True)
vfs, rem = self.asrv.vfs.get(self.vpath, self.uname, False, True)
dbv, vrem = vfs.get_dbv(rem)
body["vtop"] = dbv.vpath
@ -590,7 +588,7 @@ class HttpCli(object):
vols = []
seen = {}
for vtop in self.rvol:
vfs, _ = self.conn.auth.vfs.get(vtop, self.uname, True, False)
vfs, _ = self.asrv.vfs.get(vtop, self.uname, True, False)
vfs = vfs.dbv or vfs
if vfs in seen:
continue
@ -651,7 +649,7 @@ class HttpCli(object):
except KeyError:
raise Pebkac(400, "need hash and wark headers for binary POST")
vfs, _ = self.conn.auth.vfs.get(self.vpath, self.uname, False, True)
vfs, _ = self.asrv.vfs.get(self.vpath, self.uname, False, True)
ptop = (vfs.dbv or vfs).realpath
x = self.conn.hsrv.broker.put(True, "up2k.handle_chunk", ptop, wark, chash)
@ -724,7 +722,7 @@ class HttpCli(object):
pwd = self.parser.require("cppwd", 64)
self.parser.drop()
if pwd in self.auth.iuser:
if pwd in self.asrv.iuser:
msg = "login ok"
dt = datetime.utcfromtimestamp(time.time() + 60 * 60 * 24 * 365)
exp = dt.strftime("%a, %d %b %Y %H:%M:%S GMT")
@ -743,7 +741,7 @@ class HttpCli(object):
self.parser.drop()
nullwrite = self.args.nw
vfs, rem = self.conn.auth.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)
sanitized = sanitize_fn(new_dir)
@ -772,7 +770,7 @@ class HttpCli(object):
self.parser.drop()
nullwrite = self.args.nw
vfs, rem = self.conn.auth.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)
if not new_file.endswith(".md"):
@ -796,7 +794,7 @@ class HttpCli(object):
def handle_plain_upload(self):
nullwrite = self.args.nw
vfs, rem = self.conn.auth.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)
files = []
@ -937,7 +935,7 @@ class HttpCli(object):
raise Pebkac(400, "could not read lastmod from request")
nullwrite = self.args.nw
vfs, rem = self.conn.auth.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)
# TODO:
@ -1400,9 +1398,10 @@ class HttpCli(object):
if self.args.no_rescan:
raise Pebkac(403, "disabled by argv")
vn, _ = self.auth.vfs.get(self.vpath, self.uname, True, True)
vn, _ = self.asrv.vfs.get(self.vpath, self.uname, True, True)
args = [self.asrv.vfs.all_vols, [vn.vpath]]
args = [self.auth.vfs.all_vols, [vn.vpath]]
x = self.conn.hsrv.broker.put(True, "up2k.rescan", *args)
x = x.get()
if not x:
@ -1474,7 +1473,7 @@ class HttpCli(object):
ret["k" + quotep(excl)] = sub
try:
vn, rem = self.auth.vfs.get(top, self.uname, True, False)
vn, rem = self.asrv.vfs.get(top, self.uname, True, False)
fsroot, vfs_ls, vfs_virt = vn.ls(
rem, self.uname, not self.args.no_scandir, incl_wo=True
)
@ -1515,7 +1514,7 @@ class HttpCli(object):
vpnodes.append([quotep(vpath) + "/", html_escape(node, crlf=True)])
vn, rem = self.auth.vfs.get(
vn, rem = self.asrv.vfs.get(
self.vpath, self.uname, self.readable, self.writable
)
abspath = vn.canonical(rem)

View file

@ -34,7 +34,7 @@ class HttpConn(object):
self.hsrv = hsrv
self.args = hsrv.args
self.auth = hsrv.auth
self.asrv = hsrv.asrv
self.is_mp = hsrv.is_mp
self.cert_path = hsrv.cert_path
@ -71,7 +71,7 @@ class HttpConn(object):
def get_u2idx(self):
if not self.u2idx:
self.u2idx = U2idx(self.args, self.log_func, self.auth.vfs)
self.u2idx = U2idx(self)
return self.u2idx

View file

@ -40,7 +40,7 @@ class HttpSrv(object):
self.is_mp = is_mp
self.args = broker.args
self.log = broker.log
self.auth = broker.authsrv
self.asrv = broker.asrv
self.disconnect_func = None
self.mutex = threading.Lock()

View file

@ -37,14 +37,13 @@ class SvcHub(object):
self.log = self._log_disabled if args.q else self._log_enabled
# jank goes here
auth = AuthSrv(self.args, self.log, False)
if args.ls:
auth.dbg_ls()
# initiate all services to manage
self.asrv = AuthSrv(self.args, self.log, False)
if args.ls:
self.asrv.dbg_ls()
self.tcpsrv = TcpSrv(self)
self.up2k = Up2k(self, auth.vfs)
self.up2k = Up2k(self)
self.thumbsrv = None
if not args.no_thumb:
@ -54,7 +53,7 @@ class SvcHub(object):
msg = "setting --th-no-webp because either libwebp is not available or your Pillow is too old"
self.log("thumb", msg, c=3)
self.thumbsrv = ThumbSrv(self, auth.vfs)
self.thumbsrv = ThumbSrv(self)
else:
msg = "need Pillow to create thumbnails; for example:\n{}{} -m pip install --user Pillow\n"
self.log(

View file

@ -11,7 +11,7 @@ class ThumbCli(object):
def __init__(self, broker):
self.broker = broker
self.args = broker.args
self.hist = broker.authsrv.vfs.histtab
self.asrv = broker.asrv
# cache on both sides for less broker spam
self.cooldown = Cooldown(self.args.th_poke)
@ -32,8 +32,8 @@ class ThumbCli(object):
if self.args.th_no_webp or (is_vid and self.args.th_ff_jpg):
fmt = "j"
hist = self.hist[ptop]
tpath = thumb_path(hist, rem, mtime, fmt)
histpath = self.asrv.vfs.histtab[ptop]
tpath = thumb_path(histpath, rem, mtime, fmt)
ret = None
try:
st = os.stat(tpath)

View file

@ -2,7 +2,6 @@
from __future__ import print_function, unicode_literals
import os
import sys
import time
import shutil
import base64
@ -74,7 +73,7 @@ if HAVE_FFMPEG and HAVE_FFPROBE:
THUMBABLE.update(FMT_FF)
def thumb_path(hist, rem, mtime, fmt):
def thumb_path(histpath, rem, mtime, fmt):
# base16 = 16 = 256
# b64-lc = 38 = 1444
# base64 = 64 = 4096
@ -96,16 +95,14 @@ def thumb_path(hist, rem, mtime, fmt):
fn = base64.urlsafe_b64encode(h).decode("ascii")[:24]
return "{}/th/{}/{}.{:x}.{}".format(
hist, rd, fn, int(mtime), "webp" if fmt == "w" else "jpg"
histpath, rd, fn, int(mtime), "webp" if fmt == "w" else "jpg"
)
class ThumbSrv(object):
def __init__(self, hub, vfs):
def __init__(self, hub):
self.hub = hub
self.vols = [v.realpath for v in vfs.all_vols.values()]
self.hist = vfs.histtab
self.asrv = hub.asrv
self.args = hub.args
self.log_func = hub.log
@ -154,8 +151,8 @@ class ThumbSrv(object):
return not self.nthr
def get(self, ptop, rem, mtime, fmt):
hist = self.hist[ptop]
tpath = thumb_path(hist, rem, mtime, fmt)
histpath = self.asrv.vfs.histtab[ptop]
tpath = thumb_path(histpath, rem, mtime, fmt)
abspath = os.path.join(ptop, rem)
cond = threading.Condition()
with self.mutex:
@ -331,29 +328,29 @@ class ThumbSrv(object):
interval = self.args.th_clean
while True:
time.sleep(interval)
for vol, hist in self.hist.items():
if hist.startswith(vol):
self.log("\033[Jcln {}/\033[A".format(hist))
for vol, histpath in self.asrv.vfs.histtab.items():
if histpath.startswith(vol):
self.log("\033[Jcln {}/\033[A".format(histpath))
else:
self.log("\033[Jcln {} ({})/\033[A".format(hist, vol))
self.log("\033[Jcln {} ({})/\033[A".format(histpath, vol))
self.clean(hist)
self.clean(histpath)
self.log("\033[Jcln ok")
def clean(self, hist):
# self.log("cln {}".format(hist))
def clean(self, histpath):
# self.log("cln {}".format(histpath))
maxage = self.args.th_maxage
now = time.time()
prev_b64 = None
prev_fp = None
try:
ents = os.listdir(hist)
ents = os.listdir(histpath)
except:
return
for f in sorted(ents):
fp = os.path.join(hist, f)
fp = os.path.join(histpath, f)
cmp = fp.lower().replace("\\", "/")
# "top" or b64 prefix/full (a folder)

View file

@ -19,12 +19,11 @@ except:
class U2idx(object):
def __init__(self, args, log_func, vfs):
self.args = args
self.log_func = log_func
self.vfs = vfs
self.timeout = args.srch_time
def __init__(self, conn):
self.log_func = conn.log_func
self.asrv = conn.asrv
self.args = conn.args
self.timeout = self.args.srch_time
if not HAVE_SQLITE3:
self.log("could not load sqlite3; searchign wqill be disabled")
@ -62,7 +61,8 @@ class U2idx(object):
if cur:
return cur
db_path = os.path.join(self.vfs.histtab[ptop], "up2k.db")
histpath = self.asrv.vfs.histtab[ptop]
db_path = os.path.join(histpath, "up2k.db")
if not os.path.exists(db_path):
return None

View file

@ -48,10 +48,9 @@ class Up2k(object):
* ~/.config flatfiles for active jobs
"""
def __init__(self, hub, vfs):
def __init__(self, hub):
self.hub = hub
self.vfs = vfs
# TODO stop passing around vfs, do auth or broker instead
self.asrv = hub.asrv
self.args = hub.args
self.log_func = hub.log
@ -96,17 +95,17 @@ class Up2k(object):
self.log("could not initialize sqlite3, will use in-memory registry only")
if self.args.no_fastboot:
self.deferred_init(vfs.all_vols)
self.deferred_init()
else:
t = threading.Thread(
target=self.deferred_init,
args=(vfs.all_vols,),
name="up2k-deferred-init",
)
t.daemon = True
t.start()
def deferred_init(self, all_vols):
def deferred_init(self):
all_vols = self.asrv.vfs.all_vols
have_e2d = self.init_indexes(all_vols)
if have_e2d:
@ -300,7 +299,8 @@ class Up2k(object):
return have_e2d
def register_vpath(self, ptop, flags):
db_path = os.path.join(self.vfs.histtab[ptop], "up2k.db")
histpath = self.asrv.vfs.histtab[ptop]
db_path = os.path.join(histpath, "up2k.db")
if ptop in self.registry:
try:
return [self.cur[ptop], db_path]
@ -320,7 +320,7 @@ class Up2k(object):
self.log(" ".join(sorted(a)) + "\033[0m")
reg = {}
path = os.path.join(self.vfs.histtab[ptop], "up2k.snap")
path = os.path.join(histpath, "up2k.snap")
if "e2d" in flags and os.path.exists(path):
with gzip.GzipFile(path, "rb") as f:
j = f.read().decode("utf-8")
@ -344,7 +344,7 @@ class Up2k(object):
return None
try:
os.makedirs(self.vfs.histtab[ptop])
os.makedirs(histpath)
except:
pass
@ -386,7 +386,7 @@ class Up2k(object):
def _build_dir(self, dbw, top, excl, cdir, nohash):
self.pp.msg = "a{} {}".format(self.pp.n, cdir)
histdir = self.vfs.histtab[top]
histpath = self.asrv.vfs.histtab[top]
ret = 0
g = statdir(self.log, not self.args.no_scandir, False, cdir)
for iname, inf in sorted(g):
@ -394,7 +394,7 @@ class Up2k(object):
lmod = int(inf.st_mtime)
sz = inf.st_size
if stat.S_ISDIR(inf.st_mode):
if abspath in excl or abspath == histdir:
if abspath in excl or abspath == histpath:
continue
# self.log(" dir: {}".format(abspath))
ret += self._build_dir(dbw, top, excl, abspath, nohash)
@ -1358,11 +1358,12 @@ class Up2k(object):
for k, reg in self.registry.items():
self._snap_reg(prev, k, reg, discard_interval)
def _snap_reg(self, prev, k, reg, discard_interval):
def _snap_reg(self, prev, ptop, reg, discard_interval):
now = time.time()
histpath = self.asrv.vfs.histtab[ptop]
rm = [x for x in reg.values() if now - x["poke"] > discard_interval]
if rm:
m = "dropping {} abandoned uploads in {}".format(len(rm), k)
m = "dropping {} abandoned uploads in {}".format(len(rm), ptop)
vis = [self._vis_job_progress(x) for x in rm]
self.log("\n".join([m] + vis))
for job in rm:
@ -1380,21 +1381,21 @@ class Up2k(object):
except:
pass
path = os.path.join(self.vfs.histtab[k], "up2k.snap")
path = os.path.join(histpath, "up2k.snap")
if not reg:
if k not in prev or prev[k] is not None:
prev[k] = None
if ptop not in prev or prev[ptop] is not None:
prev[ptop] = None
if os.path.exists(fsenc(path)):
os.unlink(fsenc(path))
return
newest = max(x["poke"] for _, x in reg.items()) if reg else 0
etag = [len(reg), newest]
if etag == prev.get(k, None):
if etag == prev.get(ptop, None):
return
try:
os.makedirs(self.vfs.histtab[k])
os.makedirs(histpath)
except:
pass
@ -1406,7 +1407,7 @@ class Up2k(object):
atomic_move(path2, path)
self.log("snap: {} |{}|".format(path, len(reg.keys())))
prev[k] = etag
prev[ptop] = etag
def _tagger(self):
while True:

View file

@ -11,6 +11,7 @@ import subprocess as sp
class Cpp(object):
def __init__(self, args):
self.ls_pre = set(list(os.listdir()))
self.p = sp.Popen([sys.executable, "-m", "copyparty"] + args)
# , stdout=sp.PIPE, stderr=sp.PIPE)
@ -27,27 +28,29 @@ class Cpp(object):
if wait:
self.t.join()
def main():
t1 = set(list(os.listdir()))
try:
main2()
finally:
t2 = os.listdir()
for f in t2:
if f not in t1 and f.startswith("up."):
def clean(self):
t = os.listdir()
for f in t:
if f not in self.ls_pre and f.startswith("up."):
os.unlink(f)
def main2():
def tc1():
ub = "http://127.0.0.1:4321/"
td = os.path.join("srv", "smoketest")
try:
shutil.rmtree(td)
except:
pass
if os.path.exists(td):
raise
os.mkdir(td)
for _ in range(10):
try:
os.mkdir(td)
except:
time.sleep(0.1) # win10
assert os.path.exists(td)
vidp = os.path.join(tempfile.gettempdir(), "smoketest.h264")
if not os.path.exists(vidp):
@ -57,8 +60,17 @@ def main2():
with open(vidp, "rb") as f:
ovid = f.read()
args = ["-p", "4321", "-e2dsa", "-e2tsr", "--th-ff-jpg"]
args = [
"-p",
"4321",
"-e2dsa",
"-e2tsr",
"--th-ff-jpg",
"--hist",
os.path.join(td, "dbm"),
]
pdirs = []
hpaths = {}
for d1 in ["r", "w", "a"]:
pdirs.append("{}/{}".format(td, d1))
@ -72,8 +84,19 @@ def main2():
udirs = [x.split("/", 2)[2] for x in pdirs]
perms = [x.rstrip("j/")[-1] for x in pdirs]
for pd, ud, p in zip(pdirs, udirs, perms):
# args += ["-v", "{}:{}:{}".format(d.split("/", 1)[1], d, d[-1])]
args += ["-v", "{}:{}:{}".format(pd, ud, p)]
if ud[-1] == "j":
continue
hp = None
if pd.endswith("st/a"):
hp = os.path.join(td, "db1")
elif pd[:-1].endswith("a/j/"):
hp = os.path.join(td, "dbm")
else:
hp = "-"
hpaths[ud] = os.path.join(pd, ".hist") if hp == "-" else hp
args += ["-v", "{}:{}:{}:chist={}".format(pd, ud, p, hp)]
# print(repr(args))
# return
@ -98,6 +121,9 @@ def main2():
except:
pass
cpp.clean()
# GET permission
for d, p in zip(udirs, perms):
u = "{}{}/a.h264".format(ub, d)
r = requests.get(u)
@ -105,12 +131,14 @@ def main2():
if ok != (p in ["a"]):
raise Exception("get {} with perm {} at {}".format(ok, p, u))
# stat filesystem
for d, p in zip(pdirs, perms):
u = "{}/a.h264".format(d)
ok = os.path.exists(u)
if ok != (p in ["a", "w"]):
raise Exception("stat {} with perm {} at {}".format(ok, p, u))
# GET thumbnail, vreify contents
for d, p in zip(udirs, perms):
u = "{}{}/a.h264?th=j".format(ub, d)
r = requests.get(u)
@ -118,8 +146,34 @@ def main2():
if ok != (p in ["a"]):
raise Exception("thumb {} with perm {} at {}".format(ok, p, u))
# check tags
for d, p in zip(udirs, perms):
u = "{}{}?ls".format(ub, d)
r = requests.get(u)
j = r.json() if r else False
tag = None
if j:
for f in j["files"]:
tag = tag or f["tags"].get("res")
r_ok = bool(j)
w_ok = bool(r_ok and j.get("files"))
if not r_ok or w_ok != (p in ["a"]):
raise Exception("ls {} with perm {} at {}".format(ok, p, u))
if (tag and p != "a") or (not tag and p == "a"):
raise Exception("tag {} with perm {} at {}".format(ok, p, u))
if tag is not None and tag != "48x32":
raise Exception("tag [{}] at {}".format(tag, u))
cpp.stop(True)
def main():
tc1()
if __name__ == "__main__":
main()

View file

@ -101,7 +101,7 @@ class TestHttpCli(unittest.TestCase):
pprint.pprint(vcfg)
self.args = Cfg(v=vcfg, a=["o:o", "x:x"])
self.auth = AuthSrv(self.args, self.log)
self.asrv = AuthSrv(self.args, self.log)
vfiles = [x for x in allfiles if x.startswith(top)]
for fp in vfiles:
rok, wok = self.can_rw(fp)
@ -190,12 +190,12 @@ class TestHttpCli(unittest.TestCase):
def put(self, url):
buf = "PUT /{0} HTTP/1.1\r\nCookie: cppwd=o\r\nConnection: close\r\nContent-Length: {1}\r\n\r\nok {0}\n"
buf = buf.format(url, len(url) + 4).encode("utf-8")
conn = tu.VHttpConn(self.args, self.auth, self.log, buf)
conn = tu.VHttpConn(self.args, self.asrv, self.log, buf)
HttpCli(conn).run()
return conn.s._reply.decode("utf-8").split("\r\n\r\n", 1)
def curl(self, url, binary=False):
conn = tu.VHttpConn(self.args, self.auth, self.log, hdr(url))
conn = tu.VHttpConn(self.args, self.asrv, self.log, hdr(url))
HttpCli(conn).run()
if binary:
h, b = conn.s._reply.split(b"\r\n\r\n", 1)

View file

@ -110,12 +110,12 @@ class VHttpSrv(object):
class VHttpConn(object):
def __init__(self, args, auth, log, buf):
def __init__(self, args, asrv, log, buf):
self.s = VSock(buf)
self.sr = Unrecv(self.s)
self.addr = ("127.0.0.1", "42069")
self.args = args
self.auth = auth
self.asrv = asrv
self.is_mp = False
self.log_func = log
self.log_src = "a"