diff --git a/copyparty/__main__.py b/copyparty/__main__.py index 9b8a86e7..f88c5cc1 100755 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -874,7 +874,7 @@ def add_thumbnail(ap): ap2.add_argument("--th-poke", metavar="SEC", type=int, default=300, help="activity labeling cooldown -- avoids doing keepalive pokes (updating the mtime) on thumbnail folders more often than SEC seconds") 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 -- folders which haven't been poked for longer than --th-poke seconds will get deleted every --th-clean seconds") - ap2.add_argument("--th-covers", metavar="N,N", type=u, default="folder.png,folder.jpg,cover.png,cover.jpg", help="folder thumbnails to stat/look for") + ap2.add_argument("--th-covers", metavar="N,N", type=u, default="folder.png,folder.jpg,cover.png,cover.jpg", help="folder thumbnails to stat/look for; case-insensitive if -e2d") # https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html # https://github.com/libvips/libvips # ffmpeg -hide_banner -demuxers | awk '/^ D /{print$2}' | while IFS= read -r x; do ffmpeg -hide_banner -h demuxer=$x; done | grep -E '^Demuxer |extensions:' diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index 749d66fe..3316d110 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -3248,6 +3248,9 @@ class HttpCli(object): ): raise Pebkac(403) + e2d = "e2d" in vn.flags + e2t = "e2t" in vn.flags + self.html_head = vn.flags.get("html_head", "") if vn.flags.get("norobots") or "b" in self.uparam: self.out_headers["X-Robots-Tag"] = "noindex, nofollow" @@ -3255,16 +3258,37 @@ class HttpCli(object): self.out_headers.pop("X-Robots-Tag", None) is_dir = stat.S_ISDIR(st.st_mode) + icur = None + if e2t or (e2d and is_dir): + idx = self.conn.get_u2idx() + icur = idx.get_cur(dbv.realpath) + if self.can_read: th_fmt = self.uparam.get("th") if th_fmt is not None: if is_dir: - for fn in self.args.th_covers.split(","): - fp = os.path.join(abspath, fn) - if bos.path.exists(fp): - vrem = "{}/{}".format(vrem.rstrip("/"), fn).strip("/") - is_dir = False - break + vrem = vrem.rstrip("/") + if icur and vrem: + q = "select fn from cv where rd=? and dn=?" + crd, cdn = vrem.rsplit("/", 1) if "/" in vrem else ("", vrem) + # no mojibake support: + try: + cfn = icur.execute(q, (crd, cdn)).fetchone() + if cfn: + fn = cfn[0] + fp = os.path.join(abspath, fn) + if bos.path.exists(fp): + vrem = "{}/{}".format(vrem, fn).strip("/") + is_dir = False + except: + pass + else: + for fn in self.args.th_covers: + fp = os.path.join(abspath, fn) + if bos.path.exists(fp): + vrem = "{}/{}".format(vrem, fn).strip("/") + is_dir = False + break if is_dir: return self.tx_ico("a.folder") @@ -3371,8 +3395,8 @@ class HttpCli(object): "taglist": [], "srvinf": srv_infot, "acct": self.uname, - "idx": ("e2d" in vn.flags), - "itag": ("e2t" in vn.flags), + "idx": e2d, + "itag": e2t, "lifetime": vn.flags.get("lifetime") or 0, "frand": bool(vn.flags.get("rand")), "perms": perms, @@ -3391,8 +3415,8 @@ class HttpCli(object): "taglist": [], "def_hcols": [], "have_emp": self.args.emp, - "have_up2k_idx": ("e2d" in vn.flags), - "have_tags_idx": ("e2t" in vn.flags), + "have_up2k_idx": e2d, + "have_tags_idx": e2t, "have_acode": (not self.args.no_acode), "have_mv": (not self.args.no_mv), "have_del": (not self.args.no_del), @@ -3468,11 +3492,6 @@ class HttpCli(object): if not self.args.ed or "dots" not in self.uparam: ls_names = exclude_dotfiles(ls_names) - icur = None - if "e2t" in vn.flags: - idx = self.conn.get_u2idx() - icur = idx.get_cur(dbv.realpath) - add_fk = vn.flags.get("fk") dirs = [] diff --git a/copyparty/svchub.py b/copyparty/svchub.py index a8bcf5b7..0557707b 100644 --- a/copyparty/svchub.py +++ b/copyparty/svchub.py @@ -348,6 +348,8 @@ class SvcHub(object): if al.rsp_jtr: al.rsp_slp = 0.000001 + al.th_covers = set(al.th_covers.split(",")) + return True def _setlimits(self) -> None: diff --git a/copyparty/up2k.py b/copyparty/up2k.py index 6ca22781..16bf6028 100644 --- a/copyparty/up2k.py +++ b/copyparty/up2k.py @@ -73,6 +73,9 @@ if True: # pylint: disable=using-constant-test if TYPE_CHECKING: from .svchub import SvcHub +zs = "avif,avifs,bmp,gif,heic,heics,heif,heifs,ico,j2p,j2k,jp2,jpeg,jpg,jpx,png,tga,tif,tiff,webp" +CV_EXTS = set(zs.split(",")) + class Dbw(object): def __init__(self, c: "sqlite3.Cursor", n: int, t: float) -> None: @@ -945,6 +948,7 @@ class Up2k(object): unreg: list[str] = [] files: list[tuple[int, int, str]] = [] fat32 = True + cv = "" assert self.pp and self.mem_cur self.pp.msg = "a{} {}".format(self.pp.n, cdir) @@ -1007,6 +1011,12 @@ class Up2k(object): continue files.append((sz, lmod, iname)) + liname = iname.lower() + if sz and ( + iname in self.args.th_covers + or (not cv and liname.rsplit(".", 1)[-1] in CV_EXTS) + ): + cv = iname # folder of 1000 files = ~1 MiB RAM best-case (tiny filenames); # free up stuff we're done with before dhashing @@ -1019,6 +1029,7 @@ class Up2k(object): zh = hashlib.sha1() _ = [zh.update(str(x).encode("utf-8", "replace")) for x in files] + zh.update(cv.encode("utf-8", "replace")) zh.update(spack(b" bool: @@ -1960,6 +1996,7 @@ class Up2k(object): if ver == DB_VER: try: + self._add_cv_tab(cur) self._add_xiu_tab(cur) self._add_dhash_tab(cur) except: @@ -2055,6 +2092,7 @@ class Up2k(object): self._add_dhash_tab(cur) self._add_xiu_tab(cur) + self._add_cv_tab(cur) self.log("created DB at {}".format(db_path)) return cur @@ -2103,6 +2141,27 @@ class Up2k(object): cur.connection.commit() + def _add_cv_tab(self, cur: "sqlite3.Cursor") -> None: + # v5b -> v5c + try: + cur.execute("select rd, dn, fn from cv limit 1").fetchone() + return + except: + pass + + for cmd in [ + r"create table cv (rd text, dn text, fn text)", + r"create index cv_i on cv(rd, dn)", + ]: + cur.execute(cmd) + + try: + cur.execute("delete from dh") + except: + pass + + cur.connection.commit() + def _job_volchk(self, cj: dict[str, Any]) -> None: if not self.register_vpath(cj["ptop"], cj["vcfg"]): if cj["ptop"] not in self.registry: @@ -2824,6 +2883,16 @@ class Up2k(object): with self.rescan_cond: self.rescan_cond.notify_all() + if rd and sz and fn.lower() in self.args.th_covers: + # wasteful; db_add will re-index actual covers + # but that won't catch existing files + crd, cdn = rd.rsplit("/", 1) if "/" in rd else ("", rd) + try: + db.execute("delete from cv where rd=? and dn=?", (crd, cdn)) + db.execute("insert into cv values (?,?,?)", (crd, cdn, fn)) + except: + pass + def handle_rm(self, uname: str, ip: str, vpaths: list[str], lim: list[int]) -> str: n_files = 0 ok = {} diff --git a/copyparty/web/browser.css b/copyparty/web/browser.css index 5bbec98f..16bc0c2b 100644 --- a/copyparty/web/browser.css +++ b/copyparty/web/browser.css @@ -93,6 +93,7 @@ --g-fsel-bg: #d39; --g-fsel-b1: #f4a; --g-fsel-ts: #804; + --g-dfg: var(--srv-3); --g-fg: var(--a-hil); --g-bg: var(--bg-u2); --g-b1: var(--bg-u4); @@ -327,6 +328,7 @@ html.c { } html.cz { --bgg: var(--bg-u2); + --srv-3: #fff; } html.cy { --fg: #fff; @@ -354,6 +356,7 @@ html.cy { --chk-fg: #fd0; --srv-1: #f00; + --srv-3: #fff; --op-aa-bg: #fff; --u2-b1-bg: #f00; @@ -964,6 +967,9 @@ html.y #path a:hover { #ggrid>a.dir:before { content: '📂'; } +#ggrid>a.dir>span { + color: var(--g-dfg); +} #ggrid>a.au:before { content: '💾'; } @@ -1010,6 +1016,9 @@ html.np_open #ggrid>a.au:before { background: var(--g-sel-bg); border-color: var(--g-sel-b1); } +#ggrid>a.sel>span { + color: var(--g-sel-fg); +} #ggrid>a.sel, #ggrid>a[tt].sel { border-top: 1px solid var(--g-fsel-b1);