Index: channels/pluginmanager2.py ================================================================== --- channels/pluginmanager2.py +++ channels/pluginmanager2.py @@ -3,11 +3,11 @@ # title: User Plugin Manager Ⅱ # description: Downloads new plugins, or updates them. # version: 0.2 # type: hook # category: config -# depends: uikit, config, pluginconf +# depends: uikit >= 1.9, config >= 2.7, streamtuner2 >= 2.1.8, pluginconf < 1.0 # config: # { name: plugin_repos, type: text, value: "http://fossil.include-once.org/repo.json/streamtuner2/contrib/*.py, http://fossil.include-once.org/repo.json/streamtuner2/channels/*.py", description: "Plugin repository JSON source references." } # { name: plugin_auto, type: boolean, value: 1, description: Apply plugin activation/disabling without restart. } # priority: extra # support: experimental @@ -34,10 +34,11 @@ from config import * from uikit import * import ahttp import json import compat2and3 +from xml.sax.saxutils import escape as html_escape # Plugin manager class pluginmanager2(object): @@ -123,29 +124,31 @@ d = ahttp.get(url, encoding='utf-8') or [] meta += json.loads(d) self.parent.status() # Clean up placeholders in vbox - vbox = self.vbox - for c in vbox.get_children()[3:]: - vbox.remove(c) + _ = [self.vbox.remove(c) for c in self.vbox.get_children()[3:]] # Query existing plugins - have = {name: plugin_meta(module=name) for name in module_list()} + dep = dependency() # Attach available downloads for newpl in meta: id = newpl.get("$name") - if id.find("__") == 0: # skip __init__.py + # skip __init__.py + if id.find("__") == 0: + continue + # exclude if newer/current version already installed + if dep.have.get(id) and dep.have[id]["version"] >= newpl.get("version"): continue - if have.get(id): - if have[id]["version"] >= newpl.get("version"): - continue; + # check dependencies + #newpl["depends"] = "streamtuner2 < 2.2.0, config >= 2.5" + if not dep.depends(newpl): + continue self.add_plugin(newpl) # Readd some filler labels - for i in range(1,3): - self.add_(uikit.label("")) + _ = [self.add_(uikit.label("")) for i in range(1,3)] # Entry for plugin list def add_plugin(self, p): b = self.button("Install", stock="gtk-save", cb=lambda *w:self.install(p)) @@ -159,11 +162,12 @@ self.add_(b, safe_format(text, **p), markup=1) # Add placeholder fields def update_p(self, p): - extras = ["{}: {}".format(n, p[n]) for n in ("status", "support", "author", "depends") if p.get(n)] + fields = ("status", "priority", "support", "author", "depends") + extras = ["{}: {}".format(n, html_escape(p[n])) for n in fields if p.get(n)] p["extras"] = " ".join(["💁"] + extras) p["file"] = p["$file"] for field in ("version", "title", "description", "type", "category"): p.setdefault(field, "-") return p @@ -204,11 +208,67 @@ # just let main load any new plugins p.load_plugin_channels() + +# Do minimal depends: probing +class dependency(object): + + # prepare list of known plugins and versions + def __init__(self): + self.have = {name: plugin_meta(module=name) for name in module_list()} + # dependencies on core modules are somewhat more interesting: + self.have.update({ + "streamtuner2": plugin_meta(module="st2", plugin_base=["config"]), + "uikit": plugin_meta(module="uikit", plugin_base=["config"]), + "config": plugin_meta(module="config", plugin_base=["config"]), + "action": plugin_meta(module="action", plugin_base=["config"]), + }) + have = {} + + # depends: + def depends(self, plugin): + if plugin.get("depends"): + d = self.deps(plugin["depends"]) + if not self.cmp(d, self.have): + return False + return True + + # Split trivial "pkg, mod >= 1, uikit < 4.0" list + def deps(self, dep_str): + d = [] + for dep in re.split(r"\s*[,;]+\s*", dep_str): + # skip deb:pkg-name, rpm:name, bin:name etc. + if not len(dep) or dep.find(":") >= 0: + continue + # find comparison and version num + m = re.search(r"([\w.-]+)\s*([>== 0") + if m and m.group(2): + d.append([m.group(i) for i in (1,2,3)]) + return d + + # Do actual comparison + def cmp(self, d, have): + r = True + for name, op, ver in d: + # skip unknown plugins, might be python module references ("depends: re, json") + if not have.get(name, {}).get("version"): + continue + curr = have[name]["version"] + tbl = { + ">=": curr >= ver, + "<=": curr <= ver, + "==": curr == ver, + ">": curr > ver, + "<": curr < ver, + "!=": curr != ver, + } + r &= tbl.get(op, True) + return r + # Alternative to .format(), with keys possibly being absent from string import Template def safe_format(str, **kwargs): return Template(str).safe_substitute(**kwargs)