diff options
author | Simon McVittie <simon.mcvittie@collabora.co.uk> | 2012-08-07 16:53:56 +0100 |
---|---|---|
committer | Simon McVittie <simon.mcvittie@collabora.co.uk> | 2012-08-07 16:53:56 +0100 |
commit | 7b8d580069c5ef2dcbfe27845dc7f3e3fc773fe3 (patch) | |
tree | 47184367724d8e9d013b8143dc9c8c7b2a7371ad | |
parent | 7c2e6c31419fe6cc66c90361c36d9ff0b6a148f9 (diff) |
YtsRoster: don't crash if contact's status arrives before TpContact
Similar to a previously-fixed bug in YtsContact, the roster criticals
and discards a status update if a contact's status is updated before
the TpContact has arrived.
This can be reproduced by killing and restarting MC, then Salut, then
an example service.
Fix it in much the same way as for YtsContact, by remembering the
statuses for later.
-rw-r--r-- | ytstenut/yts-roster.c | 101 |
1 files changed, 99 insertions, 2 deletions
diff --git a/ytstenut/yts-roster.c b/ytstenut/yts-roster.c index d87ff6d..69cb432 100644 --- a/ytstenut/yts-roster.c +++ b/ytstenut/yts-roster.c @@ -66,9 +66,64 @@ enum { }; typedef struct { + gchar *contact_id; + gchar *service_id; + gchar *fqc_id; +} StatusTuple; + +static StatusTuple * +status_tuple_new (const gchar *contact_id, + const gchar *service_id, + const gchar *fqc_id) +{ + StatusTuple *st = g_slice_new0 (StatusTuple); + + st->contact_id = g_strdup (contact_id); + st->service_id = g_strdup (service_id); + st->fqc_id = g_strdup (fqc_id); + return st; +} + +static void +status_tuple_free (gpointer p) +{ + StatusTuple *st = p; + + g_free (st->contact_id); + g_free (st->service_id); + g_free (st->fqc_id); + g_slice_free (StatusTuple, st); +} + +static guint +status_tuple_hash (gconstpointer v) +{ + const StatusTuple *st = v; + + return g_str_hash (st->contact_id) + + g_str_hash (st->service_id) * 2 + + g_str_hash (st->fqc_id) * 3; +} + +static gboolean +status_tuple_equal (gconstpointer a, + gconstpointer b) +{ + const StatusTuple *a_ = a; + const StatusTuple *b_ = b; + + return g_str_equal (a_->contact_id, b_->contact_id) && + g_str_equal (a_->service_id, b_->service_id) && + g_str_equal (a_->fqc_id, b_->fqc_id); +} + +typedef struct { GHashTable *contacts; /* hash of YtsContact this roster holds */ + /* StatusTuple => status XML */ + GHashTable *deferred_statuses; + } YtsRosterPrivate; static unsigned _signals[N_SIGNALS] = { 0, }; @@ -157,6 +212,12 @@ _dispose (GObject *object) priv->contacts = NULL; } + if (priv->deferred_statuses) + { + g_hash_table_unref (priv->deferred_statuses); + priv->deferred_statuses = NULL; + } + G_OBJECT_CLASS (yts_roster_parent_class)->dispose (object); } @@ -253,6 +314,9 @@ yts_roster_init (YtsRoster *self) g_str_equal, g_free, g_object_unref); + + priv->deferred_statuses = g_hash_table_new_full (status_tuple_hash, + status_tuple_equal, status_tuple_free, g_free); } YtsService *const @@ -423,6 +487,8 @@ _connection_get_contacts (TpConnection *connection, YtsContact *contact; char const *contact_id = requested_ids[0]; TpContact *tp_contact = TP_CONTACT (contacts[0]); + GHashTableIter iter; + gpointer k, v; g_message ("Creating new contact for %s", contact_id); @@ -447,6 +513,22 @@ _connection_get_contacts (TpConnection *connection, yts_contact_add_service (contact, service); g_object_unref (service); + + /* Apply deferred status updates */ + + g_hash_table_iter_init (&iter, priv->deferred_statuses); + + while (g_hash_table_iter_next (&iter, &k, &v)) + { + StatusTuple *st = k; + + if (g_str_equal (st->contact_id, contact_id)) + { + yts_contact_update_service_status (contact, st->service_id, + st->fqc_id, v); + g_hash_table_iter_remove (&iter); + } + } } } @@ -511,9 +593,24 @@ yts_roster_update_contact_status (YtsRoster *self, DEBUG ("contact=%s service=%s fqc=%s", contact_id, service_id, fqc_id); contact = g_hash_table_lookup (priv->contacts, contact_id); - g_return_if_fail (contact); - yts_contact_update_service_status (contact, service_id, fqc_id, status_xml); + if (contact != NULL) + { + DEBUG ("updating service status straight away"); + yts_contact_update_service_status (contact, service_id, fqc_id, + status_xml); + } + else + { + /* We've hit a race condition between the contact's status being + * discovered, and the TpContact being set up. + * Save the status and apply it when we get the contact. + */ + DEBUG ("no contact yet, will update status when we have one"); + g_hash_table_insert (priv->deferred_statuses, + status_tuple_new (contact_id, service_id, fqc_id), + g_strdup (status_xml)); + } } /** |