=?utf-8?q?=5Bfolks=5D_Bug_665376_=E2=80=94_Add_API_to_get_a_TpfPersona_fr?= =?utf-8?q?om_a_TpContact?=



commit 0487df55a28c7fdf8670354f15c311b5c605ac3c
Author: Philip Withnall <philip tecnocode co uk>
Date:   Tue Dec 6 15:39:25 2011 +0000

    Bug 665376 â Add API to get a TpfPersona from a TpContact
    
    Add static functions to quickly look up Tpf.PersonaStores and Tpf.Personas
    from Tp.Accounts and Tp.Contacts (respectively).
    
    Closes: bgo#665376

 NEWS                                              |    2 +
 backends/telepathy/lib/tpf-persona-store.vala     |  223 +++++++++++++++++++++
 backends/telepathy/lib/tpf-persona.vala           |   71 +++++++-
 backends/telepathy/tp-backend.vala                |   31 +---
 tests/lib/telepathy/contactlist/account-manager.c |    2 +
 tests/lib/telepathy/contactlist/backend.c         |    3 +
 6 files changed, 304 insertions(+), 28 deletions(-)
---
diff --git a/NEWS b/NEWS
index 17e0d0a..1db5861 100644
--- a/NEWS
+++ b/NEWS
@@ -9,6 +9,7 @@ Bugs fixed:
 * Bug 665728 â TpfPersonaStore: prepare() isn't mutually exclusive inside a
   single thread
 * Bug 665692 â Use constructors correctly
+* Bug 665376 â Add API to get a TpfPersona from a TpContact
 
 API changes:
 * Add Edsf.PersonaStore.source
@@ -26,6 +27,7 @@ API changes:
   normal setter)
 * Add ObjectCache.type_id
 * Add ObjectCache.id
+* Add Tpf.PersonaStore.dup_for_account() and Tpf.Persona.dup_for_contact()
 
 Overview of changes from libfolks 0.6.4.1 to libfolks 0.6.5
 =============================================================
diff --git a/backends/telepathy/lib/tpf-persona-store.vala b/backends/telepathy/lib/tpf-persona-store.vala
index d5c2dde..a311ec6 100644
--- a/backends/telepathy/lib/tpf-persona-store.vala
+++ b/backends/telepathy/lib/tpf-persona-store.vala
@@ -78,6 +78,7 @@ public class Tpf.PersonaStore : Folks.PersonaStore
   private HashSet<Persona> _persona_set;
   /* universal, contact owner handles (not channel-specific) */
   private HashMap<uint, Persona> _handle_persona_map;
+  private HashSet<Persona> _weakly_referenced_personas;
   private HashMap<Channel, HashSet<Persona>> _channel_group_personas_map;
   private HashMap<Channel, HashSet<uint>> _channel_group_incoming_adds;
   private HashMap<string, HashSet<Tpf.Persona>> _group_outgoing_adds;
@@ -260,6 +261,9 @@ public class Tpf.PersonaStore : Folks.PersonaStore
       this._debug = Debug.dup ();
       this._debug.print_status.connect (this._debug_print_status);
 
+      // Add to the map of persona stores by account
+      PersonaStore._add_store_to_map (this);
+
       // Set up the cache
       this._cache = new PersonaStoreCache (this);
 
@@ -270,6 +274,9 @@ public class Tpf.PersonaStore : Folks.PersonaStore
     {
       debug ("Destroying Tpf.PersonaStore %p ('%s').", this, this.id);
 
+      // Remove from the map of persona stores by account
+      PersonaStore._remove_store_from_map (this);
+
       this._debug.print_status.disconnect (this._debug_print_status);
       this._debug = null;
       if (this._logger != null)
@@ -510,6 +517,19 @@ public class Tpf.PersonaStore : Folks.PersonaStore
           this._conn = null;
         }
 
