diff options
author | Jörg Frings-Fürst <debian@jff-webhosting.net> | 2014-07-23 09:06:59 +0200 |
---|---|---|
committer | Jörg Frings-Fürst <debian@jff-webhosting.net> | 2014-07-23 09:06:59 +0200 |
commit | 4ea2cc3bd4a7d9b1c54a9d33e6a1cf82e7c8c21d (patch) | |
tree | d2e54377d14d604356c86862a326f64ae64dadd6 /src/plugins/Plugins.vala |
Imported Upstream version 0.18.1upstream/0.18.1
Diffstat (limited to 'src/plugins/Plugins.vala')
-rw-r--r-- | src/plugins/Plugins.vala | 436 |
1 files changed, 436 insertions, 0 deletions
diff --git a/src/plugins/Plugins.vala b/src/plugins/Plugins.vala new file mode 100644 index 0000000..d0f9185 --- /dev/null +++ b/src/plugins/Plugins.vala @@ -0,0 +1,436 @@ +/* Copyright 2011-2014 Yorba Foundation + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +namespace Plugins { + +// GModule doesn't have a truly generic way to determine if a file is a shared library by extension, +// so these are hard-coded +private const string[] SHARED_LIB_EXTS = { "so", "la" }; + +// Although not expecting this system to last very long, these ranges declare what versions of this +// interface are supported by the current implementation. +private const int MIN_SPIT_INTERFACE = 0; +private const int MAX_SPIT_INTERFACE = 0; + +public class ExtensionPoint { + public GLib.Type pluggable_type { get; private set; } + // name is user-visible + public string name { get; private set; } + public string? icon_name { get; private set; } + public string[]? core_ids { get; private set; } + + public ExtensionPoint(Type pluggable_type, string name, string? icon_name, string[]? core_ids) { + this.pluggable_type = pluggable_type; + this.name = name; + this.icon_name = icon_name; + this.core_ids = core_ids; + } +} + +private class ModuleRep { + public File file; + public Module? module; + public Spit.Module? spit_module = null; + public int spit_interface = Spit.UNSUPPORTED_INTERFACE; + public string? id = null; + + private ModuleRep(File file) { + this.file = file; + + module = Module.open(file.get_path(), ModuleFlags.BIND_LAZY); + } + + ~ModuleRep() { + // ensure that the Spit.Module is destroyed before the GLib.Module + spit_module = null; + } + + // Have to use this funky static factory because GModule is a compact class and has no copy + // constructor. The handle must be kept open for the lifetime of the application (or until + // the module is ready to be discarded), as dropping the reference will unload the binary. + public static ModuleRep? open(File file) { + ModuleRep module_rep = new ModuleRep(file); + + return (module_rep.module != null) ? module_rep : null; + } +} + +private class PluggableRep { + public Spit.Pluggable pluggable { get; private set; } + public string id { get; private set; } + public bool is_core { get; private set; default = false; } + public bool activated { get; private set; default = false; } + + private bool enabled = false; + + // Note that creating a PluggableRep does not activate it. + public PluggableRep(Spit.Pluggable pluggable) { + this.pluggable = pluggable; + id = pluggable.get_id(); + } + + public void activate() { + // determine if a core pluggable (which is only known after all the extension points + // register themselves) + is_core = is_core_pluggable(pluggable); + + FuzzyPropertyState saved_state = Config.Facade.get_instance().is_plugin_enabled(id); + enabled = ((is_core && (saved_state != FuzzyPropertyState.DISABLED)) || + (!is_core && (saved_state == FuzzyPropertyState.ENABLED))); + + // inform the plugin of its activation state + pluggable.activation(enabled); + + activated = true; + } + + public bool is_enabled() { + return enabled; + } + + // Returns true if value changed, false otherwise + public bool set_enabled(bool enabled) { + if (enabled == this.enabled) + return false; + + this.enabled = enabled; + Config.Facade.get_instance().set_plugin_enabled(id, enabled); + pluggable.activation(enabled); + + return true; + } +} + +private File[] search_dirs; +private Gee.HashMap<string, ModuleRep> module_table; +private Gee.HashMap<string, PluggableRep> pluggable_table; +private Gee.HashMap<Type, ExtensionPoint> extension_points; +private Gee.HashSet<string> core_ids; + +public void init() throws Error { + search_dirs = new File[0]; + search_dirs += AppDirs.get_user_plugins_dir(); + search_dirs += AppDirs.get_system_plugins_dir(); + + module_table = new Gee.HashMap<string, ModuleRep>(); + pluggable_table = new Gee.HashMap<string, PluggableRep>(); + extension_points = new Gee.HashMap<Type, ExtensionPoint>(); + core_ids = new Gee.HashSet<string>(); + + // do this after constructing member variables so accessors don't blow up if GModule isn't + // supported + if (!Module.supported()) { + warning("Plugins not support: GModule not supported on this platform."); + + return; + } + + foreach (File dir in search_dirs) { + try { + search_for_plugins(dir); + } catch (Error err) { + debug("Unable to search directory %s for plugins: %s", dir.get_path(), err.message); + } + } +} + +public void terminate() { + search_dirs = null; + pluggable_table = null; + module_table = null; + extension_points = null; + core_ids = null; +} + +public class Notifier { + private static Notifier? instance = null; + + public signal void pluggable_activation(Spit.Pluggable pluggable, bool enabled); + + private Notifier() { + } + + public static Notifier get_instance() { + if (instance == null) + instance = new Notifier(); + + return instance; + } +} + +public void register_extension_point(Type type, string name, string? icon_name, string[]? core_ids) { + // if this assertion triggers, it means this extension point has already registered + assert(!extension_points.has_key(type)); + + extension_points.set(type, new ExtensionPoint(type, name, icon_name, core_ids)); + + // add core IDs to master list + if (core_ids != null) { + foreach (string core_id in core_ids) + Plugins.core_ids.add(core_id); + } + + // activate all the pluggables for this extension point + foreach (PluggableRep pluggable_rep in pluggable_table.values) { + if (!pluggable_rep.pluggable.get_type().is_a(type)) + continue; + + pluggable_rep.activate(); + Notifier.get_instance().pluggable_activation(pluggable_rep.pluggable, pluggable_rep.is_enabled()); + } +} + +public Gee.Collection<Spit.Pluggable> get_pluggables(bool include_disabled = false) { + Gee.Collection<Spit.Pluggable> all = new Gee.HashSet<Spit.Pluggable>(); + foreach (PluggableRep pluggable_rep in pluggable_table.values) { + if (pluggable_rep.activated && (include_disabled || pluggable_rep.is_enabled())) + all.add(pluggable_rep.pluggable); + } + + return all; +} + +public bool is_core_pluggable(Spit.Pluggable pluggable) { + return core_ids.contains(pluggable.get_id()); +} + +private ModuleRep? get_module_for_pluggable(Spit.Pluggable needle) { + foreach (ModuleRep module_rep in module_table.values) { + Spit.Pluggable[]? pluggables = module_rep.spit_module.get_pluggables(); + if (pluggables != null) { + foreach (Spit.Pluggable pluggable in pluggables) { + if (pluggable == needle) + return module_rep; + } + } + } + + return null; +} + +public string? get_pluggable_module_id(Spit.Pluggable needle) { + ModuleRep? module_rep = get_module_for_pluggable(needle); + + return (module_rep != null) ? module_rep.spit_module.get_id() : null; +} + +public Gee.Collection<ExtensionPoint> get_extension_points(owned CompareDataFunc? compare_func = null) { + Gee.Collection<ExtensionPoint> sorted = new Gee.TreeSet<ExtensionPoint>((owned) compare_func); + sorted.add_all(extension_points.values); + + return sorted; +} + +public Gee.Collection<Spit.Pluggable> get_pluggables_for_type(Type type, + owned CompareDataFunc? compare_func = null, bool include_disabled = false) { + // if this triggers it means the extension point didn't register itself at init() time + assert(extension_points.has_key(type)); + + Gee.Collection<Spit.Pluggable> for_type = new Gee.TreeSet<Spit.Pluggable>((owned) compare_func); + foreach (PluggableRep pluggable_rep in pluggable_table.values) { + if (pluggable_rep.activated + && pluggable_rep.pluggable.get_type().is_a(type) + && (include_disabled || pluggable_rep.is_enabled())) { + for_type.add(pluggable_rep.pluggable); + } + } + + return for_type; +} + +public string? get_pluggable_name(string id) { + PluggableRep? pluggable_rep = pluggable_table.get(id); + + return (pluggable_rep != null && pluggable_rep.activated) + ? pluggable_rep.pluggable.get_pluggable_name() : null; +} + +public bool get_pluggable_info(string id, ref Spit.PluggableInfo info) { + PluggableRep? pluggable_rep = pluggable_table.get(id); + if (pluggable_rep == null || !pluggable_rep.activated) + return false; + + pluggable_rep.pluggable.get_info(ref info); + + return true; +} + +public bool get_pluggable_enabled(string id, out bool enabled) { + PluggableRep? pluggable_rep = pluggable_table.get(id); + if (pluggable_rep == null || !pluggable_rep.activated) { + enabled = false; + + return false; + } + + enabled = pluggable_rep.is_enabled(); + + return true; +} + +public void set_pluggable_enabled(string id, bool enabled) { + PluggableRep? pluggable_rep = pluggable_table.get(id); + if (pluggable_rep == null || !pluggable_rep.activated) + return; + + if (pluggable_rep.set_enabled(enabled)) + Notifier.get_instance().pluggable_activation(pluggable_rep.pluggable, enabled); +} + +public File get_pluggable_module_file(Spit.Pluggable pluggable) { + ModuleRep? module_rep = get_module_for_pluggable(pluggable); + + return (module_rep != null) ? module_rep.file : null; +} + +public int compare_pluggable_names(void *a, void *b) { + Spit.Pluggable *apluggable = (Spit.Pluggable *) a; + Spit.Pluggable *bpluggable = (Spit.Pluggable *) b; + + return apluggable->get_pluggable_name().collate(bpluggable->get_pluggable_name()); +} + +public int compare_extension_point_names(void *a, void *b) { + ExtensionPoint *apoint = (ExtensionPoint *) a; + ExtensionPoint *bpoint = (ExtensionPoint *) b; + + return apoint->name.collate(bpoint->name); +} + +private bool is_shared_library(File file) { + string name, ext; + disassemble_filename(file.get_basename(), out name, out ext); + + foreach (string shared_ext in SHARED_LIB_EXTS) { + if (ext == shared_ext) + return true; + } + + return false; +} + +private void search_for_plugins(File dir) throws Error { + debug("Searching %s for plugins ...", dir.get_path()); + + // build a set of module names sans file extension ... this is to deal with the question of + // .so vs. .la existing in the same directory (and letting GModule deal with the problem) + FileEnumerator enumerator = dir.enumerate_children(Util.FILE_ATTRIBUTES, + FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null); + for (;;) { + FileInfo? info = enumerator.next_file(null); + if (info == null) + break; + + if (info.get_is_hidden()) + continue; + + File file = dir.get_child(info.get_name()); + + switch (info.get_file_type()) { + case FileType.DIRECTORY: + try { + search_for_plugins(file); + } catch (Error err) { + warning("Unable to search directory %s for plugins: %s", file.get_path(), err.message); + } + break; + + case FileType.REGULAR: + if (is_shared_library(file)) + load_module(file); + break; + + default: + // ignored + break; + } + } +} + +private void load_module(File file) { + ModuleRep? module_rep = ModuleRep.open(file); + if (module_rep == null) { + critical("Unable to load module %s: %s", file.get_path(), Module.error()); + + return; + } + + // look for the well-known entry point + void *entry; + if (!module_rep.module.symbol(Spit.ENTRY_POINT_NAME, out entry)) { + critical("Unable to load module %s: well-known entry point %s not found", file.get_path(), + Spit.ENTRY_POINT_NAME); + + return; + } + + Spit.EntryPoint spit_entry_point = (Spit.EntryPoint) entry; + + assert(MIN_SPIT_INTERFACE <= Spit.CURRENT_INTERFACE && Spit.CURRENT_INTERFACE <= MAX_SPIT_INTERFACE); + Spit.EntryPointParams params = Spit.EntryPointParams(); + params.host_min_spit_interface = MIN_SPIT_INTERFACE; + params.host_max_spit_interface = MAX_SPIT_INTERFACE; + params.module_spit_interface = Spit.UNSUPPORTED_INTERFACE; + params.module_file = file; + + module_rep.spit_module = spit_entry_point(¶ms); + if (params.module_spit_interface == Spit.UNSUPPORTED_INTERFACE) { + critical("Unable to load module %s: module reports no support for SPIT interfaces %d to %d", + file.get_path(), MIN_SPIT_INTERFACE, MAX_SPIT_INTERFACE); + + return; + } + + if (params.module_spit_interface < MIN_SPIT_INTERFACE || params.module_spit_interface > MAX_SPIT_INTERFACE) { + critical("Unable to load module %s: module reports unsupported SPIT version %d (out of range %d to %d)", + file.get_path(), module_rep.spit_interface, MIN_SPIT_INTERFACE, MAX_SPIT_INTERFACE); + + return; + } + + module_rep.spit_interface = params.module_spit_interface; + + // verify type (as best as possible; still potential to segfault inside GType here) + if (!(module_rep.spit_module is Spit.Module)) + module_rep.spit_module = null; + + if (module_rep.spit_module == null) { + critical("Unable to load module %s (SPIT %d): no spit module returned", file.get_path(), + module_rep.spit_interface); + + return; + } + + // if module has already been loaded, drop this one (search path is set up to load user-installed + // binaries prior to system binaries) + module_rep.id = prepare_input_text(module_rep.spit_module.get_id(), PrepareInputTextOptions.DEFAULT, -1); + if (module_rep.id == null) { + critical("Unable to load module %s (SPIT %d): invalid or empty module name", + file.get_path(), module_rep.spit_interface); + + return; + } + + if (module_table.has_key(module_rep.id)) { + critical("Not loading module %s (SPIT %d): module with name \"%s\" already loaded", + file.get_path(), module_rep.spit_interface, module_rep.id); + + return; + } + + debug("Loaded SPIT module \"%s %s\" (%s) [%s]", module_rep.spit_module.get_module_name(), + module_rep.spit_module.get_version(), module_rep.id, file.get_path()); + + // stash in module table by their ID + module_table.set(module_rep.id, module_rep); + + // stash pluggables in pluggable table by their ID + foreach (Spit.Pluggable pluggable in module_rep.spit_module.get_pluggables()) + pluggable_table.set(pluggable.get_id(), new PluggableRep(pluggable)); +} + +} + |