/* * avahi-self.c - Source for SalutAvahiSelf * Copyright (C) 2008 Collabora Ltd. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "config.h" #include "avahi-self.h" #include #include #include #include #include #include #define DEBUG_FLAG DEBUG_SELF #include "debug.h" #include "sha1/sha1-util.h" #ifdef ENABLE_OLPC #define KEY_SEGMENT_SIZE 200 #endif G_DEFINE_TYPE (SalutAvahiSelf, salut_avahi_self, SALUT_TYPE_SELF); /* properties */ enum { PROP_DISCOVERY_CLIENT = 1, LAST_PROPERTY }; struct _SalutAvahiSelfPrivate { SalutAvahiDiscoveryClient *discovery_client; GaEntryGroup *presence_group; GaEntryGroupService *presence; GaEntryGroup *avatar_group; gboolean dispose_has_run; }; static void salut_avahi_self_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { SalutAvahiSelf *self = SALUT_AVAHI_SELF (object); SalutAvahiSelfPrivate *priv = self->priv; switch (property_id) { case PROP_DISCOVERY_CLIENT: g_value_set_object (value, priv->discovery_client); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void salut_avahi_self_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { SalutAvahiSelf *self = SALUT_AVAHI_SELF (object); SalutAvahiSelfPrivate *priv = self->priv; switch (property_id) { case PROP_DISCOVERY_CLIENT: priv->discovery_client = g_value_get_object (value); g_object_ref (priv->discovery_client); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void salut_avahi_self_init (SalutAvahiSelf *self) { SalutAvahiSelfPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self, SALUT_TYPE_AVAHI_SELF, SalutAvahiSelfPrivate); self->priv = priv; priv->discovery_client = NULL; } static void _avahi_presence_group_established (GaEntryGroup *group, GaEntryGroupState state, SalutAvahiSelf *self) { salut_self_established (SALUT_SELF (self)); } static void _avahi_presence_group_failed (GaEntryGroup *group, GaEntryGroupState state, SalutAvahiSelf *self) { DEBUG ("self presence group failed"); } static AvahiStringList * create_txt_record (SalutAvahiSelf *self, guint16 port) { SalutSelf *_self = SALUT_SELF (self); AvahiStringList *ret; ret = avahi_string_list_new ("txtvers=1", NULL); /* Some silly clients still use this */ ret = avahi_string_list_add_printf (ret, "port.p2pj=%" G_GUINT16_FORMAT, port); if (_self->nickname != NULL) ret = avahi_string_list_add_printf (ret, "nick=%s", _self->nickname); if (_self->first_name != NULL) ret = avahi_string_list_add_printf (ret, "1st=%s", _self->first_name); if (_self->last_name != NULL) ret = avahi_string_list_add_printf (ret, "last=%s", _self->last_name); if (_self->email != NULL) ret = avahi_string_list_add_printf (ret, "email=%s", _self->email); if (_self->jid != NULL) ret = avahi_string_list_add_printf (ret, "jid=%s", _self->jid); #ifdef ENABLE_OLPC if (_self->olpc_color) ret = avahi_string_list_add_printf (ret, "olpc-color=%s", _self->olpc_color); if (_self->olpc_key != NULL) { uint8_t *key = (uint8_t *) _self->olpc_key->data; size_t key_len = _self->olpc_key->len; guint i = 0; while (key_len > 0) { size_t step = MIN (key_len, KEY_SEGMENT_SIZE); gchar *name = g_strdup_printf ("olpc-key-part%u", i); ret = avahi_string_list_add_pair_arbitrary (ret, name, key, step); key += step; key_len -= step; i++; } } #endif ret = avahi_string_list_add_printf (ret, "status=%s", salut_presence_status_txt_names[_self->status]); if (_self->status_message != NULL) ret = avahi_string_list_add_printf (ret, "msg=%s", _self->status_message); return ret; } static gboolean salut_avahi_self_set_caps (SalutSelf *_self, GError **error) { SalutAvahiSelf *self = SALUT_AVAHI_SELF (_self); SalutAvahiSelfPrivate *priv = self->priv; if (priv->presence == NULL) /* Service is not announced yet */ return TRUE; ga_entry_group_service_freeze (priv->presence); if (_self->node == NULL) ga_entry_group_service_remove_key (priv->presence, "node", NULL); else ga_entry_group_service_set (priv->presence, "node", _self->node, NULL); if (_self->hash == NULL) ga_entry_group_service_remove_key (priv->presence, "hash", NULL); else ga_entry_group_service_set (priv->presence, "hash", _self->hash, NULL); if (_self->ver == NULL) ga_entry_group_service_remove_key (priv->presence, "ver", NULL); else ga_entry_group_service_set (priv->presence, "ver", _self->ver, NULL); return ga_entry_group_service_thaw (priv->presence, error); } static gboolean salut_avahi_self_announce (SalutSelf *_self, guint16 port, GError **error) { SalutAvahiSelf *self = SALUT_AVAHI_SELF (_self); SalutAvahiSelfPrivate *priv = self->priv; AvahiStringList *txt_record = NULL; const char *dnssd_name; priv->presence_group = ga_entry_group_new (); g_signal_connect (priv->presence_group, "state-changed::established", G_CALLBACK (_avahi_presence_group_established), self); g_signal_connect (priv->presence_group, "state-changed::collision", G_CALLBACK (_avahi_presence_group_failed), self); g_signal_connect (priv->presence_group, "state-changed::failure", G_CALLBACK(_avahi_presence_group_failed), self); if (!ga_entry_group_attach (priv->presence_group, priv->discovery_client->avahi_client, error)) goto error; _self->name = g_strdup_printf ("%s@%s", _self->published_name, avahi_client_get_host_name (priv->discovery_client->avahi_client->avahi_client)); txt_record = create_txt_record (self, port); dnssd_name = salut_avahi_discovery_client_get_dnssd_name ( priv->discovery_client); priv->presence = ga_entry_group_add_service_strlist (priv->presence_group, _self->name, dnssd_name, port, error, txt_record); if (priv->presence == NULL) goto error; if (!salut_avahi_self_set_caps (_self, error)) goto error; if (!ga_entry_group_commit (priv->presence_group, error)) goto error; avahi_string_list_free (txt_record); return TRUE; error: avahi_string_list_free (txt_record); return FALSE; } static gboolean salut_avahi_self_set_presence (SalutSelf *self, GError **error) { SalutAvahiSelf *avahi_self = SALUT_AVAHI_SELF (self); SalutAvahiSelfPrivate *priv = avahi_self->priv; ga_entry_group_service_freeze (priv->presence); ga_entry_group_service_set (priv->presence, "status", salut_presence_status_txt_names[self->status], NULL); if (self->status_message) ga_entry_group_service_set (priv->presence, "msg", self->status_message, NULL); else ga_entry_group_service_remove_key (priv->presence, "msg", NULL); return ga_entry_group_service_thaw (priv->presence, error); } static gboolean salut_avahi_self_set_alias (SalutSelf *_self, GError **error) { SalutAvahiSelf *self = SALUT_AVAHI_SELF (_self); SalutAvahiSelfPrivate *priv = self->priv; return ga_entry_group_service_set (priv->presence, "nick", _self->alias, error); } static void salut_avahi_self_remove_avatar (SalutSelf *_self) { SalutAvahiSelf *self = SALUT_AVAHI_SELF (_self); SalutAvahiSelfPrivate *priv = self->priv; ga_entry_group_service_remove_key (priv->presence, "phsh", NULL); if (priv->avatar_group != NULL) { g_object_unref (priv->avatar_group); priv->avatar_group = NULL; } } static gboolean salut_avahi_self_publish_avatar (SalutAvahiSelf *self, guint8 *data, gsize size, GError **error) { SalutAvahiSelfPrivate *priv = self->priv; SalutSelf *_self = SALUT_SELF (self); gchar *name; gboolean ret; gboolean is_new = FALSE; const gchar *dnssd_name; dnssd_name = salut_avahi_discovery_client_get_dnssd_name ( priv->discovery_client); name = g_strdup_printf ("%s.%s.local", _self->name, dnssd_name); if (priv->avatar_group == NULL) { priv->avatar_group = ga_entry_group_new (); ga_entry_group_attach (priv->avatar_group, priv->discovery_client->avahi_client, NULL); is_new = TRUE; } ret = ga_entry_group_add_record (priv->avatar_group, is_new ? 0 : AVAHI_PUBLISH_UPDATE, name, 0xA, 120, data, size, error); g_free (name); if (is_new) ga_entry_group_commit (priv->avatar_group, error); return ret; } static gboolean salut_avahi_self_set_avatar (SalutSelf *_self, guint8 *data, gsize size, GError **error) { SalutAvahiSelf *self = SALUT_AVAHI_SELF (_self); SalutAvahiSelfPrivate *priv = self->priv; if (!salut_avahi_self_publish_avatar (self, data, size, error)) return FALSE; _self->avatar = g_memdup (data, size); _self->avatar_size = size; if (size > 0) _self->avatar_token = sha1_hex (data, size); return ga_entry_group_service_set (priv->presence, "phsh", _self->avatar_token, error); } #ifdef ENABLE_OLPC static gboolean salut_avahi_self_update_current_activity (SalutSelf *_self, const gchar *room_name, GError **error) { SalutAvahiSelf *self = SALUT_AVAHI_SELF (_self); SalutAvahiSelfPrivate *priv = self->priv; ga_entry_group_service_freeze (priv->presence); ga_entry_group_service_set (priv->presence, "olpc-current-activity", _self->olpc_cur_act, NULL); ga_entry_group_service_set (priv->presence, "olpc-current-activity-room", room_name, NULL); return ga_entry_group_service_thaw (priv->presence, error); } static gboolean salut_avahi_self_set_olpc_properties (SalutSelf *_self, const GArray *key, const gchar *color, const gchar *jid, GError **error) { SalutAvahiSelf *self = SALUT_AVAHI_SELF (_self); SalutAvahiSelfPrivate *priv = self->priv; ga_entry_group_service_freeze (priv->presence); if (key != NULL) { size_t key_len = key->len; const guint8 *key_data = (const guint8 *) key->data; guint i; guint to_remove; if (_self->olpc_key == NULL) { to_remove = 0; } else { to_remove = (_self->olpc_key->len + KEY_SEGMENT_SIZE - 1) / KEY_SEGMENT_SIZE; } i = 0; while (key_len > 0) { size_t step = MIN (key_len, KEY_SEGMENT_SIZE); gchar *name = g_strdup_printf ("olpc-key-part%u", i); ga_entry_group_service_set_arbitrary (priv->presence, name, key_data, step, NULL); g_free (name); key_data += step; key_len -= step; i++; } /* if the new key is shorter than the old, clean up any stray segments */ while (i < to_remove) { gchar *name = g_strdup_printf ("olpc-key-part%u", i); ga_entry_group_service_remove_key (priv->presence, name, NULL); g_free (name); i++; } } if (color != NULL) { ga_entry_group_service_set (priv->presence, "olpc-color", color, NULL); } if (jid != NULL) { ga_entry_group_service_set (priv->presence, "jid", jid, NULL); } return ga_entry_group_service_thaw (priv->presence, error); } #endif static void salut_avahi_self_dispose (GObject *object); static void salut_avahi_self_class_init ( SalutAvahiSelfClass *salut_avahi_self_class) { GObjectClass *object_class = G_OBJECT_CLASS (salut_avahi_self_class); SalutSelfClass *self_class = SALUT_SELF_CLASS ( salut_avahi_self_class); GParamSpec *param_spec; g_type_class_add_private (salut_avahi_self_class, sizeof (SalutAvahiSelfPrivate)); object_class->dispose = salut_avahi_self_dispose; object_class->get_property = salut_avahi_self_get_property; object_class->set_property = salut_avahi_self_set_property; self_class->announce = salut_avahi_self_announce; self_class->set_presence = salut_avahi_self_set_presence; self_class->set_caps = salut_avahi_self_set_caps; self_class->set_alias = salut_avahi_self_set_alias; self_class->remove_avatar = salut_avahi_self_remove_avatar; self_class->set_avatar = salut_avahi_self_set_avatar; #ifdef ENABLE_OLPC self_class->update_current_activity = salut_avahi_self_update_current_activity; self_class->set_olpc_properties = salut_avahi_self_set_olpc_properties; #endif param_spec = g_param_spec_object ( "discovery-client", "SalutAvahiDiscoveryClient object", "The Salut Avahi Discovery client associated with this self object", SALUT_TYPE_AVAHI_DISCOVERY_CLIENT, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_DISCOVERY_CLIENT, param_spec); } void salut_avahi_self_dispose (GObject *object) { SalutAvahiSelf *self = SALUT_AVAHI_SELF (object); SalutAvahiSelfPrivate *priv = self->priv; if (priv->dispose_has_run) return; priv->dispose_has_run = TRUE; if (priv->presence_group != NULL) { g_object_unref (priv->presence_group); priv->presence_group = NULL; } if (priv->avatar_group != NULL) { g_object_unref (priv->avatar_group); priv->avatar_group = NULL; } if (priv->discovery_client != NULL) { g_object_unref (priv->discovery_client); priv->discovery_client = NULL; } if (G_OBJECT_CLASS (salut_avahi_self_parent_class)->dispose) G_OBJECT_CLASS (salut_avahi_self_parent_class)->dispose (object); } SalutAvahiSelf * salut_avahi_self_new (SalutConnection *connection, SalutAvahiDiscoveryClient *discovery_client, const gchar *nickname, const gchar *first_name, const gchar *last_name, const gchar *jid, const gchar *email, const gchar *published_name, const GArray *olpc_key, const gchar *olpc_color) { return g_object_new (SALUT_TYPE_AVAHI_SELF, "connection", connection, "discovery-client", discovery_client, "nickname", nickname, "first-name", first_name, "last-name", last_name, "jid", jid, "email", email, "published-name", published_name, #ifdef ENABLE_OLPC "olpc-key", olpc_key, "olpc-color", olpc_color, #endif NULL); }