+      if (this._weakly_referenced_personas != null)
+        {
+          foreach (var p in this._weakly_referenced_personas)
+            {
+              if (p.contact != null)
+                {
+                  p.contact.weak_unref (this._contact_weak_notify_cb);
+                }
+            }
+        }
+
+      this._weakly_referenced_personas = new HashSet<Persona> ();
+
       this._handle_persona_map = new HashMap<uint, Persona> ();
       this._channel_group_personas_map =
           new HashMap<Channel, HashSet<Persona>> ();
@@ -1533,6 +1553,13 @@ public class Tpf.PersonaStore : Folks.PersonaStore
       if (persona == null)
         return;
 
+      /* If we hold a weak ref. on the persona's TpContact, release that. */
+      if (this._weakly_referenced_personas.remove (persona) == true &&
+          persona.contact != null)
+        {
+          persona.contact.weak_unref (this._contact_weak_notify_cb);
+        }
+
       /*
        * remove all persona-keyed entries
        */
@@ -1930,6 +1957,56 @@ public class Tpf.PersonaStore : Folks.PersonaStore
       return personas;
     }
 
+  private void _contact_weak_notify_cb (Object obj)
+    {
+      var c = obj as Contact;
+      this._ignore_by_handle (c.get_handle (), null, null,
+          GroupDetails.ChangeReason.NONE);
+    }
+
+  internal Tpf.Persona? _ensure_persona_from_contact (Contact contact)
+    {
+      uint handle = contact.get_handle ();
+
+      debug ("Ensuring contact %p (handle: %u) exists in Tpf.PersonaStore " +
+          "%p ('%s').", contact, handle, this, this.id);
+
+      if (handle == 0)
+        {
+          return null;
+        }
+
+      /* If the persona already exists, return them. */
+      var persona = this._handle_persona_map[handle];
+
+      if (persona != null)
+        {
+          return persona;
+        }
+
+      /* Otherwise, add the persona to the store. See bgo#665376 for details of
+       * why this is necessary. Since the TpContact is coming from a source
+       * other than the TpChannels which are associated with this store, we only
+       * hold a weak reference to it and remove it from the store as soon as
+       * it's destroyed. */
+      persona = this._add_persona_from_contact (contact, false);
+
+      if (persona == null)
+        {
+          return null;
+        }
+
+      /* Weak ref. on the contact. */
+      contact.weak_ref (this._contact_weak_notify_cb);
+      this._weakly_referenced_personas.add (persona);
+
+      /* Signal the addition of the new persona. */
+      var personas = new HashSet<Persona> ();
+      this._emit_personas_changed (personas, null);
+
+      return persona;
+    }
+
   private Tpf.Persona? _add_persona_from_contact (Contact contact,
       bool from_contact_list)
     {
@@ -2332,4 +2409,150 @@ public class Tpf.PersonaStore : Folks.PersonaStore
 
       return info_list;
     }
