diff --git a/copyparty/__main__.py b/copyparty/__main__.py index 0a19378e..5f00d31a 100755 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -27,6 +27,8 @@ from .authsrv import expand_config_file, re_vol, split_cfg_ln, upgrade_cfg_fmt from .cfg import flagcats, onedash from .svchub import SvcHub from .util import ( + DEF_MTE, + DEF_MTH, IMPLICATIONS, JINJA_VER, PYFTPD_VER, @@ -1132,10 +1134,8 @@ def add_db_metadata(ap): ap2.add_argument("--mtag-v", action="store_true", help="verbose tag scanning; print errors from mtp subprocesses and such") ap2.add_argument("--mtag-vv", action="store_true", help="debug mtp settings and mutagen/ffprobe parsers") 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=u, help="tags to index/display (comma-sep.)", - default="circle,album,.tn,artist,title,.bpm,key,.dur,.q,.vq,.aq,vc,ac,fmt,res,.fps,ahash,vhash,up_ip,.up_at") - ap2.add_argument("-mth", metavar="M,M,M", type=u, help="tags to hide by default (comma-sep.)", - default=".vq,.aq,vc,ac,fmt,res,.fps") + ap2.add_argument("-mte", metavar="M,M,M", type=u, help="tags to index/display (comma-sep.); either an entire replacement list, or add/remove stuff on the default-list with +foo or /bar", default=DEF_MTE) + ap2.add_argument("-mth", metavar="M,M,M", type=u, help="tags to hide by default (comma-sep.); assign/add/remove same as -mte", default=DEF_MTH) ap2.add_argument("-mtp", metavar="M=[f,]BIN", type=u, action="append", help="read tag M using program BIN to parse the file") diff --git a/copyparty/authsrv.py b/copyparty/authsrv.py index a863cf67..8a98dbd6 100644 --- a/copyparty/authsrv.py +++ b/copyparty/authsrv.py @@ -21,11 +21,13 @@ from .util import ( META_NOBOTS, SQLITE_VER, UNPLICATIONS, + ODict, Pebkac, absreal, afsenc, get_df, humansize, + odfusion, relchk, statdir, uncyg, @@ -1477,13 +1479,14 @@ class AuthSrv(object): # default tag cfgs if unset if "mte" not in vol.flags: - vol.flags["mte"] = self.args.mte - elif vol.flags["mte"].startswith("+"): - vol.flags["mte"] = ",".join( - x for x in [self.args.mte, vol.flags["mte"][1:]] if x - ) + vol.flags["mte"] = self.args.mte.copy() + else: + vol.flags["mte"] = odfusion(self.args.mte, vol.flags["mte"]) + if "mth" not in vol.flags: - vol.flags["mth"] = self.args.mth + vol.flags["mth"] = self.args.mth.copy() + else: + vol.flags["mth"] = odfusion(self.args.mth, vol.flags["mth"]) # append additive args from argv to volflags hooks = "xbu xau xiu xbr xar xbd xad xm xban".split() @@ -1584,12 +1587,12 @@ class AuthSrv(object): if local: local_only_mtp[a] = True - local_mte = {} - for a in vol.flags.get("mte", "").split(","): + local_mte = ODict() + for a in vol.flags.get("mte", {}).keys(): local = True all_mte[a] = True local_mte[a] = True - for b in self.args.mte.split(","): + for b in self.args.mte.keys(): if not a or not b: continue diff --git a/copyparty/httpcli.py b/copyparty/httpcli.py index 8df315c7..846b751c 100644 --- a/copyparty/httpcli.py +++ b/copyparty/httpcli.py @@ -40,6 +40,7 @@ from .util import ( HTTPCODE, META_NOBOTS, MultipartParser, + ODict, Pebkac, UnrecvEOF, absreal, @@ -1971,8 +1972,7 @@ class HttpCli(object): self.log("q#: {} ({:.2f}s)".format(msg, idx.p_dur)) order = [] - cfg = self.args.mte.split(",") - for t in cfg: + for t in self.args.mte: if t in taglist: order.append(t) for t in taglist: @@ -4051,6 +4051,9 @@ class HttpCli(object): ap = vn.canonical(rem) return self.tx_file(ap) # is no-cache + mte = vn.flags.get("mte", {}) + add_up_at = ".up_at" in mte + is_admin = self.can_admin tagset: set[str] = set() for fe in files: fn = fe["name"] @@ -4078,24 +4081,38 @@ class HttpCli(object): self.log(t.format(rd, fn, min_ex())) break - fe["tags"] = {k: v for k, v in r} + tags = {k: v for k, v in r} - if self.can_admin: + if is_admin: q = "select ip, at from up where rd=? and fn=?" try: zs1, zs2 = icur.execute(q, erd_efn).fetchone() - fe["tags"]["up_ip"] = zs1 - fe["tags"][".up_at"] = zs2 + if zs1: + tags["up_ip"] = zs1 + if zs2: + tags[".up_at"] = zs2 + except: + pass + elif add_up_at: + q = "select at from up where rd=? and fn=?" + try: + (zs1,) = icur.execute(q, erd_efn).fetchone() + if zs1: + tags[".up_at"] = zs1 except: pass - _ = [tagset.add(k) for k in fe["tags"]] + _ = [tagset.add(k) for k in tags] + fe["tags"] = tags if icur: - mte = vn.flags.get("mte") or "up_ip,.up_at" - taglist = [k for k in mte.split(",") if k in tagset] + lmte = list(mte) + if self.can_admin: + lmte += ["up_ip", ".up_at"] + + taglist = [k for k in lmte if k in tagset] for fe in dirs: - fe["tags"] = {} + fe["tags"] = ODict() else: taglist = list(tagset) @@ -4142,7 +4159,7 @@ class HttpCli(object): j2a["txt_ext"] = self.args.textfiles.replace(",", " ") if "mth" in vn.flags: - j2a["def_hcols"] = vn.flags["mth"].split(",") + j2a["def_hcols"] = list(vn.flags["mth"]) html = self.j2s(tpl, **j2a) self.reply(html.encode("utf-8", "replace")) diff --git a/copyparty/svchub.py b/copyparty/svchub.py index 2a4effb4..77f52041 100644 --- a/copyparty/svchub.py +++ b/copyparty/svchub.py @@ -39,13 +39,17 @@ from .util import ( FFMPEG_URL, VERSIONS, Daemon, + DEF_MTE, + DEF_MTH, Garda, HLog, HMaccas, + ODict, alltrace, ansi_re, min_ex, mp, + odfusion, pybin, start_log_thrs, start_stackmon, @@ -433,6 +437,12 @@ class SvcHub(object): zs = al.xff_src.replace(" ", "").replace(".", "\\.").replace(",", "|") al.xff_re = re.compile("^(?:" + zs + ")") + mte = ODict.fromkeys(DEF_MTE.split(","), True) + al.mte = odfusion(mte, al.mte) + + mth = ODict.fromkeys(DEF_MTH.split(","), True) + al.mth = odfusion(mth, al.mth) + return True def _setlimits(self) -> None: diff --git a/copyparty/up2k.py b/copyparty/up2k.py index ddb5a4c7..7f255783 100644 --- a/copyparty/up2k.py +++ b/copyparty/up2k.py @@ -642,10 +642,7 @@ class Up2k(object): if self.stop: break - en: set[str] = set() - if "mte" in vol.flags: - en = set(vol.flags["mte"].split(",")) - + en = set(vol.flags.get("mte", {})) self.entags[vol.realpath] = en if "e2d" in vol.flags: diff --git a/copyparty/util.py b/copyparty/util.py index f1606682..5172dd04 100644 --- a/copyparty/util.py +++ b/copyparty/util.py @@ -36,6 +36,14 @@ from .__version__ import S_BUILD_DT, S_VERSION from .stolen import surrogateescape +if sys.version_info >= (3, 7) or ( + sys.version_info >= (3, 6) and platform.python_implementation() == "CPython" +): + ODict = dict +else: + from collections import OrderedDict as ODict + + def _ens(want: str) -> tuple[int, ...]: ret: list[int] = [] for v in want.split(): @@ -261,6 +269,11 @@ EXTS["vnd.mozilla.apng"] = "png" MAGIC_MAP = {"jpeg": "jpg"} +DEF_MTE = "circle,album,.tn,artist,title,.bpm,key,.dur,.q,.vq,.aq,vc,ac,fmt,res,.fps,ahash,vhash" + +DEF_MTH = ".vq,.aq,vc,ac,fmt,res,.fps" + + REKOBO_KEY = { v: ln.split(" ", 1)[0] for ln in """ @@ -1774,6 +1787,21 @@ def exclude_dotfiles(filepaths: list[str]) -> list[str]: return [x for x in filepaths if not x.split("/")[-1].startswith(".")] +def odfusion(base: ODict[str, bool], oth: str) -> ODict[str, bool]: + # merge an "ordered set" (just a dict really) with another list of keys + ret = base.copy() + if oth.startswith("+"): + for k in oth[1:].split(","): + ret[k] = True + elif oth[:1] in ("-", "/"): + for k in oth[1:].split(","): + ret.pop(k, None) + else: + ret = ODict.fromkeys(oth.split(","), True) + + return ret + + def ipnorm(ip: str) -> str: if ":" in ip: # assume /64 clients; drop 4 groups