[CCode (has_target = false)]
private delegate void ModuleFinalizeFunc (BackendStore store);
+ /* this contains all backends, regardless of enabled or prepared state */
private HashMap<string,Backend> backend_hash;
private HashMap<string,Backend> _prepared_backends;
private File config_file;
private GLib.KeyFile backends_key_file;
- private GLib.List<ModuleFinalizeFunc> finalize_funcs = null;
+ private HashMap<string,Module> modules;
private static weak BackendStore instance;
- private static bool _backends_loaded = false;
private bool _is_prepared = false;
/**
/* Treat this as a library init function */
Debug.set_flags (Environment.get_variable ("FOLKS_DEBUG"));
+ this.modules = new HashMap<string,unowned Module> (str_hash, str_equal);
this.backend_hash = new HashMap<string,Backend> (str_hash, str_equal);
this._prepared_backends = new HashMap<string,Backend> (str_hash,
str_equal);
~BackendStore ()
{
- /* Finalize all the loaded modules */
- foreach (ModuleFinalizeFunc func in this.finalize_funcs)
- func (this);
-
- /* reset status of backends */
- lock (this._backends_loaded)
- this._backends_loaded = false;
+ /* Finalize all the loaded modules that have finalize functions */
+ foreach (var entry in this.modules)
+ {
+ unowned Module module = entry.value;
+ void* func;
+ if (module.symbol ("module_finalize", out func))
+ {
+ ModuleFinalizeFunc module_finalize = (ModuleFinalizeFunc) func;
+ module_finalize (this);
+ }
+ }
/* manually clear the singleton instance */
instance = null;
*/
public async void prepare ()
{
+ /* (re-)load the list of disabled backends */
+ yield this.load_disabled_backend_names ();
+
if (this._is_prepared == true)
return;
this._is_prepared = true;
- /* Load the list of disabled backends */
- yield this.load_disabled_backend_names ();
-
this.notify_property ("is-prepared");
}
*/
public async void load_backends () throws GLib.Error
{
- lock (this._backends_loaded)
+ assert (Module.supported());
+
+ yield this.prepare ();
+
+ /* unload backends that have been disabled since they were loaded */
+ foreach (var backend_existing in this.backend_hash.values)
{
- if (!this._backends_loaded)
- {
- assert (Module.supported());
+ yield this.backend_unload_if_needed (backend_existing);
+ }
+
+ var path = Environment.get_variable ("FOLKS_BACKEND_DIR");
+ if (path == null)
+ {
+ path = BuildConf.BACKEND_DIR;
- if (this._is_prepared == false)
- yield this.prepare ();
+ debug ("Using built-in backend dir '%s' (override with " +
+ "environment variable FOLKS_BACKEND_DIR)", path);
+ }
+ else
+ {
+ debug ("Using environment variable FOLKS_BACKEND_DIR = " +
+ "'%s' to look for backends", path);
+ }
- var path = Environment.get_variable ("FOLKS_BACKEND_DIR");
- if (path == null)
- {
- path = BuildConf.BACKEND_DIR;
+ File dir = File.new_for_path (path);
+ assert (dir != null && yield is_dir (dir));
- debug ("Using built-in backend dir '%s' (override with " +
- "environment variable FOLKS_BACKEND_DIR)", path);
- }
- else
+ var modules = yield this.get_modules_from_dir (dir);
+
+ /* this will load any new modules found in the backends dir and will
+ * prepare and unprepare backends such that they match the state in the
+ * backend store key file */
+ foreach (var mod_entry in modules)
+ {
+ var module = (File) mod_entry.value;
+ this.load_module_from_file (module);
+ }
+
+ /* this is populated indirectly from load_module_from_file(), above */
+ foreach (var backend in this.backend_hash.values)
+ {
+ yield this.backend_load_if_needed (backend);
+ }
+ }
+
+ private async void backend_load_if_needed (Backend backend)
+ {
+ if (this.backend_is_enabled (backend.name))
+ {
+ try
+ {
+ if (!this._prepared_backends.has_key (backend.name))
{
- debug ("Using environment variable FOLKS_BACKEND_DIR = " +
- "'%s' to look for backends", path);
+ yield backend.prepare ();
+
+ debug ("New backend '%s' prepared", backend.name);
+ this._prepared_backends.set (backend.name, backend);
+ this.backend_available (backend);
}
+ }
+ catch (GLib.Error e)
+ {
+ warning ("Error preparing Backend '%s': %s", backend.name,
+ e.message);
+ }
+ }
+ }
- File dir = File.new_for_path (path);
- assert (dir != null && yield is_dir (dir));
+ private async bool backend_unload_if_needed (Backend backend)
+ {
+ bool unloaded = false;
- var modules = yield this.get_modules_from_dir (dir);
- foreach (var entry in modules)
+ if (!this.backend_is_enabled (backend.name))
+ {
+ var backend_existing = this.backend_hash.get (backend.name);
+ if (backend_existing != null)
+ {
+ try
{
- var module = (File) entry.value;
- this.load_module_from_file (module);
+ yield backend_existing.unprepare ();
}
-
- /* this is populated indirectly from load_module_from_file(),
- * above */
- foreach (var backend in this.backend_hash.values)
+ catch (GLib.Error e)
{
- try
- {
- yield backend.prepare ();
-
- debug ("New backend '%s' prepared", backend.name);
- this._prepared_backends.set (backend.name, backend);
- this.backend_available (backend);
- }
- catch (GLib.Error e)
- {
- warning ("Error preparing Backend '%s': %s", backend.name,
- e.message);
- }
+ warning ("Error unpreparing Backend '%s': %s", backend.name,
+ e.message);
}
- this._backends_loaded = true;
+ this._prepared_backends.unset (backend_existing.name);
+
+ unloaded = true;
}
}
+
+ return unloaded;
}
/**
*/
public void add_backend (Backend backend)
{
- /* Check the backend isn't disabled */
+ /* Purge any other backend with the same name; re-add if enabled */
+ var backend_existing = this.backend_hash.get (backend.name);
+ if (backend_existing != null && backend_existing != backend)
+ {
+ backend_existing.unprepare ();
+ this._prepared_backends.unset (backend_existing.name);
+ }
+
+ this.backend_hash.set (backend.name, backend);
+ }
+
+ private bool backend_is_enabled (string name)
+ {
bool enabled = true;
try
{
- enabled = this.backends_key_file.get_boolean (backend.name,
- "enabled");
+ enabled = this.backends_key_file.get_boolean (name, "enabled");
}
catch (KeyFileError e)
{
{
warning ("Couldn't check enabled state of backend '%s': %s\n" +
"Disabling backend.",
- backend.name, e.message);
+ name, e.message);
enabled = false;
}
}
- if (enabled == true)
- this.backend_hash.set (backend.name, backend);
+ return enabled;
}
/**
{
string file_path = file.get_path ();
+ if (this.modules.has_key (file_path))
+ return;
+
Module module = Module.open (file_path, ModuleFlags.BIND_LOCAL);
if (module == null)
{
void* function;
/* this causes the module to call add_backend() for its backends (adding
- * them to the backend hash) */
+ * them to the backend hash); any backends that already existed will be
+ * removed if they've since been disabled */
if (!module.symbol("module_init", out function))
{
warning ("Failed to find entry point function '%s' in '%s': %s",
ModuleInitFunc module_init = (ModuleInitFunc) function;
assert (module_init != null);
- /* It's optional for modules to have a finalize function */
- if (module.symbol ("module_finalize", out function))
- {
- ModuleFinalizeFunc module_finalize = (ModuleFinalizeFunc) function;
- this.finalize_funcs.prepend (module_finalize);
- }
+ this.modules.set (file_path, module);
/* We don't want our modules to ever unload */
module.make_resident ();
this.add_test ("load and prep", this.test_load_and_prep);
this.add_test ("disabling", this.test_disabling);
+ this.add_test ("reloading", this.test_reloading);
}
public override void set_up ()
GLib.error ("Failed to load backends: %s", e.message);
}
}
+
+ public void test_reloading ()
+ {
+ this.main_loop = new GLib.MainLoop (null, false);
+
+ var store = BackendStore.dup ();
+ this.test_reloading_async (store, (o, r) =>
+ {
+ this.test_reloading_async.end (r);
+ });
+
+ main_loop.run ();
+ }
+
+ private async void test_reloading_async (BackendStore store)
+ {
+ HashSet<string> backends_expected;
+
+ /*
+ * First loading
+ */
+ backends_expected = new HashSet<string> (str_hash, str_equal);
+ backends_expected.add ("key-file");
+ backends_expected.add ("telepathy");
+
+ try
+ {
+ yield store.load_backends ();
+
+ store.enabled_backends.foreach ((i) =>
+ {
+ var backend = (Backend) i;
+ assert (backends_expected.contains (backend.name));
+ backends_expected.remove (backend.name);
+ });
+
+ assert (backends_expected.size == 0);
+ }
+ catch (GLib.Error e1)
+ {
+ GLib.error ("Failed to load backends: %s", e1.message);
+ }
+
+ /*
+ * Second loading: late disabling
+ */
+ backends_expected = new HashSet<string> (str_hash, str_equal);
+ backends_expected.add ("telepathy");
+
+ /* Disable some backends */
+ yield store.disable_backend ("key-file");
+
+ /* this time we should get (all - key-file) */
+ try
+ {
+ yield store.load_backends ();
+
+ store.enabled_backends.foreach ((i) =>
+ {
+ var backend = (Backend) i;
+ assert (backends_expected.contains (backend.name));
+ backends_expected.remove (backend.name);
+ });
+
+ assert (backends_expected.size == 0);
+ }
+ catch (GLib.Error e2)
+ {
+ GLib.error ("Failed to load backends: %s", e2.message);
+ }
+
+ /*
+ * Third loading: late enabling
+ */
+ backends_expected = new HashSet<string> (str_hash, str_equal);
+ backends_expected.add ("key-file");
+ backends_expected.add ("telepathy");
+
+ /* Re-enable some backends */
+ yield store.enable_backend ("key-file");
+
+ /* this time we should get them all */
+ try
+ {
+ yield store.load_backends ();
+
+ store.enabled_backends.foreach ((i) =>
+ {
+ var backend = (Backend) i;
+ assert (backends_expected.contains (backend.name));
+ backends_expected.remove (backend.name);
+ });
+
+ assert (backends_expected.size == 0);
+ }
+ catch (GLib.Error e3)
+ {
+ GLib.error ("Failed to load backends: %s", e3.message);
+ }
+
+ /*
+ * Fourth loading: idempotency
+ */
+
+ backends_expected = new HashSet<string> (str_hash, str_equal);
+ backends_expected.add ("key-file");
+ backends_expected.add ("telepathy");
+
+ /* this time we should get them all */
+ try
+ {
+ yield store.load_backends ();
+
+ store.enabled_backends.foreach ((i) =>
+ {
+ var backend = (Backend) i;
+
+ assert (backends_expected.contains (backend.name));
+ backends_expected.remove (backend.name);
+ });
+
+ assert (backends_expected.size == 0);
+ }
+ catch (GLib.Error e4)
+ {
+ GLib.error ("Failed to load backends: %s", e4.message);
+ }
+
+ this.main_loop.quit ();
+ }
}
public int main (string[] args)