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
* 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
=========================================================
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<string, PersonaStore> _persona_stores;
private Map<string, PersonaStore> _persona_stores_ro;
private E.SourceList _ab_sources;
}
/**
+ * 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
this._is_prepared = true;
this.notify_property ("is-prepared");
+
+ this._is_quiescent = true;
+ this.notify_property ("is-quiescent");
}
}
}
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");
}
private HashMap<string, Persona> _personas;
private Map<string, Persona> _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;
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.
*
{
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<E.Contact> contacts)
public class Folks.Backends.Kf.Backend : Folks.Backend
{
private bool _is_prepared = false;
+ private bool _is_quiescent = false;
private HashMap<string, PersonaStore> _persona_stores;
private Map<string, PersonaStore> _persona_stores_ro;
}
/**
+ * 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; } }
this._is_prepared = true;
this.notify_property ("is-prepared");
+
+ this._is_quiescent = true;
+ this.notify_property ("is-quiescent");
}
}
}
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");
}
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 =
{
}
/**
+ * 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
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");
}
}
}
private HashMap<string, Persona> _personas;
private Map<string, Persona> _personas_ro;
private bool _is_prepared = false;
+ private bool _is_quiescent = false;
private ClientService _service;
private ClientContactView _contact_view;
{
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.
*
{
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<unowned Contact> contacts)
public class Folks.Backends.Sw.Backend : Folks.Backend
{
private bool _is_prepared = false;
+ private bool _is_quiescent = false;
private Client _client;
private HashMap<string, PersonaStore> _persona_stores;
private Map<string, PersonaStore> _persona_stores_ro;
}
/**
+ * 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
this._is_prepared = true;
this.notify_property ("is-prepared");
+
+ this._is_quiescent = true;
+ this.notify_property ("is-quiescent");
});
}
}
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");
}
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;
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.
*
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),
/* 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
});
});
+ /* 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)
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 };
this._self_contact = contact;
this._emit_personas_changed (personas, null);
+
+ this._got_self_handle = true;
+ this._notify_if_is_quiescent ();
},
this);
}
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++)
{
{
private AccountManager _account_manager;
private bool _is_prepared = false;
+ private bool _is_quiescent = false;
private HashMap<string, PersonaStore> _persona_stores;
private Map<string, PersonaStore> _persona_stores_ro;
}
/**
+ * 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
this._is_prepared = true;
this.notify_property ("is-prepared");
+
+ this._is_quiescent = true;
+ this.notify_property ("is-quiescent");
}
}
}
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");
}
private HashMap<string, Persona> _personas;
private Map<string, Persona> _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;
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.
*
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)
{
public class Folks.Backends.Tr.Backend : Folks.Backend
{
private bool _is_prepared = false;
+ private bool _is_quiescent = false;
private HashMap<string, PersonaStore> _persona_stores;
private Map<string, PersonaStore> _persona_stores_ro;
}
/**
+ * 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}
*
*/
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");
}
}
}
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");
}
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);
"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 ()
);
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
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.
}
/**
+ * 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)
"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,
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)
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);
}
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) =>
{
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) */
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)
*/
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.
*