add cache eviction
This commit is contained in:
parent
e55678e28f
commit
483dd527c6
10
README.md
10
README.md
@ -92,10 +92,10 @@ you may also want these, especially on servers:
|
|||||||
* browser
|
* browser
|
||||||
* ☑ tree-view
|
* ☑ tree-view
|
||||||
* ☑ media player
|
* ☑ media player
|
||||||
* ✖ thumbnails
|
* ☑ thumbnails
|
||||||
* ☑ images
|
* ☑ images using Pillow
|
||||||
* ☑ videos
|
* ☑ videos using FFmpeg
|
||||||
* ✖ cache eviction
|
* ☑ cache eviction (max-age; maybe max-size eventually)
|
||||||
* ☑ SPA (browse while uploading)
|
* ☑ SPA (browse while uploading)
|
||||||
* if you use the file-tree on the left only, not folders in the file list
|
* if you use the file-tree on the left only, not folders in the file list
|
||||||
* server indexing
|
* server indexing
|
||||||
@ -106,7 +106,7 @@ you may also want these, especially on servers:
|
|||||||
* ☑ viewer
|
* ☑ viewer
|
||||||
* ☑ editor (sure why not)
|
* ☑ editor (sure why not)
|
||||||
|
|
||||||
summary: it works! you can use it! (but technically not even close to beta)
|
summary: it works!
|
||||||
|
|
||||||
|
|
||||||
# bugs
|
# bugs
|
||||||
|
@ -253,6 +253,9 @@ def run_argparse(argv, formatter):
|
|||||||
ap2.add_argument("--no-thumb", action="store_true", help="disable all thumbnails")
|
ap2.add_argument("--no-thumb", action="store_true", help="disable all thumbnails")
|
||||||
ap2.add_argument("--no-vthumb", action="store_true", help="disable video thumbnails")
|
ap2.add_argument("--no-vthumb", action="store_true", help="disable video thumbnails")
|
||||||
ap2.add_argument("--thumbsz", metavar="WxH", default="352x352", help="thumbnail res")
|
ap2.add_argument("--thumbsz", metavar="WxH", default="352x352", help="thumbnail res")
|
||||||
|
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=1800, help="cleanup interval")
|
||||||
|
ap2.add_argument("--th-maxage", metavar="SEC", type=int, default=604800, help="max folder age")
|
||||||
|
|
||||||
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")
|
||||||
|
@ -286,7 +286,7 @@ class HttpCli(object):
|
|||||||
# "embedded" resources
|
# "embedded" resources
|
||||||
if self.vpath.startswith(".cpr"):
|
if self.vpath.startswith(".cpr"):
|
||||||
if self.vpath.startswith(".cpr/ico/"):
|
if self.vpath.startswith(".cpr/ico/"):
|
||||||
return self.tx_ico(self.vpath.split("/")[-1])
|
return self.tx_ico(self.vpath.split("/")[-1], exact=True)
|
||||||
|
|
||||||
static_path = os.path.join(E.mod, "web/", self.vpath[5:])
|
static_path = os.path.join(E.mod, "web/", self.vpath[5:])
|
||||||
return self.tx_file(static_path)
|
return self.tx_file(static_path)
|
||||||
@ -1207,7 +1207,11 @@ class HttpCli(object):
|
|||||||
self.log("{}, {}".format(logmsg, spd))
|
self.log("{}, {}".format(logmsg, spd))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def tx_ico(self, ext):
|
def tx_ico(self, ext, exact=False):
|
||||||
|
if not exact:
|
||||||
|
if ext.endswith("/"):
|
||||||
|
ext = "a.folder"
|
||||||
|
|
||||||
bad = re.compile(r"[](){}[]|^[0-9_-]*$")
|
bad = re.compile(r"[](){}[]|^[0-9_-]*$")
|
||||||
n = ext.split(".")[1:][::-1]
|
n = ext.split(".")[1:][::-1]
|
||||||
ext = ""
|
ext = ""
|
||||||
|
@ -11,6 +11,7 @@ import calendar
|
|||||||
|
|
||||||
from .__init__ import PY2, WINDOWS, MACOS, VT100
|
from .__init__ import PY2, WINDOWS, MACOS, VT100
|
||||||
from .util import mp
|
from .util import mp
|
||||||
|
from .authsrv import AuthSrv
|
||||||
from .tcpsrv import TcpSrv
|
from .tcpsrv import TcpSrv
|
||||||
from .up2k import Up2k
|
from .up2k import Up2k
|
||||||
from .th_srv import ThumbSrv, HAVE_PIL
|
from .th_srv import ThumbSrv, HAVE_PIL
|
||||||
@ -36,14 +37,17 @@ class SvcHub(object):
|
|||||||
|
|
||||||
self.log = self._log_disabled if args.q else self._log_enabled
|
self.log = self._log_disabled if args.q else self._log_enabled
|
||||||
|
|
||||||
|
# jank goes here
|
||||||
|
auth = AuthSrv(self.args, self.log, False)
|
||||||
|
|
||||||
# initiate all services to manage
|
# initiate all services to manage
|
||||||
self.tcpsrv = TcpSrv(self)
|
self.tcpsrv = TcpSrv(self)
|
||||||
self.up2k = Up2k(self)
|
self.up2k = Up2k(self, auth.vfs.all_vols)
|
||||||
|
|
||||||
self.thumbsrv = None
|
self.thumbsrv = None
|
||||||
if not args.no_thumb:
|
if not args.no_thumb:
|
||||||
if HAVE_PIL:
|
if HAVE_PIL:
|
||||||
self.thumbsrv = ThumbSrv(self)
|
self.thumbsrv = ThumbSrv(self, auth.vfs.all_vols)
|
||||||
else:
|
else:
|
||||||
msg = "need Pillow to create thumbnails; for example:\n {} -m pip install --user Pillow"
|
msg = "need Pillow to create thumbnails; for example:\n {} -m pip install --user Pillow"
|
||||||
self.log("thumb", msg.format(os.path.basename(sys.executable)), c=3)
|
self.log("thumb", msg.format(os.path.basename(sys.executable)), c=3)
|
||||||
@ -76,9 +80,13 @@ class SvcHub(object):
|
|||||||
if self.thumbsrv:
|
if self.thumbsrv:
|
||||||
self.thumbsrv.shutdown()
|
self.thumbsrv.shutdown()
|
||||||
|
|
||||||
print("waiting for thumbsrv...")
|
for n in range(200): # 10s
|
||||||
while not self.thumbsrv.stopped():
|
|
||||||
time.sleep(0.05)
|
time.sleep(0.05)
|
||||||
|
if self.thumbsrv.stopped():
|
||||||
|
break
|
||||||
|
|
||||||
|
if n == 3:
|
||||||
|
print("waiting for thumbsrv...")
|
||||||
|
|
||||||
print("nailed it")
|
print("nailed it")
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
from .util import Cooldown
|
||||||
from .th_srv import thumb_path, THUMBABLE, FMT_FF
|
from .th_srv import thumb_path, THUMBABLE, FMT_FF
|
||||||
|
|
||||||
|
|
||||||
@ -8,6 +10,9 @@ class ThumbCli(object):
|
|||||||
self.broker = broker
|
self.broker = broker
|
||||||
self.args = broker.args
|
self.args = broker.args
|
||||||
|
|
||||||
|
# cache on both sides for less broker spam
|
||||||
|
self.cooldown = Cooldown(self.args.th_poke)
|
||||||
|
|
||||||
def get(self, ptop, rem, mtime):
|
def get(self, ptop, rem, mtime):
|
||||||
ext = rem.rsplit(".")[-1].lower()
|
ext = rem.rsplit(".")[-1].lower()
|
||||||
if ext not in THUMBABLE:
|
if ext not in THUMBABLE:
|
||||||
@ -17,13 +22,22 @@ class ThumbCli(object):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
tpath = thumb_path(ptop, rem, mtime)
|
tpath = thumb_path(ptop, rem, mtime)
|
||||||
|
ret = None
|
||||||
try:
|
try:
|
||||||
st = os.stat(tpath)
|
st = os.stat(tpath)
|
||||||
if st.st_size:
|
if st.st_size:
|
||||||
return tpath
|
ret = tpath
|
||||||
|
else:
|
||||||
return None
|
return None
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
if ret:
|
||||||
|
tdir = os.path.dirname(tpath)
|
||||||
|
if self.cooldown.poke(tdir):
|
||||||
|
self.broker.put(False, "thumbsrv.poke", tdir)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
x = self.broker.put(True, "thumbsrv.get", ptop, rem, mtime)
|
x = self.broker.put(True, "thumbsrv.get", ptop, rem, mtime)
|
||||||
return x.get()
|
return x.get()
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import time
|
||||||
|
import shutil
|
||||||
import base64
|
import base64
|
||||||
import hashlib
|
import hashlib
|
||||||
import threading
|
import threading
|
||||||
import subprocess as sp
|
import subprocess as sp
|
||||||
|
|
||||||
from .__init__ import PY2
|
from .__init__ import PY2
|
||||||
from .util import fsenc, Queue
|
from .util import fsenc, Queue, Cooldown
|
||||||
from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, parse_ffprobe
|
from .mtag import HAVE_FFMPEG, HAVE_FFPROBE, parse_ffprobe
|
||||||
|
|
||||||
|
|
||||||
@ -74,13 +76,16 @@ def thumb_path(ptop, rem, mtime):
|
|||||||
|
|
||||||
|
|
||||||
class ThumbSrv(object):
|
class ThumbSrv(object):
|
||||||
def __init__(self, hub):
|
def __init__(self, hub, vols):
|
||||||
self.hub = hub
|
self.hub = hub
|
||||||
|
self.vols = [v.realpath for v in vols.values()]
|
||||||
|
|
||||||
self.args = hub.args
|
self.args = hub.args
|
||||||
self.log_func = hub.log
|
self.log_func = hub.log
|
||||||
|
|
||||||
res = hub.args.thumbsz.split("x")
|
res = hub.args.thumbsz.split("x")
|
||||||
self.res = tuple([int(x) for x in res])
|
self.res = tuple([int(x) for x in res])
|
||||||
|
self.poke_cd = Cooldown(self.args.th_poke)
|
||||||
|
|
||||||
self.mutex = threading.Lock()
|
self.mutex = threading.Lock()
|
||||||
self.busy = {}
|
self.busy = {}
|
||||||
@ -108,6 +113,10 @@ class ThumbSrv(object):
|
|||||||
msg += ", ".join(missing)
|
msg += ", ".join(missing)
|
||||||
self.log(msg, c=1)
|
self.log(msg, c=1)
|
||||||
|
|
||||||
|
t = threading.Thread(target=self.cleaner)
|
||||||
|
t.daemon = True
|
||||||
|
t.start()
|
||||||
|
|
||||||
def log(self, msg, c=0):
|
def log(self, msg, c=0):
|
||||||
self.log_func("thumb", msg, c)
|
self.log_func("thumb", msg, c)
|
||||||
|
|
||||||
@ -233,3 +242,78 @@ class ThumbSrv(object):
|
|||||||
]
|
]
|
||||||
p = sp.Popen(cmd, stdout=sp.PIPE, stderr=sp.PIPE)
|
p = sp.Popen(cmd, stdout=sp.PIPE, stderr=sp.PIPE)
|
||||||
r = p.communicate()
|
r = p.communicate()
|
||||||
|
|
||||||
|
def poke(self, tdir):
|
||||||
|
if not self.poke_cd.poke(tdir):
|
||||||
|
return
|
||||||
|
|
||||||
|
ts = int(time.time())
|
||||||
|
try:
|
||||||
|
p1 = os.path.dirname(tdir)
|
||||||
|
p2 = os.path.dirname(p1)
|
||||||
|
for dp in [tdir, p1, p2]:
|
||||||
|
os.utime(fsenc(dp), (ts, ts))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def cleaner(self):
|
||||||
|
interval = self.args.th_clean
|
||||||
|
while True:
|
||||||
|
time.sleep(interval)
|
||||||
|
for vol in self.vols:
|
||||||
|
vol += "/.hist/th"
|
||||||
|
self.log("cln {}/".format(vol))
|
||||||
|
self.clean(vol)
|
||||||
|
|
||||||
|
def clean(self, vol):
|
||||||
|
# self.log("cln {}".format(vol))
|
||||||
|
maxage = self.args.th_maxage
|
||||||
|
now = time.time()
|
||||||
|
prev_b64 = None
|
||||||
|
prev_fp = None
|
||||||
|
try:
|
||||||
|
ents = os.listdir(vol)
|
||||||
|
except:
|
||||||
|
return
|
||||||
|
|
||||||
|
for f in sorted(ents):
|
||||||
|
fp = os.path.join(vol, f)
|
||||||
|
cmp = fp.lower().replace("\\", "/")
|
||||||
|
|
||||||
|
# "top" or b64 prefix/full (a folder)
|
||||||
|
if len(f) <= 3 or len(f) == 24:
|
||||||
|
age = now - os.path.getmtime(fp)
|
||||||
|
if age > maxage:
|
||||||
|
with self.mutex:
|
||||||
|
safe = True
|
||||||
|
for k in self.busy.keys():
|
||||||
|
if k.lower().replace("\\", "/").startswith(cmp):
|
||||||
|
safe = False
|
||||||
|
break
|
||||||
|
|
||||||
|
if safe:
|
||||||
|
self.log("rm -rf [{}]".format(fp))
|
||||||
|
shutil.rmtree(fp, ignore_errors=True)
|
||||||
|
else:
|
||||||
|
self.clean(fp)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# thumb file
|
||||||
|
try:
|
||||||
|
b64, ts, ext = f.split(".")
|
||||||
|
if len(b64) != 24 or len(ts) != 8 or ext != "jpg":
|
||||||
|
raise Exception()
|
||||||
|
|
||||||
|
ts = int(ts, 16)
|
||||||
|
except:
|
||||||
|
if f != "dir.txt":
|
||||||
|
self.log("foreign file in thumbs dir: [{}]".format(fp), 1)
|
||||||
|
|
||||||
|
continue
|
||||||
|
|
||||||
|
if b64 == prev_b64:
|
||||||
|
self.log("rm replaced [{}]".format(fp))
|
||||||
|
os.unlink(prev_fp)
|
||||||
|
|
||||||
|
prev_b64 = b64
|
||||||
|
prev_fp = fp
|
||||||
|
@ -32,7 +32,6 @@ from .util import (
|
|||||||
s2hms,
|
s2hms,
|
||||||
)
|
)
|
||||||
from .mtag import MTag
|
from .mtag import MTag
|
||||||
from .authsrv import AuthSrv
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
HAVE_SQLITE3 = True
|
HAVE_SQLITE3 = True
|
||||||
@ -49,10 +48,11 @@ class Up2k(object):
|
|||||||
* ~/.config flatfiles for active jobs
|
* ~/.config flatfiles for active jobs
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, hub):
|
def __init__(self, hub, all_vols):
|
||||||
self.hub = hub
|
self.hub = hub
|
||||||
self.args = hub.args
|
self.args = hub.args
|
||||||
self.log_func = hub.log
|
self.log_func = hub.log
|
||||||
|
self.all_vols = all_vols
|
||||||
|
|
||||||
# config
|
# config
|
||||||
self.salt = self.args.salt
|
self.salt = self.args.salt
|
||||||
@ -92,9 +92,7 @@ class Up2k(object):
|
|||||||
if not HAVE_SQLITE3:
|
if not HAVE_SQLITE3:
|
||||||
self.log("could not initialize sqlite3, will use in-memory registry only")
|
self.log("could not initialize sqlite3, will use in-memory registry only")
|
||||||
|
|
||||||
# this is kinda jank
|
have_e2d = self.init_indexes()
|
||||||
auth = AuthSrv(self.args, self.log_func, False)
|
|
||||||
have_e2d = self.init_indexes(auth)
|
|
||||||
|
|
||||||
if have_e2d:
|
if have_e2d:
|
||||||
thr = threading.Thread(target=self._snapshot)
|
thr = threading.Thread(target=self._snapshot)
|
||||||
@ -139,9 +137,9 @@ class Up2k(object):
|
|||||||
|
|
||||||
return True, ret
|
return True, ret
|
||||||
|
|
||||||
def init_indexes(self, auth):
|
def init_indexes(self):
|
||||||
self.pp = ProgressPrinter()
|
self.pp = ProgressPrinter()
|
||||||
vols = auth.vfs.all_vols.values()
|
vols = self.all_vols.values()
|
||||||
t0 = time.time()
|
t0 = time.time()
|
||||||
have_e2d = False
|
have_e2d = False
|
||||||
|
|
||||||
|
@ -124,6 +124,32 @@ class Counter(object):
|
|||||||
self.v = absval
|
self.v = absval
|
||||||
|
|
||||||
|
|
||||||
|
class Cooldown(object):
|
||||||
|
def __init__(self, maxage):
|
||||||
|
self.maxage = maxage
|
||||||
|
self.mutex = threading.Lock()
|
||||||
|
self.hist = {}
|
||||||
|
self.oldest = 0
|
||||||
|
|
||||||
|
def poke(self, key):
|
||||||
|
with self.mutex:
|
||||||
|
now = time.time()
|
||||||
|
|
||||||
|
ret = False
|
||||||
|
v = self.hist.get(key, 0)
|
||||||
|
if now - v > self.maxage:
|
||||||
|
self.hist[key] = now
|
||||||
|
ret = True
|
||||||
|
|
||||||
|
if self.oldest - now > self.maxage * 2:
|
||||||
|
self.hist = {
|
||||||
|
k: v for k, v in self.hist.items() if now - v < self.maxage
|
||||||
|
}
|
||||||
|
self.oldest = sorted(self.hist.values())[0]
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
class Unrecv(object):
|
class Unrecv(object):
|
||||||
"""
|
"""
|
||||||
undo any number of socket recv ops
|
undo any number of socket recv ops
|
||||||
|
Loading…
Reference in New Issue
Block a user