+
+  /* Must be locked before being accessed. A ref. is held on each PersonaStore,
+   * and they're only removed when they're finalised or their removed signal is
+   * emitted. The map as a whole is lazily constructed and destroyed according
+   * to when PersonaStores are constructed and destroyed. */
+  private static HashMap<string /* Account object path */, PersonaStore>
+      _persona_stores_by_account = null;
+  private static Map<string, PersonaStore> _persona_stores_by_account_ro = null;
+
+  /**
+   * Get a map of all the currently constructed { link Tpf.PersonaStore}s.
+   *
+   * If a { link BackendStore} has been prepared, this map will be complete,
+   * containing every store known to the Telepathy account manager. If no
+   * { link BackendStore} has been prepared, this map will only contain the
+   * stores which have been created by calling
+   * { link Tpf.PersonaStore.dup_for_account}.
+   *
+   * This map is read-only. Use { link BackendStore} or
+   * { link Tpf.PersonaStore.dup_for_account} to add stores.
+   *
+   * @return map from { link PersonaStore.id} to { link PersonaStore}
+   * @since UNRELEASED
+   */
+  public static unowned Map<string, PersonaStore> list_persona_stores ()
+    {
+      unowned Map<string, PersonaStore> store;
+
+      lock (PersonaStore._persona_stores_by_account)
+        {
+          if (PersonaStore._persona_stores_by_account == null)
+            {
+              PersonaStore._persona_stores_by_account =
+                  new HashMap<string, PersonaStore> ();
+              PersonaStore._persona_stores_by_account_ro =
+                  PersonaStore._persona_stores_by_account.read_only_view;
+            }
+
+          store = PersonaStore._persona_stores_by_account_ro;
+        }
+
+      return store;
+    }
+
+  private static void _store_removed_cb (Folks.PersonaStore store)
+    {
+      /* Remove the store from the map. */
+      PersonaStore._remove_store_from_map ((Tpf.PersonaStore) store);
+    }
+
+  private static void _add_store_to_map (PersonaStore store)
+    {
+      debug ("Adding PersonaStore %p ('%s') to map.", store, store.id);
+
+      lock (PersonaStore._persona_stores_by_account)
+        {
+          /* Lazy construction. */
+          if (PersonaStore._persona_stores_by_account == null)
+            {
+              PersonaStore._persona_stores_by_account =
+                  new HashMap<string, PersonaStore> ();
+              PersonaStore._persona_stores_by_account_ro =
+                  PersonaStore._persona_stores_by_account.read_only_view;
+            }
+
+          /* Bail if a store already exists for this account. */
+          assert (!PersonaStore._persona_stores_by_account.has_key (store.id));
+
+          /* Add the store. */
+          PersonaStore._persona_stores_by_account.set (store.id, store);
+          store.removed.connect (PersonaStore._store_removed_cb);
+        }
+    }
+
+  private static void _remove_store_from_map (PersonaStore store)
+    {
+      debug ("Removing PersonaStore %p ('%s') from map.", store, store.id);
+
+      lock (PersonaStore._persona_stores_by_account)
+        {
+          /* Bail if no store existed for this account. This can happen if the
+           * store emits its removed() signal (correctly) before being
+           * finalised; we remove the store from the map in both cases. */
+          if (PersonaStore._persona_stores_by_account == null ||
+              !PersonaStore._persona_stores_by_account.unset (store.id))
+            {
+              return;
+            }
+
+          store.removed.disconnect (PersonaStore._store_removed_cb);
+
+          /* Lazy destruction. */
+          if (PersonaStore._persona_stores_by_account.size == 0)
+            {
+              PersonaStore._persona_stores_by_account_ro = null;
+              PersonaStore._persona_stores_by_account = null;
+            }
+        }
+    }
+
+  /**
+   * Look up a { link Tpf.PersonaStore} by its { link TelepathyGLib.Account}.
+   *
+   * If found, a new reference to the persona store will be returned. If not
+   * found, a new { link Tpf.PersonaStore} will be created for the account.
+   *
+   * See the documentation for { link Tpf.PersonaStore.list_persona_stores} for
+   * information on the lifecycle of these stores when a { link BackendStore} is
+   * and is not present.
+   *
+   * @param account the Telepathy account of the persona store
+   * @return the persona store associated with the account
+   * @since UNRELEASED
+   */
+  public static PersonaStore dup_for_account (Account account)
+    {
+      PersonaStore? store = null;
+
+      debug ("Tpf.PersonaStore.dup_for_account (%p):", account);
+
+      lock (PersonaStore._persona_stores_by_account)
+        {
+          /* If the store already exists, return it. */
+          if (PersonaStore._persona_stores_by_account != null)
+            {
+              store =
+                  PersonaStore._persona_stores_by_account.get (
+                      account.get_object_path ());
+            }
+
+          /* Otherwise, we have to create it. It's added to the map in its
+           * constructor. */
+          if (store == null)
+            {
+              debug ("    Creating new PersonaStore.");
+              store = new PersonaStore (account);
+            }
+          else
+            {
+              debug ("    Found existing PersonaStore %p ('%s').", store,
+                  store.id);
+            }
+        }
+
+      return store;
+    }
 }
