From 77ee30d3c8df1fab219ca7e0d1d08f6c263184c8 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Fri, 2 Sep 2011 21:15:35 +0100 Subject: [PATCH] =?utf8?q?Bug=20656184=20=E2=80=94=20Add=20is-quiescent=20?= =?utf8?q?property?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit This allows clients to determine when the IndividualAggregator has reached a quiescent state — it's received and linked all of the personas it should do at startup, and from that point onwards will only relink personas in response to (for example), persona stores dynamically being added and removed at runtime. Closes: bgo#656184 --- NEWS | 3 + backends/eds/eds-backend.vala | 19 +++++ backends/eds/lib/edsf-persona-store.vala | 21 +++++ backends/key-file/kf-backend.vala | 19 +++++ backends/key-file/kf-persona-store.vala | 17 ++++ backends/libsocialweb/lib/swf-persona-store.vala | 23 ++++++ backends/libsocialweb/sw-backend.vala | 19 +++++ backends/telepathy/lib/tpf-persona-store.vala | 64 +++++++++++++- backends/telepathy/tp-backend.vala | 19 +++++ backends/tracker/lib/trf-persona-store.vala | 18 ++++ backends/tracker/tr-backend.vala | 20 +++++ folks/backend-store.vala | 4 +- folks/backend.vala | 15 ++++ folks/individual-aggregator.vala | 101 ++++++++++++++++++++++- folks/persona-store.vala | 13 +++ 15 files changed, 371 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index 0889982..b75bb91 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,7 @@ Bugs fixed: persona * Bug 657635 — Linking personas from different (e-d-s) stores is not working * Bug 657510 — Add asynchronous property setter methods +* Bug 656184 — Add is-quiescent property API changes: * Add PersonaStore:always-writeable-properties property @@ -14,6 +15,8 @@ API changes: * Add IndividualAggregator.ensure_individual_property_writeable() * Add Folks.PropertyError * Add *Details.change_*() virtual methods +* Add IndividualAggregator:is-quiescent, Backend:is-quiescent and + PersonaStore:is-quiescent Overview of changes from libfolks 0.6.0 to libfolks 0.6.1 ========================================================= diff --git a/backends/eds/eds-backend.vala b/backends/eds/eds-backend.vala index b5fd81a..e83f55f 100644 --- a/backends/eds/eds-backend.vala +++ b/backends/eds/eds-backend.vala @@ -37,6 +37,7 @@ public class Folks.Backends.Eds.Backend : Folks.Backend private static const string _use_address_books = "FOLKS_BACKEND_EDS_USE_ADDRESS_BOOKS"; private bool _is_prepared = false; + private bool _is_quiescent = false; private HashMap _persona_stores; private Map _persona_stores_ro; private E.SourceList _ab_sources; @@ -76,6 +77,18 @@ public class Folks.Backends.Eds.Backend : Folks.Backend } /** + * Whether this Backend has reached a quiescent state. + * + * See {@link Folks.Backend.is_quiescent}. + * + * @since UNRELEASED + */ + public override bool is_quiescent + { + get { return this._is_quiescent; } + } + + /** * {@inheritDoc} */ public override async void prepare () throws GLib.Error @@ -93,6 +106,9 @@ public class Folks.Backends.Eds.Backend : Folks.Backend this._is_prepared = true; this.notify_property ("is-prepared"); + + this._is_quiescent = true; + this.notify_property ("is-quiescent"); } } } @@ -114,6 +130,9 @@ public class Folks.Backends.Eds.Backend : Folks.Backend this._ab_sources.changed.disconnect (this._ab_source_list_changed_cb); this._ab_sources = null; + this._is_quiescent = false; + this.notify_property ("is-quiescent"); + this._is_prepared = false; this.notify_property ("is-prepared"); } diff --git a/backends/eds/lib/edsf-persona-store.vala b/backends/eds/lib/edsf-persona-store.vala index 83e0c29..3ff8aee 100644 --- a/backends/eds/lib/edsf-persona-store.vala +++ b/backends/eds/lib/edsf-persona-store.vala @@ -37,6 +37,7 @@ public class Edsf.PersonaStore : Folks.PersonaStore private HashMap _personas; private Map _personas_ro; private bool _is_prepared = false; + private bool _is_quiescent = false; private E.BookClient _addressbook; private E.BookClientView _ebookview; private string _addressbook_uri = null; @@ -166,6 +167,18 @@ public class Edsf.PersonaStore : Folks.PersonaStore get { return this._always_writeable_properties; } } + /* + * Whether this PersonaStore has reached a quiescent state. + * + * See {@link Folks.PersonaStore.is_quiescent}. + * + * @since UNRELEASED + */ + public override bool is_quiescent + { + get { return this._is_quiescent; } + } + /** * The {@link Persona}s exposed by this PersonaStore. * @@ -1268,6 +1281,14 @@ public class Edsf.PersonaStore : Folks.PersonaStore { this._emit_personas_changed (added_personas, null); } + + /* If this is the first contacts-added notification, assume we've reached + * a quiescent state. */ + if (this._is_quiescent == false) + { + this._is_quiescent = true; + this.notify_property ("is-quiescent"); + } } private void _contacts_changed_cb (GLib.List contacts) diff --git a/backends/key-file/kf-backend.vala b/backends/key-file/kf-backend.vala index 53b8191..8569a63 100644 --- a/backends/key-file/kf-backend.vala +++ b/backends/key-file/kf-backend.vala @@ -35,6 +35,7 @@ extern const string BACKEND_NAME; public class Folks.Backends.Kf.Backend : Folks.Backend { private bool _is_prepared = false; + private bool _is_quiescent = false; private HashMap _persona_stores; private Map _persona_stores_ro; @@ -51,6 +52,18 @@ public class Folks.Backends.Kf.Backend : Folks.Backend } /** + * Whether this Backend has reached a quiescent state. + * + * See {@link Folks.Backend.is_quiescent}. + * + * @since UNRELEASED + */ + public override bool is_quiescent + { + get { return this._is_quiescent; } + } + + /** * {@inheritDoc} */ public override string name { get { return BACKEND_NAME; } } @@ -113,6 +126,9 @@ public class Folks.Backends.Kf.Backend : Folks.Backend this._is_prepared = true; this.notify_property ("is-prepared"); + + this._is_quiescent = true; + this.notify_property ("is-quiescent"); } } } @@ -130,6 +146,9 @@ public class Folks.Backends.Kf.Backend : Folks.Backend this._persona_stores.clear (); this.notify_property ("persona-stores"); + this._is_quiescent = false; + this.notify_property ("is-quiescent"); + this._is_prepared = false; this.notify_property ("is-prepared"); } diff --git a/backends/key-file/kf-persona-store.vala b/backends/key-file/kf-persona-store.vala index d4d075b..956d306 100644 --- a/backends/key-file/kf-persona-store.vala +++ b/backends/key-file/kf-persona-store.vala @@ -38,6 +38,7 @@ public class Folks.Backends.Kf.PersonaStore : Folks.PersonaStore private GLib.KeyFile _key_file; private unowned Cancellable _save_key_file_cancellable = null; private bool _is_prepared = false; + private bool _is_quiescent = false; private const string[] _always_writeable_properties = { @@ -112,6 +113,18 @@ public class Folks.Backends.Kf.PersonaStore : Folks.PersonaStore } /** + * Whether this PersonaStore has reached a quiescent state. + * + * See {@link Folks.PersonaStore.is_quiescent}. + * + * @since UNRELEASED + */ + public override bool is_quiescent + { + get { return this._is_quiescent; } + } + + /** * {@inheritDoc} * * @since UNRELEASED @@ -265,6 +278,10 @@ public class Folks.Backends.Kf.PersonaStore : Folks.PersonaStore this._is_prepared = true; this.notify_property ("is-prepared"); + + /* We've finished loading all the personas we know about */ + this._is_quiescent = true; + this.notify_property ("is-quiescent"); } } } diff --git a/backends/libsocialweb/lib/swf-persona-store.vala b/backends/libsocialweb/lib/swf-persona-store.vala index 631b783..89fd6d2 100644 --- a/backends/libsocialweb/lib/swf-persona-store.vala +++ b/backends/libsocialweb/lib/swf-persona-store.vala @@ -37,6 +37,7 @@ public class Swf.PersonaStore : Folks.PersonaStore private HashMap _personas; private Map _personas_ro; private bool _is_prepared = false; + private bool _is_quiescent = false; private ClientService _service; private ClientContactView _contact_view; @@ -124,6 +125,19 @@ public class Swf.PersonaStore : Folks.PersonaStore { get { return this._always_writeable_properties; } } + + /* + * Whether this PersonaStore has reached a quiescent state. + * + * See {@link Folks.PersonaStore.is_quiescent}. + * + * @since UNRELEASED + */ + public override bool is_quiescent + { + get { return this._is_quiescent; } + } + /** * The {@link Persona}s exposed by this PersonaStore. * @@ -250,6 +264,15 @@ public class Swf.PersonaStore : Folks.PersonaStore { this._emit_personas_changed (added_personas, null); } + + /* If this is the first contacts-added notification, assume we've reached + * a quiescent state. We can't do any better, since libsocialweb doesn't + * expose an is-quiescent property (or similar). */ + if (this._is_quiescent == false) + { + this._is_quiescent = true; + this.notify_property ("is-quiescent"); + } } private void contacts_changed_cb (GLib.List contacts) diff --git a/backends/libsocialweb/sw-backend.vala b/backends/libsocialweb/sw-backend.vala index 120bac5..f598e23 100644 --- a/backends/libsocialweb/sw-backend.vala +++ b/backends/libsocialweb/sw-backend.vala @@ -34,6 +34,7 @@ extern const string BACKEND_NAME; public class Folks.Backends.Sw.Backend : Folks.Backend { private bool _is_prepared = false; + private bool _is_quiescent = false; private Client _client; private HashMap _persona_stores; private Map _persona_stores_ro; @@ -72,6 +73,18 @@ public class Folks.Backends.Sw.Backend : Folks.Backend } /** + * Whether this Backend has reached a quiescent state. + * + * See {@link Folks.Backend.is_quiescent}. + * + * @since UNRELEASED + */ + public override bool is_quiescent + { + get { return this._is_quiescent; } + } + + /** * {@inheritDoc} */ public override async void prepare () throws GLib.Error @@ -88,6 +101,9 @@ public class Folks.Backends.Sw.Backend : Folks.Backend this._is_prepared = true; this.notify_property ("is-prepared"); + + this._is_quiescent = true; + this.notify_property ("is-quiescent"); }); } } @@ -109,6 +125,9 @@ public class Folks.Backends.Sw.Backend : Folks.Backend this._persona_stores.clear (); this.notify_property ("persona-stores"); + this._is_quiescent = false; + this.notify_property ("is-quiescent"); + this._is_prepared = false; this.notify_property ("is-prepared"); } diff --git a/backends/telepathy/lib/tpf-persona-store.vala b/backends/telepathy/lib/tpf-persona-store.vala index 37532c8..8e3c32f 100644 --- a/backends/telepathy/lib/tpf-persona-store.vala +++ b/backends/telepathy/lib/tpf-persona-store.vala @@ -92,6 +92,9 @@ public class Tpf.PersonaStore : Folks.PersonaStore private MaybeBool _can_group_personas = MaybeBool.UNSET; private MaybeBool _can_remove_personas = MaybeBool.UNSET; private bool _is_prepared = false; + private bool _is_quiescent = false; + private bool _got_stored_channel_members = false; + private bool _got_self_handle = false; private Debug _debug; private PersonaStoreCache _cache; private Cancellable? _load_cache_cancellable = null; @@ -185,6 +188,29 @@ public class Tpf.PersonaStore : Folks.PersonaStore get { return this._always_writeable_properties; } } + /* + * Whether this PersonaStore has reached a quiescent state. + * + * See {@link Folks.PersonaStore.is_quiescent}. + * + * @since UNRELEASED + */ + public override bool is_quiescent + { + get { return this._is_quiescent; } + } + + private void _notify_if_is_quiescent () + { + if (this._got_stored_channel_members == true && + this._got_self_handle == true && + this._is_quiescent == false) + { + this._is_quiescent = true; + this.notify_property ("is-quiescent"); + } + } + /** * The {@link Persona}s exposed by this PersonaStore. * @@ -250,6 +276,8 @@ public class Tpf.PersonaStore : Folks.PersonaStore debug.print_key_value_pairs (domain, level, "ID", this.id, "Prepared?", this._is_prepared ? "yes" : "no", + "Has stored contact members?", this._got_stored_channel_members ? "yes" : "no", + "Has self handle?", this._got_self_handle ? "yes" : "no", "Publish TpChannel", "%p".printf (this._publish), "Stored TpChannel", "%p".printf (this._stored), "Subscribe TpChannel", "%p".printf (this._subscribe), @@ -563,6 +591,11 @@ public class Tpf.PersonaStore : Folks.PersonaStore /* If we're disconnected, advertise personas from the cache * instead. */ yield this._load_cache (); + + /* We've reached a quiescent state. */ + this._got_self_handle = true; + this._got_stored_channel_members = true; + this._notify_if_is_quiescent (); } try @@ -762,6 +795,12 @@ public class Tpf.PersonaStore : Folks.PersonaStore }); }); + /* If the persona store starts offline, we've reached a quiescent + * state. */ + this._got_self_handle = true; + this._got_stored_channel_members = true; + this._notify_if_is_quiescent (); + return; } else if (new_status != TelepathyGLib.ConnectionStatus.CONNECTED) @@ -969,7 +1008,14 @@ public class Tpf.PersonaStore : Folks.PersonaStore this._ignore_by_handle (this._self_contact.handle, null, null, 0); if (c.self_handle == 0) - return; + { + /* We can only claim to have reached a quiescent state once we've + * got the stored contact list and the self handle. */ + this._got_self_handle = true; + this._notify_if_is_quiescent (); + + return; + } uint[] contact_handles = { c.self_handle }; @@ -1002,6 +1048,9 @@ public class Tpf.PersonaStore : Folks.PersonaStore this._self_contact = contact; this._emit_personas_changed (personas, null); + + this._got_self_handle = true; + this._notify_if_is_quiescent (); }, this); } @@ -1185,7 +1234,18 @@ public class Tpf.PersonaStore : Folks.PersonaStore HashTable details) { if (added.length > 0) - this._channel_group_pend_incoming_adds.begin (channel, added, true); + { + this._channel_group_pend_incoming_adds.begin (channel, added, true, + (obj, res) => + { + this._channel_group_pend_incoming_adds.end (res); + + /* We can only claim to have reached a quiescent state once we've + * got the stored contact list and the self handle. */ + this._got_stored_channel_members = true; + this._notify_if_is_quiescent (); + }); + } for (var i = 0; i < removed.length; i++) { diff --git a/backends/telepathy/tp-backend.vala b/backends/telepathy/tp-backend.vala index b67de00..7a90a42 100644 --- a/backends/telepathy/tp-backend.vala +++ b/backends/telepathy/tp-backend.vala @@ -34,6 +34,7 @@ public class Folks.Backends.Tp.Backend : Folks.Backend { private AccountManager _account_manager; private bool _is_prepared = false; + private bool _is_quiescent = false; private HashMap _persona_stores; private Map _persona_stores_ro; @@ -72,6 +73,18 @@ public class Folks.Backends.Tp.Backend : Folks.Backend } /** + * Whether this Backend has reached a quiescent state. + * + * See {@link Folks.Backend.is_quiescent}. + * + * @since UNRELEASED + */ + public override bool is_quiescent + { + get { return this._is_quiescent; } + } + + /** * {@inheritDoc} */ public override async void prepare () throws GLib.Error @@ -100,6 +113,9 @@ public class Folks.Backends.Tp.Backend : Folks.Backend this._is_prepared = true; this.notify_property ("is-prepared"); + + this._is_quiescent = true; + this.notify_property ("is-quiescent"); } } } @@ -123,6 +139,9 @@ public class Folks.Backends.Tp.Backend : Folks.Backend this._persona_stores.clear (); this.notify_property ("persona-stores"); + this._is_quiescent = false; + this.notify_property ("is-quiescent"); + this._is_prepared = false; this.notify_property ("is-prepared"); } diff --git a/backends/tracker/lib/trf-persona-store.vala b/backends/tracker/lib/trf-persona-store.vala index 598b9a2..e6a2c46 100644 --- a/backends/tracker/lib/trf-persona-store.vala +++ b/backends/tracker/lib/trf-persona-store.vala @@ -166,6 +166,7 @@ public class Trf.PersonaStore : Folks.PersonaStore private HashMap _personas; private Map _personas_ro; private bool _is_prepared = false; + private bool _is_quiescent = false; private static const int _default_timeout = 100; private Resources _resources_object; private Tracker.Sparql.Connection _connection; @@ -383,6 +384,18 @@ public class Trf.PersonaStore : Folks.PersonaStore get { return this._always_writeable_properties; } } + /* + * Whether this PersonaStore has reached a quiescent state. + * + * See {@link Folks.PersonaStore.is_quiescent}. + * + * @since UNRELEASED + */ + public override bool is_quiescent + { + get { return this._is_quiescent; } + } + /** * The {@link Persona}s exposed by this PersonaStore. * @@ -1071,6 +1084,11 @@ public class Trf.PersonaStore : Folks.PersonaStore this._is_prepared = true; this.notify_property ("is-prepared"); + + /* By this time (due to having done the INITIAL_QUERY above) + * we have already reached a quiescent state. */ + this._is_quiescent = true; + this.notify_property ("is-quiescent"); } catch (GLib.IOError e1) { diff --git a/backends/tracker/tr-backend.vala b/backends/tracker/tr-backend.vala index f67b0cd..34e4dad 100644 --- a/backends/tracker/tr-backend.vala +++ b/backends/tracker/tr-backend.vala @@ -33,6 +33,7 @@ extern const string BACKEND_NAME; public class Folks.Backends.Tr.Backend : Folks.Backend { private bool _is_prepared = false; + private bool _is_quiescent = false; private HashMap _persona_stores; private Map _persona_stores_ro; @@ -69,6 +70,18 @@ public class Folks.Backends.Tr.Backend : Folks.Backend } /** + * Whether this Backend has reached a quiescent state. + * + * See {@link Folks.Backend.is_quiescent}. + * + * @since UNRELEASED + */ + public override bool is_quiescent + { + get { return this._is_quiescent; } + } + + /** * {@inheritDoc} * */ @@ -79,8 +92,12 @@ public class Folks.Backends.Tr.Backend : Folks.Backend if (!this._is_prepared) { this._add_default_persona_store (); + this._is_prepared = true; this.notify_property ("is-prepared"); + + this._is_quiescent = true; + this.notify_property ("is-quiescent"); } } } @@ -98,6 +115,9 @@ public class Folks.Backends.Tr.Backend : Folks.Backend this._persona_stores.clear (); this.notify_property ("persona-stores"); + this._is_quiescent = false; + this.notify_property ("is-quiescent"); + this._is_prepared = false; this.notify_property ("is-prepared"); } diff --git a/folks/backend-store.vala b/folks/backend-store.vala index 89d9821..4a55d83 100644 --- a/folks/backend-store.vala +++ b/folks/backend-store.vala @@ -186,7 +186,8 @@ public class Folks.BackendStore : Object { debug.print_key_value_pairs (domain, level, "Ref. count", this.ref_count.to_string (), "Name", backend.name, - "Prepared?", backend.is_prepared ? "yes" : "no" + "Prepared?", backend.is_prepared ? "yes" : "no", + "Quiescent?", backend.is_quiescent ? "yes" : "no" ); debug.print_line (domain, level, "%u PersonaStores:", backend.persona_stores.size); @@ -219,6 +220,7 @@ public class Folks.BackendStore : Object { "ID", persona_store.id, "Prepared?", persona_store.is_prepared ? "yes" : "no", "Writeable?", persona_store.is_writeable ? "yes" : "no", + "Quiescent?", persona_store.is_quiescent ? "yes" : "no", "Trust level", trust_level, "Persona count", persona_store.personas.size.to_string () ); diff --git a/folks/backend.vala b/folks/backend.vala index db8d222..8e728fa 100644 --- a/folks/backend.vala +++ b/folks/backend.vala @@ -44,6 +44,21 @@ public abstract class Folks.Backend : Object public abstract bool is_prepared { get; default = false; } /** + * Whether the backend has reached a quiescent state. This will happen at some + * point after {@link Backend.prepare} has successfully completed for the + * backend. A backend is in a quiescent state when all the + * {@link PersonaStore}s that it originally knows about have been loaded. + * + * It's guaranteed that this property's value will only ever change after + * {@link Backend.is_prepared} has changed to `true`. + * + * When {@link Backend.unprepare} is called, this will be reset to `false`. + * + * @since UNRELEASED + */ + public abstract bool is_quiescent { get; default = false; } + + /** * A unique name for the backend. * * This will be used to identify the backend, and should also be used as the diff --git a/folks/individual-aggregator.vala b/folks/individual-aggregator.vala index b24ef5b..01efcab 100644 --- a/folks/individual-aggregator.vala +++ b/folks/individual-aggregator.vala @@ -79,6 +79,18 @@ public class Folks.IndividualAggregator : Object private static const string _FOLKS_CONFIG_KEY = "/system/folks/backends/primary_store"; + /* The number of persona stores and backends we're waiting to become + * quiescent. Once these both reach 0, we should be in a quiescent state. + * We have to count both of them so that we can handle the case where one + * backend becomes available, and its persona stores all become quiescent, + * long before any other backend becomes available. In this case, we want + * the aggregator to signal that it's reached a quiescent state only once + * all the other backends have also become available. */ + private uint _non_quiescent_persona_store_count = 0; + /* Same for backends. */ + private uint _non_quiescent_backend_count = 0; + private bool _is_quiescent = false; + /** * Whether {@link IndividualAggregator.prepare} has successfully completed for * this aggregator. @@ -91,6 +103,23 @@ public class Folks.IndividualAggregator : Object } /** + * Whether the aggregator has reached a quiescent state. This will happen at + * some point after {@link IndividualAggregator.prepare} has successfully + * completed for the aggregator. An aggregator is in a quiescent state when + * all the {@link PersonaStore}s listed by its backends have reached a + * quiescent state. + * + * It's guaranteed that this property's value will only ever change after + * {@link IndividualAggregator.is_prepared} has changed to `true`. + * + * @since UNRELEASED + */ + public bool is_quiescent + { + get { return this._is_quiescent; } + } + + /** * Our configured primary (writeable) store. * * Which one to use is decided (in order or precedence) @@ -265,7 +294,12 @@ public class Folks.IndividualAggregator : Object "Ref. count", this.ref_count.to_string (), "Writeable store", "%p".printf (this._writeable_store), "Linking enabled?", this._linking_enabled ? "yes" : "no", - "Prepared?", this._is_prepared ? "yes" : "no" + "Prepared?", this._is_prepared ? "yes" : "no", + "Quiescent?", this._is_quiescent + ? "yes" + : "no (%u backends, %u persona stores left)".printf ( + this._non_quiescent_backend_count, + this._non_quiescent_persona_store_count) ); debug.print_line (domain, level, @@ -463,6 +497,8 @@ public class Folks.IndividualAggregator : Object this._backend_persona_store_added_cb); backend.persona_store_removed.connect ( this._backend_persona_store_removed_cb); + backend.notify["is-quiescent"].connect ( + this._backend_is_quiescent_changed_cb); /* handle the stores that have already been signaled */ foreach (var persona_store in backend.persona_stores.values) @@ -475,6 +511,16 @@ public class Folks.IndividualAggregator : Object private void _backend_available_cb (BackendStore backend_store, Backend backend) { + /* Increase the number of non-quiescent backends we're waiting for. + * If we've already reached a quiescent state, this is ignored. If we + * haven't, this delays us reaching a quiescent state until the + * _backend_is_quiescent_changed_cb() callback is called for this + * backend. */ + if (backend.is_quiescent == false) + { + this._non_quiescent_backend_count++; + } + this._add_backend.begin (backend); } @@ -507,6 +553,18 @@ public class Folks.IndividualAggregator : Object store.personas_changed.connect (this._personas_changed_cb); store.notify["is-writeable"].connect (this._is_writeable_changed_cb); store.notify["trust-level"].connect (this._trust_level_changed_cb); + store.notify["is-quiescent"].connect ( + this._persona_store_is_quiescent_changed_cb); + + /* Increase the number of non-quiescent persona stores we're waiting for. + * If we've already reached a quiescent state, this is ignored. If we + * haven't, this delays us reaching a quiescent state until the + * _persona_store_is_quiescent_changed_cb() callback is called for this + * store. */ + if (store.is_quiescent == false) + { + this._non_quiescent_persona_store_count++; + } store.prepare.begin ((obj, result) => { @@ -528,9 +586,19 @@ public class Folks.IndividualAggregator : Object PersonaStore store) { store.personas_changed.disconnect (this._personas_changed_cb); + store.notify["is-quiescent"].disconnect ( + this._persona_store_is_quiescent_changed_cb); store.notify["trust-level"].disconnect (this._trust_level_changed_cb); store.notify["is-writeable"].disconnect (this._is_writeable_changed_cb); + /* If we were still waiting on this persona store to reach a quiescent + * state, stop waiting. */ + if (this._is_quiescent == false && store.is_quiescent == false) + { + this._non_quiescent_persona_store_count--; + this._notify_if_is_quiescent (); + } + /* no need to remove this store's personas from all the individuals, since * they'll do that themselves (and emit their own 'removed' signal if * necessary) */ @@ -984,6 +1052,37 @@ public class Folks.IndividualAggregator : Object assert (store.trust_level != PersonaStoreTrust.FULL); } + private void _persona_store_is_quiescent_changed_cb (Object obj, + ParamSpec pspec) + { + /* Have we reached a quiescent state yet? */ + if (this._non_quiescent_persona_store_count > 0) + { + this._non_quiescent_persona_store_count--; + this._notify_if_is_quiescent (); + } + } + + private void _backend_is_quiescent_changed_cb (Object obj, ParamSpec pspec) + { + if (this._non_quiescent_backend_count > 0) + { + this._non_quiescent_backend_count--; + this._notify_if_is_quiescent (); + } + } + + private void _notify_if_is_quiescent () + { + if (this._non_quiescent_backend_count == 0 && + this._non_quiescent_persona_store_count == 0 && + this._is_quiescent == false) + { + this._is_quiescent = true; + this.notify_property ("is-quiescent"); + } + } + private void _individual_removed_cb (Individual i, Individual? replacement) { if (this.user == i) diff --git a/folks/persona-store.vala b/folks/persona-store.vala index 4f70778..b330536 100644 --- a/folks/persona-store.vala +++ b/folks/persona-store.vala @@ -463,6 +463,19 @@ public abstract class Folks.PersonaStore : Object */ public abstract bool is_prepared { get; default = false; } + /** + * Whether the store has reached a quiescent state. This will happen at some + * point after {@link PersonaStore.prepare} has successfully completed for the + * store. A store is in a quiescent state when all the {@link Persona}s that + * it originally knows about have been loaded. + * + * It's guaranteed that this property's value will only ever change after + * {@link IndividualAggregator.is_prepared} has changed to `true`. + * + * @since UNRELEASED + */ + public abstract bool is_quiescent { get; default = false; } + /** * Whether the PersonaStore is writeable. * -- 2.7.4