[folks] Make BackendStore.load_backends() unload disabled backends.



commit 037df05b1a069d484ff84de2aa5f86b3da259f1f
Author: Travis Reitter <travis reitter collabora co uk>
Date:   Tue Nov 2 09:53:29 2010 -0700

    Make BackendStore.load_backends() unload disabled backends.
    
    This also allows this function to be called multiple times (previously,
    subsequent calls were ignored).
    
    Fixes bgo#629081.

 NEWS                               |    8 ++
 backends/key-file/kf-backend.vala  |   16 +++
 backends/telepathy/tp-backend.vala |   27 ++++++
 folks/backend-store.vala           |  183 +++++++++++++++++++++++-------------
 folks/backend.vala                 |   16 +++
 tests/folks/backend-loading.vala   |  131 ++++++++++++++++++++++++++
 6 files changed, 316 insertions(+), 65 deletions(-)
---
diff --git a/NEWS b/NEWS
index faa4af4..6d558be 100644
--- a/NEWS
+++ b/NEWS
@@ -1,10 +1,18 @@
 Overview of changes from libfolks 0.3.1 to libfolks 0.3.2
 ==========================================================
 
+Major changes:
+* BackendStore.load_backends() now (un)loads backends which have been
+  (dis|en)abled since the last call
+
 API changes:
 * Added BackendStore.prepare() and BackendStore::is-prepared
 * Add BackendStore.enable_backend().
 * Add BackendStore.disable_backend().
+* Add BackendStore.unprepare().
+
+Bugs fixed:
+* Bug 629081 â?? Add API to allow specific backends to be disabled
 
 Overview of changes from libfolks 0.3.0 to libfolks 0.3.1
 ==========================================================
diff --git a/backends/key-file/kf-backend.vala b/backends/key-file/kf-backend.vala
index b36c4f4..32ca991 100644
--- a/backends/key-file/kf-backend.vala
+++ b/backends/key-file/kf-backend.vala
@@ -110,6 +110,22 @@ public class Folks.Backends.Kf.Backend : Folks.Backend
         }
     }
 
+  /**
+   * { inheritDoc}
+   */
+  public override async void unprepare () throws GLib.Error
+    {
+      this.persona_stores.foreach ((k, v) =>
+        {
+          this.persona_store_removed ((PersonaStore) v);
+        });
+
+      this.persona_stores.remove_all ();
+
+      this._is_prepared = false;
+      this.notify_property ("is-prepared");
+    }
+
   private void store_removed_cb (Folks.PersonaStore store)
     {
       this.persona_store_removed (store);
diff --git a/backends/telepathy/tp-backend.vala b/backends/telepathy/tp-backend.vala
index edc8df8..81ef4ab 100644
--- a/backends/telepathy/tp-backend.vala
+++ b/backends/telepathy/tp-backend.vala
@@ -98,6 +98,33 @@ public class Folks.Backends.Tp.Backend : Folks.Backend
         }
     }
 
+  /**
+   * { inheritDoc}
+   */
+  public override async void unprepare () throws GLib.Error
+    {
+      this.account_manager.account_enabled.disconnect (this.account_enabled_cb);
+      this.account_manager.account_validity_changed.disconnect (
+          this.account_validity_changed_cb);
+      this.account_manager = null;
+
+      this.persona_stores.foreach ((k, v) =>
+        {
+          this.persona_store_removed ((PersonaStore) v);
+        });
+
+      this.persona_stores.remove_all ();
+
+      this._is_prepared = false;
+      this.notify_property ("is-prepared");
+    }
+
+  private void account_validity_changed_cb (Account account, bool valid)
+    {
+      if (valid)
+        this.account_enabled_cb (account);
+    }
+
   private void account_enabled_cb (Account account)
     {
       PersonaStore store = this.persona_stores.lookup (
diff --git a/folks/backend-store.vala b/folks/backend-store.vala
index 3649c9a..9e87510 100644
--- a/folks/backend-store.vala
+++ b/folks/backend-store.vala
@@ -39,13 +39,13 @@ public class Folks.BackendStore : Object {
   [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;
 
   /**
@@ -121,6 +121,7 @@ public class Folks.BackendStore : Object {
       /* 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);
@@ -128,13 +129,17 @@ public class Folks.BackendStore : Object {
 
   ~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;
@@ -151,13 +156,13 @@ public class Folks.BackendStore : Object {
    */
   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");
     }
 
@@ -170,61 +175,100 @@ public class Folks.BackendStore : Object {
    */
   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;
     }
 
   /**
@@ -234,12 +278,23 @@ public class Folks.BackendStore : Object {
    */
   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)
         {
@@ -248,13 +303,12 @@ public class Folks.BackendStore : Object {
             {
               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;
     }
 
   /**
@@ -378,6 +432,9 @@ public class Folks.BackendStore : Object {
     {
       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)
         {
@@ -390,7 +447,8 @@ public class Folks.BackendStore : Object {
       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",
@@ -404,12 +462,7 @@ public class Folks.BackendStore : Object {
       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 ();
diff --git a/folks/backend.vala b/folks/backend.vala
index 696a00c..5b73602 100644
--- a/folks/backend.vala
+++ b/folks/backend.vala
@@ -105,6 +105,22 @@ public abstract class Folks.Backend : Object
    */
   public abstract async void prepare () throws GLib.Error;
 
+  /**
+   * Revert the Backend to its pre-prepared state.
+   *
+   * This will disconnect this Backend and its dependencies from their
+   * respective services and the Backend will issue
+   * { link Backend.persona_store_removed} for each of its
+   * { link PersonaStore}s.
+   *
+   * Most users won't need to use this function.
+   *
+   * If this function throws an error, the Backend will not be functional.
+   *
+   * @since 0.3.2
+   */
+  public abstract async void unprepare () throws GLib.Error;
+
   construct
     {
       this.persona_stores = new HashTable<string, PersonaStore> (str_hash,
diff --git a/tests/folks/backend-loading.vala b/tests/folks/backend-loading.vala
index bcea573..e666753 100644
--- a/tests/folks/backend-loading.vala
+++ b/tests/folks/backend-loading.vala
@@ -20,6 +20,7 @@ public class BackendLoadingTests : Folks.TestCase
 
       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 ()
@@ -119,6 +120,136 @@ public class BackendLoadingTests : Folks.TestCase
           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)



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]