diff --git a/backends/telepathy/lib/tpf-persona.vala b/backends/telepathy/lib/tpf-persona.vala
index bfe1783..60b9c84 100644
--- a/backends/telepathy/lib/tpf-persona.vala
+++ b/backends/telepathy/lib/tpf-persona.vala
@@ -464,6 +464,16 @@ public class Tpf.Persona : Folks.Persona,
       this.notify_property ("groups");
     }
 
+  /* This has to be weak since, in general, we can't force any TpContacts to
+   * remain alive if we want to solve bgo#665376. */
+  private weak Contact? _contact = null;
+
+  private void _contact_weak_notify_cb (Object obj)
+    {
+      this._contact = null;
+      this.notify_property ("contact");
+    }
+
   /**
    * The Telepathy contact represented by this persona.
    *
@@ -473,7 +483,28 @@ public class Tpf.Persona : Folks.Persona,
    * are being retrieved from a cache and may not be current (though there's no
    * way to tell this).
    */
-  public Contact? contact { get; construct; }
+  public Contact? contact
+    {
+      get
+        {
+          if (this._contact == null)
+            {
+              return null;
+            }
+
+          return this._contact;
+        }
+
+      construct
+        {
+          if (value != null)
+            {
+              value.weak_ref (this._contact_weak_notify_cb);
+            }
+
+          this._contact = value;
+        }
+    }
 
   private HashSet<PhoneFieldDetails> _phone_numbers =
       new HashSet<PhoneFieldDetails> (
@@ -967,6 +998,11 @@ public class Tpf.Persona : Folks.Persona,
   ~Persona ()
     {
       debug ("Destroying Tpf.Persona '%s': %p", this.uid, this);
+
+      if (this._contact != null)
+        {
+          this._contact.weak_unref (this._contact_weak_notify_cb);
+        }
     }
 
   private static Account? _account_for_connection (Connection conn)
@@ -1046,4 +1082,37 @@ public class Tpf.Persona : Folks.Persona,
           this.notify_property ("avatar");
         }
     }
+
+  /**
+   * Look up a { link Tpf.Persona} by its { link TelepathyGLib.Contact}.
+   *
+   * If the { link TelepathyGLib.Account} for the contact's
+   * { link TelepathyGLib.Connection} is `null`, or if a
+   * { link Tpf.PersonaStore} can't be found for that account, `null` will be
+   * returned. Otherwise, if a { link Tpf.Persona} already exists for the given
+   * contact, that will be returned; if one doesn't exist a new one will be
+   * created and returned. In this case, the { link Tpf.Persona} will be added
+   * to the { link PersonaStore} associated with the account, and will be
+   * removed when `contact` is destroyed.
+   *
+   * @param contact the Telepathy contact of the persona
+   * @return the persona associated with the contact, or `null`
+   * @since UNRELEASED
+   */
+  public static Persona? dup_for_contact (Contact contact)
+    {
+      var account = contact.connection.get_account ();
+
+      debug ("Tpf.Persona.dup_for_contact (%p): got account %p", contact,
+          account);
+
+      /* Account could be null; see the docs for tp_connection_get_account(). */
+      if (account == null)
+        {
+          return null;
+        }
+
+      var store = PersonaStore.dup_for_account (account);
+      return store._ensure_persona_from_contact (contact);
+    }
 }
diff --git a/backends/telepathy/tp-backend.vala b/backends/telepathy/tp-backend.vala
index 3f770b1..9df7827 100644
--- a/backends/telepathy/tp-backend.vala
+++ b/backends/telepathy/tp-backend.vala
@@ -36,8 +36,6 @@ public class Folks.Backends.Tp.Backend : Folks.Backend
   private bool _is_prepared = false;
   private bool _prepare_pending = false; /* used by unprepare() too */
   private bool _is_quiescent = false;
