summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon McVittie <simon.mcvittie@collabora.co.uk>2012-08-07 16:53:56 +0100
committerSimon McVittie <simon.mcvittie@collabora.co.uk>2012-08-07 16:53:56 +0100
commit7b8d580069c5ef2dcbfe27845dc7f3e3fc773fe3 (patch)
tree47184367724d8e9d013b8143dc9c8c7b2a7371ad
parent7c2e6c31419fe6cc66c90361c36d9ff0b6a148f9 (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.c101
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));
+ }
}
/**