Rewrite plugin installer as a declarative manifest/engine framework for reliable mix-and-match plugin installs, with backup/restore support and assorted plugin fixes (Hardcover file API, Bookerly/Hardcover SettingAction registration, GitHubSync include path, Lockscreen render timing)
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
import importlib.util
|
||||
import os
|
||||
|
||||
from .engine import SelectedPlugin
|
||||
|
||||
|
||||
def discover_all(plugins_dir):
|
||||
"""Return {name: (plugin_dir, get_manifest_callable)} for every plugin
|
||||
directory that has a plugin.py exposing get_manifest(ctx)."""
|
||||
found = {}
|
||||
for name in sorted(os.listdir(plugins_dir)):
|
||||
plugin_dir = os.path.join(plugins_dir, name)
|
||||
manifest_path = os.path.join(plugin_dir, "plugin.py")
|
||||
if not os.path.isdir(plugin_dir) or not os.path.exists(manifest_path):
|
||||
continue
|
||||
spec = importlib.util.spec_from_file_location(f"xteink_plugin_{name}", manifest_path)
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
if not hasattr(module, "get_manifest"):
|
||||
continue
|
||||
found[name] = (plugin_dir, module.get_manifest)
|
||||
return found
|
||||
|
||||
|
||||
def build_selected(names, plugins_dir, ctx):
|
||||
"""Build a deterministic, sorted-by-name list of SelectedPlugin for the
|
||||
given plugin names. Order doesn't affect correctness (the engine derives
|
||||
shared files from the whole set at once) but keeping it stable makes
|
||||
output/diffs reproducible."""
|
||||
found = discover_all(plugins_dir)
|
||||
selected = []
|
||||
for name in sorted(names):
|
||||
if name not in found:
|
||||
raise KeyError(f"Unknown plugin: {name} (available: {', '.join(sorted(found))})")
|
||||
plugin_dir, get_manifest = found[name]
|
||||
manifest = get_manifest(ctx)
|
||||
selected.append(SelectedPlugin(name=name, plugin_dir=plugin_dir, manifest=manifest))
|
||||
return selected
|
||||
Reference in New Issue
Block a user