-  private HashMap<string, PersonaStore> _persona_stores;
-  private Map<string, PersonaStore> _persona_stores_ro;
 
   /**
    * { inheritDoc}
@@ -49,7 +47,7 @@ public class Folks.Backends.Tp.Backend : Folks.Backend
    */
   public override Map<string, PersonaStore> persona_stores
     {
-      get { return this._persona_stores_ro; }
+      get { return Tpf.PersonaStore.list_persona_stores (); }
     }
 
   /**
@@ -60,12 +58,6 @@ public class Folks.Backends.Tp.Backend : Folks.Backend
       Object ();
     }
 
-  construct
-    {
-      this._persona_stores = new HashMap<string, PersonaStore> ();
-      this._persona_stores_ro = this._persona_stores.read_only_view;
-    }
-
   /**
    * Whether this Backend has been prepared.
    *
@@ -151,14 +143,6 @@ public class Folks.Backends.Tp.Backend : Folks.Backend
                   this._account_validity_changed_cb);
               this._account_manager = null;
 
-              foreach (var persona_store in this._persona_stores.values)
-                {
-                  this.persona_store_removed (persona_store);
-                }
-
-              this._persona_stores.clear ();
-              this.notify_property ("persona-stores");
-
               this._is_quiescent = false;
               this.notify_property ("is-quiescent");
 
@@ -180,25 +164,18 @@ public class Folks.Backends.Tp.Backend : Folks.Backend
 
   private void _account_enabled_cb (Account account)
     {
-      var store = this._persona_stores.get (account.get_object_path ());
-
-      if (store != null)
-        return;
-
-      store = new Tpf.PersonaStore (account);
-
-      this._persona_stores.set (store.id, store);
+      var store = Tpf.PersonaStore.dup_for_account (account);
       store.removed.connect (this._store_removed_cb);
-      this.notify_property ("persona-stores");
 
+      this.notify_property ("persona-stores");
       this.persona_store_added (store);
     }
 
   private void _store_removed_cb (PersonaStore store)
     {
       store.removed.disconnect (this._store_removed_cb);
+
       this.persona_store_removed (store);
-      this._persona_stores.unset (store.id);
       this.notify_property ("persona-stores");
     }
 }
diff --git a/tests/lib/telepathy/contactlist/account-manager.c b/tests/lib/telepathy/contactlist/account-manager.c
index 26a1a3f..6c3191b 100644
--- a/tests/lib/telepathy/contactlist/account-manager.c
+++ b/tests/lib/telepathy/contactlist/account-manager.c
@@ -201,6 +201,7 @@ tp_test_account_manager_add_account (TpTestAccountManager *self,
     const gchar *account_path)
 {
   g_ptr_array_add (self->priv->valid_accounts, g_strdup (account_path));
+  tp_svc_account_manager_emit_account_validity_changed (self, account_path, TRUE);
   g_object_notify (G_OBJECT (self), "valid-accounts");
 }
 
@@ -221,4 +222,5 @@ tp_test_account_manager_remove_account (TpTestAccountManager *self,
     }
 
   g_object_notify (G_OBJECT (self), "valid-accounts");
+  tp_svc_account_manager_emit_account_removed (self, account_path);
 }
diff --git a/tests/lib/telepathy/contactlist/backend.c b/tests/lib/telepathy/contactlist/backend.c
index 9c7a2e0..f7d6db1 100644
--- a/tests/lib/telepathy/contactlist/backend.c
+++ b/tests/lib/telepathy/contactlist/backend.c
@@ -22,6 +22,7 @@
 #include <glib.h>
 #include <telepathy-glib/base-connection.h>
 #include <telepathy-glib/dbus.h>
+#include <telepathy-glib/svc-account.h>
 
 #include "account.h"
 #include "account-manager.h"
@@ -295,6 +296,8 @@ tp_test_backend_remove_account (TpTestBackend *self,
   tp_base_connection_change_status (data->conn,
       TP_CONNECTION_STATUS_DISCONNECTED, TP_CONNECTION_STATUS_REASON_REQUESTED);
 
+  tp_svc_account_emit_removed (data->account);
+
   tp_dbus_daemon_unregister_object (priv->daemon, data->account);
   tp_clear_object (&data->account);
 



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