ADDED channels/pluginmanager2.py
Index: channels/pluginmanager2.py
==================================================================
--- channels/pluginmanager2.py
+++ channels/pluginmanager2.py
@@ -0,0 +1,165 @@
+# encoding: UTF-8
+# api: streamtuner2
+# title: User Plugin Manager Ⅱ
+# description: Downloads new plugins, or updates them.
+# version: 0.1
+# type: hook
+# category: config
+# depends: uikit, config, pluginconf
+# config:
+# { name: plugin_repos, type: text, value: "http://fossil.include-once.org/plugins.php/streamtuner2/contrib/*.py, http://fossil.include-once.org/plugins.php/streamtuner2/channels/*.py", description: "Plugin sources (common-repo.json)" }
+# priority: extra
+# support: experimental
+#
+# Scans for new plugins from the repository server, using
+# a common-repo.json list. Compares new against installed
+# plugins, and permits to update or download new ones.
+#
+# User plugins go into ~/.config/streamtuner2/channels/
+# and will be picked up in favour of system-installed ones.
+#
+# Further enables direct activation of existing plugins
+# without restarting streamtuner2.
+#
+
+
+import imp
+import config
+import pkgutil
+from channels import __path__ as channels__path__
+import os
+from config import *
+from uikit import *
+import ahttp
+import json
+import compat2and3
+
+
+# Plugin manager
+class pluginmanager2(object):
+
+ module = "pluginmanager2"
+ meta = plugin_meta()
+ parent = None
+ vbox = None
+
+
+ # Hook up
+ def __init__(self, parent):
+
+ # main references
+ self.parent = parent
+ conf.add_plugin_defaults(self.meta, self.module)
+
+ # config dialog
+ parent.hooks["config_load"].append(self.add_config_tab)
+ parent.hooks["config_save"].append(self.activate_plugins)
+
+ # prepare user plugin directory
+ conf.plugin_dir = conf.dir + "/plugins"
+ if not os.path.exists(conf.plugin_dir):
+ os.mkdir(conf.plugin_dir)
+ open(conf.plugin_dir + "/__init__.py", "w").close()
+
+ # register config dir for module loading
+ sys.path.insert(0, conf.dir)
+ channels__path__.insert(0, conf.plugin_dir)
+ # config.plugin_base.append("plugins")
+ # = pkgutil.extend_path(config.__path__, config.__name__)
+
+
+ # Craft new config dialog notebook tab
+ def add_config_tab(self, *w):
+ if self.vbox:
+ return
+
+ # Notebook tab = label, content = vbox in scrolledwindow
+ w = self.parent.config_notebook
+ self.vbox = gtk.VBox(True, 5)
+ vp = gtk.Viewport()
+ vp.add(self.vbox)
+ sw = gtk.ScrolledWindow()
+ sw.add(vp) # ScrolledWindow → Viewport → VBox
+ # label
+ label = gtk.EventBox()
+ label.add(gtk.Label(" 📦 Add "))
+ label.show_all()
+ sw.show_all()
+ # add page
+ tab = w.insert_page_menu(sw, label, label, -1)
+
+ # Prepare some text
+ self.add_(uikit.label("\nInstall or update plugins", size=520, markup=1))
+ self.add_(uikit.label("You can update existing plugins, or install new contrib/ channels. User plugins reside in ~/.config/streamtuner2/plugins/ and can even be modified there (such as setting a custom # color: entry).\n", size=520, markup=1))
+ self.add_(self.button("Refresh", stock="gtk-refresh", cb=self.refresh), "Show available plugins from repository\nhttp://fossil.include-once.org/streamtuner2/")
+ self.add_(gtk.image_new_from_stock("gtk-info", gtk.ICON_SIZE_LARGE_TOOLBAR), "While plugins are generally compatible across releases, newer versions may also require to update the streamtuner2 core setup.\n Please note that plugin installation is rather experimental. It still requires a restart of ST2 to activate them.")
+ for i in range(1,10):
+ self.add_(uikit.label(""))
+
+
+ # Append to vbox
+ def add_(self, w, label=None, markup=0):
+ w = uikit.wrap(w=w, label=label, align=10, label_size=400, label_markup=1)
+ self.vbox.add(w)
+
+ # Create button, connect click signal
+ def button(self, label, stock=None, cb=None):
+ b = gtk.Button(label, stock=stock)
+ b.connect("clicked", cb)
+ return b
+
+
+ # Add plugin list
+ def refresh(self, *w):
+
+ # fetch plugins
+ meta = []
+ for url in re.split("[\s,]+", conf.plugin_repos.strip()):
+ if re.match("https?://", url):
+ d = ahttp.get(url, encoding='utf-8') or []
+ meta += json.loads(d)
+ self.parent.status()
+
+ # clean up vbox
+ vbox = self.vbox
+ for i,c in enumerate(vbox.get_children()):
+ if i>=3:
+ vbox.remove(c)
+
+ # query existing plugins
+ have = {name: plugin_meta(module=name) for name in module_list()}
+ # add plugins
+ for p in meta:
+ id = p.get("$name")
+ if id.find("__") == 0: # skip __init__.py
+ continue
+ if have.get(id):
+ if p.get("version") == have[id]["version"]:
+ continue;
+ self.add_plugin(p)
+ # some filler
+ for i in range(1,3):
+ self.add_(uikit.label(""))
+
+
+ # Entry for plugin list
+ def add_plugin(self, p):
+ b = self.button("Install", stock="gtk-save", cb=lambda *w:self.install(p))
+ text = "{title} {version}\n{description}\n{type}/{category}".format(**p)
+ self.add_(b, text, markup=1)
+
+
+ # Download a plugin
+ def install(self, p):
+ src = ahttp.get(p["$file"], encoding="utf-8")
+ with open("{}/{$name}.py".format(conf.plugin_dir, **p), "w") as f:
+ f.write(src)
+ self.parent.status("Plugin '{$name}.py' installed.".format(**p))
+
+
+ # Activate/deactivate changed plugins
+ def activate_plugins(self, *w):
+ pass
+
+
+