melunko@250: /** melunko@250: * GMyth Library melunko@250: * melunko@250: * @file gmyth/gmyth_upnp.c melunko@250: * melunko@250: * @brief

GMythUPnP allows that a MythTV frontend discovers a melunko@250: * MythTV backend, using the UPnP architecture. melunko@250: * melunko@250: * Copyright (C) 2006 INdT - Instituto Nokia de Tecnologia. melunko@250: * @author Rosfran Lins Borges renatofilho@922: * @authon Renato Araujo Oliveira Filho melunko@250: * renatofilho@922: * This library is free software; you can redistribute it and/or renatofilho@922: * modify it under the terms of the GNU Library General Public renatofilho@922: * License as published by the Free Software Foundation; either renatofilho@922: * version 2 of the License, or (at your option) any later version. renatofilho@922: * renatofilho@922: * This library is distributed in the hope that it will be useful, melunko@250: * but WITHOUT ANY WARRANTY; without even the implied warranty of renatofilho@922: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU renatofilho@922: * Library General Public License for more details. melunko@250: * renatofilho@922: * You should have received a copy of the GNU Library General Public renatofilho@922: * License along with this library; if not, write to the renatofilho@922: * Free Software Foundation, Inc., 59 Temple Place - Suite 330, renatofilho@922: * Boston, MA 02111-1307, USA. melunko@250: */ melunko@250: melunko@250: #ifdef HAVE_CONFIG_H melunko@250: #include "config.h" melunko@250: #endif melunko@250: melunko@250: #include "gmyth_upnp.h" melunko@250: renatofilho@909: #include renatofilho@909: #include renatofilho@909: #include melunko@250: melunko@250: renatofilho@909: #define UPNP_SEARCH_TIMEOUT 5 renatofilho@909: #define UPNP_SERVICE_FILTER "urn:schemas-mythtv-org:service:MythTv:1" renatofilho@909: #define SERVER_ID "MythTV AV Media Server" renatofilho@909: renatofilho@909: typedef struct _GMythUPnPPrivate GMythUPnPPrivate; renatofilho@909: typedef struct _GMythUPnPIdleData GMythUPnPIdleData; melunko@250: rosfran@696: #define GMYTH_UPNP_GET_PRIVATE(obj) \ rosfran@696: (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GMYTH_UPNP_TYPE, GMythUPnPPrivate)) rosfran@696: renatofilho@909: enum renatofilho@909: { renatofilho@909: DEVICE_FOUND, renatofilho@909: DEVICE_LOST, renatofilho@909: LAST_SIGNAL renatofilho@909: }; renatofilho@909: renatofilho@909: struct _GMythUPnPIdleData renatofilho@909: { renatofilho@909: GMythUPnP *parent; renatofilho@909: GMythBackendInfo *server; renatofilho@909: }; renatofilho@909: rosfran@696: struct _GMythUPnPPrivate { renatofilho@909: GHashTable *servers; renatofilho@754: GMythUPnPDeviceStatus last_status; renatofilho@754: gboolean upnp_dev_found; renatofilho@754: gchar *udn; renatofilho@754: GMutex *mutex; renatofilho@909: gint idle_count; renatofilho@909: renatofilho@909: /* upnp */ renatofilho@909: UpnpClient_Handle client_id; rosfran@696: }; rosfran@696: renatofilho@909: static void gmyth_upnp_class_init (GMythUPnPClass* klass); renatofilho@909: static void gmyth_upnp_init (GMythUPnP* object); renatofilho@909: static void gmyth_upnp_dispose (GObject* object); renatofilho@909: static void gmyth_upnp_finalize (GObject* object); renatofilho@909: static GObject* gmyth_upnp_constructor (GType type, renatofilho@909: guint n_construct_params, renatofilho@909: GObjectConstructParam *construct_params); melunko@250: melunko@250: renatofilho@909: static int _upnp_event_handler (Upnp_EventType e_type, renatofilho@909: void* e, renatofilho@909: void* data); melunko@250: rosfran@696: renatofilho@909: static int signals[LAST_SIGNAL] = {0}; melunko@250: renatofilho@909: static GMythUPnP *singleton = NULL; melunko@250: renatofilho@909: G_DEFINE_TYPE(GMythUPnP, gmyth_upnp, G_TYPE_OBJECT); melunko@250: renatofilho@909: static void renatofilho@909: gmyth_upnp_class_init(GMythUPnPClass * klass) melunko@250: { renatofilho@754: GObjectClass *gobject_class; renatofilho@754: GMythUPnPClass *gupnp_class; melunko@250: renatofilho@754: gobject_class = (GObjectClass *) klass; renatofilho@754: gupnp_class = (GMythUPnPClass *) gobject_class; melunko@250: renatofilho@754: gobject_class->dispose = gmyth_upnp_dispose; renatofilho@754: gobject_class->finalize = gmyth_upnp_finalize; renatofilho@909: gobject_class->constructor = gmyth_upnp_constructor; rosfran@696: renatofilho@909: g_type_class_add_private (gobject_class, sizeof(GMythUPnPPrivate)); rosfran@696: renatofilho@754: renatofilho@909: renatofilho@909: signals[DEVICE_FOUND] = g_signal_new("device-found", renatofilho@909: G_TYPE_FROM_CLASS(gupnp_class), renatofilho@909: G_SIGNAL_RUN_LAST, renatofilho@909: 0, NULL, NULL, renatofilho@909: g_cclosure_marshal_VOID__OBJECT, renatofilho@909: G_TYPE_NONE, 1, renatofilho@909: GMYTH_BACKEND_INFO_TYPE); renatofilho@909: renatofilho@909: signals[DEVICE_LOST] = g_signal_new("device-lost", renatofilho@909: G_TYPE_FROM_CLASS(gupnp_class), renatofilho@909: G_SIGNAL_RUN_LAST, renatofilho@909: 0, NULL, NULL, renatofilho@909: g_cclosure_marshal_VOID__OBJECT, renatofilho@909: G_TYPE_NONE, 1, renatofilho@909: GMYTH_BACKEND_INFO_TYPE); rosfran@696: melunko@250: } melunko@250: melunko@250: static void renatofilho@909: gmyth_upnp_init(GMythUPnP* self) melunko@250: { renatofilho@909: gint ret; renatofilho@909: GMythUPnPPrivate *priv; rosfran@696: renatofilho@909: priv = GMYTH_UPNP_GET_PRIVATE (self); rosfran@696: renatofilho@909: priv->mutex = g_mutex_new (); renatofilho@909: priv->servers = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); rosfran@696: renatofilho@909: /* initalize upnp client */ renatofilho@909: ret = UpnpInit (NULL, 0); renatofilho@909: if (ret != UPNP_E_SUCCESS) renatofilho@909: g_warning ("Fail to inilialize upnp SDK: %d", ret); renatofilho@909: else renatofilho@909: { renatofilho@909: ret = UpnpRegisterClient (_upnp_event_handler, renatofilho@909: &priv->client_id, &priv->client_id); rosfran@696: renatofilho@909: if (ret != UPNP_E_SUCCESS) renatofilho@909: g_warning ("Fail to start upnp client: %d", ret); renatofilho@909: } melunko@250: } melunko@250: renatofilho@909: static GObject* renatofilho@909: gmyth_upnp_constructor (GType type, renatofilho@909: guint n_construct_params, renatofilho@909: GObjectConstructParam *construct_params) melunko@250: { renatofilho@909: GObject *object; renatofilho@754: renatofilho@909: if (!singleton) renatofilho@909: { renatofilho@909: object = G_OBJECT_CLASS (gmyth_upnp_parent_class)->constructor (type, renatofilho@909: n_construct_params, renatofilho@909: construct_params); rosfran@696: renatofilho@909: singleton = GMYTH_UPNP (object); renatofilho@909: } renatofilho@909: else renatofilho@909: object = g_object_ref (G_OBJECT (singleton)); rosfran@696: renatofilho@909: return object; melunko@250: } melunko@250: melunko@250: static void renatofilho@754: gmyth_upnp_dispose(GObject * object) melunko@250: { renatofilho@909: /* finalize upnp client */ renatofilho@909: UpnpFinish (); renatofilho@754: G_OBJECT_CLASS(gmyth_upnp_parent_class)->dispose(object); melunko@250: } melunko@250: melunko@250: static void renatofilho@754: gmyth_upnp_finalize(GObject * object) melunko@250: { renatofilho@754: G_OBJECT_CLASS(gmyth_upnp_parent_class)->finalize(object); renatofilho@909: singleton = NULL; melunko@250: } melunko@250: renatofilho@909: renatofilho@909: GMythUPnP* renatofilho@909: gmyth_upnp_get_instance (void) rosfran@706: { renatofilho@909: return GMYTH_UPNP(g_object_new(GMYTH_UPNP_TYPE, NULL)); rosfran@696: } rosfran@696: renatofilho@909: renatofilho@909: void renatofilho@909: gmyth_upnp_search (GMythUPnP *self) rosfran@696: { renatofilho@909: int ret; renatofilho@909: GMythUPnPPrivate *priv; renatofilho@909: renatofilho@909: priv = GMYTH_UPNP_GET_PRIVATE (self); renatofilho@909: renatofilho@909: ret = UpnpSearchAsync (priv->client_id, renatofilho@909: UPNP_SEARCH_TIMEOUT, renatofilho@909: UPNP_SERVICE_FILTER, renatofilho@909: NULL); renatofilho@909: renatofilho@909: if (ret != UPNP_E_SUCCESS) renatofilho@909: g_warning ("Fail to start upnp listener: %d", ret); renatofilho@754: } renatofilho@754: renatofilho@915: static void renatofilho@915: _fill_servers_cb (gpointer key, renatofilho@915: gpointer value, renatofilho@915: gpointer user_data) renatofilho@915: { renatofilho@915: GList **lst; renatofilho@915: renatofilho@915: lst = (GList **) user_data; renatofilho@915: renatofilho@915: *lst = g_list_append (*lst, g_object_ref (value)); renatofilho@915: } renatofilho@915: renatofilho@915: GList* renatofilho@915: gmyth_upnp_get_devices (GMythUPnP *self) renatofilho@915: { renatofilho@915: GMythUPnPPrivate *priv; renatofilho@915: GList *lst; renatofilho@915: renatofilho@915: priv = GMYTH_UPNP_GET_PRIVATE (self); renatofilho@915: lst = NULL; renatofilho@915: g_hash_table_foreach (priv->servers, (GHFunc) _fill_servers_cb, &lst); renatofilho@915: renatofilho@915: return lst; renatofilho@915: } renatofilho@915: renatofilho@909: static gboolean renatofilho@909: _idle_emit_device_found_signal (gpointer data) renatofilho@754: { renatofilho@909: GMythUPnPPrivate *priv; renatofilho@909: GMythUPnPIdleData *idle_data; renatofilho@909: renatofilho@909: idle_data = (GMythUPnPIdleData *) data; renatofilho@909: priv = GMYTH_UPNP_GET_PRIVATE (idle_data->parent); renatofilho@909: renatofilho@909: g_signal_emit (idle_data->parent, signals[DEVICE_FOUND], 0, idle_data->server); renatofilho@909: renatofilho@909: g_object_unref (idle_data->server); renatofilho@909: g_free (idle_data); renatofilho@909: priv->idle_count--; renatofilho@909: renatofilho@909: return FALSE; rosfran@696: } rosfran@696: renatofilho@909: static gboolean renatofilho@909: _idle_emit_device_lost_signal (gpointer data) melunko@250: { renatofilho@909: GMythUPnPPrivate *priv; renatofilho@909: GMythUPnPIdleData *idle_data; rosfran@696: renatofilho@909: idle_data = (GMythUPnPIdleData *) data; renatofilho@909: priv = GMYTH_UPNP_GET_PRIVATE (idle_data->parent); rosfran@696: renatofilho@909: g_signal_emit (idle_data->parent, signals[DEVICE_LOST], 0, idle_data->server); rosfran@696: renatofilho@909: g_object_unref (idle_data->server); renatofilho@909: g_free (idle_data); renatofilho@909: priv->idle_count--; rosfran@696: renatofilho@909: return FALSE; renatofilho@909: } renatofilho@909: renatofilho@909: static char* renatofilho@909: _xml_get_first_document_item (IXML_Document * doc, renatofilho@909: const gchar *item ) renatofilho@909: { renatofilho@909: IXML_NodeList *node_list = NULL; renatofilho@909: IXML_Node *text_node = NULL; renatofilho@909: IXML_Node *tmp_node = NULL; renatofilho@909: renatofilho@909: gchar *ret = NULL; renatofilho@909: renatofilho@909: node_list = ixmlDocument_getElementsByTagName (doc, renatofilho@909: (char *) item); renatofilho@909: renatofilho@909: if (node_list) renatofilho@909: { renatofilho@909: if ((tmp_node = ixmlNodeList_item (node_list, 0))) renatofilho@909: { renatofilho@909: text_node = ixmlNode_getFirstChild (tmp_node); renatofilho@909: renatofilho@909: ret = strdup (ixmlNode_getNodeValue (text_node)); renatofilho@909: } renatofilho@754: } rosfran@696: renatofilho@909: if (node_list) renatofilho@909: ixmlNodeList_free (node_list); rosfran@696: renatofilho@754: return ret; rosfran@696: } rosfran@696: renatofilho@909: rosfran@696: static void renatofilho@909: _append_mythtv_server_from_loation (GMythUPnP *self, renatofilho@909: const gchar *uuid, renatofilho@909: const gchar *location) rosfran@696: { renatofilho@909: GMythUPnPPrivate *priv; renatofilho@909: gchar *base_url; renatofilho@909: gchar *end; rosfran@696: renatofilho@909: priv = GMYTH_UPNP_GET_PRIVATE (self); rosfran@696: renatofilho@909: base_url = g_strdup (location); renatofilho@909: end = g_strrstr (base_url, "/"); renatofilho@909: if (end) renatofilho@909: { renatofilho@909: gint ret; renatofilho@909: IXML_Document *desc_doc; renatofilho@909: gchar *info_url; rosfran@696: renatofilho@909: end[0] = '\0'; renatofilho@909: info_url = g_strconcat (base_url, renatofilho@909: "Myth/GetConnectionInfo", renatofilho@909: NULL); renatofilho@909: g_free (base_url); renatofilho@909: desc_doc = NULL; renatofilho@909: ret = UpnpDownloadXmlDoc (info_url, &desc_doc); renatofilho@909: if (ret != UPNP_E_SUCCESS) renatofilho@909: { renatofilho@909: g_warning ("Error obtaining device desc: %d", ret); renatofilho@909: } renatofilho@909: else renatofilho@909: { renatofilho@909: GMythBackendInfo *info; renatofilho@909: GMythUPnPIdleData *idle_data; rosfran@706: renatofilho@909: info = gmyth_backend_info_new_full ( renatofilho@909: _xml_get_first_document_item (desc_doc, "Host"), renatofilho@909: _xml_get_first_document_item (desc_doc, "UserName"), renatofilho@909: _xml_get_first_document_item (desc_doc, "Password"), renatofilho@909: _xml_get_first_document_item (desc_doc, "Name"), renatofilho@909: // Current mythtv version not export port number renatofilho@909: 6543); rosfran@696: renatofilho@909: if (desc_doc) renatofilho@909: ixmlDocument_free (desc_doc); renatofilho@909: renatofilho@909: g_mutex_lock (priv->mutex); renatofilho@909: g_hash_table_insert (priv->servers, renatofilho@909: g_strdup (uuid), renatofilho@909: g_object_ref (info)); renatofilho@909: g_mutex_unlock (priv->mutex); renatofilho@909: g_free (info_url); renatofilho@909: renatofilho@909: idle_data = g_new0 (GMythUPnPIdleData, 1); renatofilho@909: idle_data->parent = self; renatofilho@909: idle_data->server = g_object_ref (info); renatofilho@909: renatofilho@909: priv->idle_count++; renatofilho@909: g_idle_add (_idle_emit_device_found_signal, idle_data); renatofilho@909: } renatofilho@754: } renatofilho@909: } renatofilho@909: renatofilho@909: static void renatofilho@909: _remove_mythtv_server (GMythUPnP *self, renatofilho@909: const gchar *uuid) renatofilho@909: { renatofilho@909: GMythUPnPPrivate *priv; renatofilho@909: GMythBackendInfo *info; renatofilho@909: renatofilho@909: priv = GMYTH_UPNP_GET_PRIVATE (self); renatofilho@909: renatofilho@909: g_mutex_lock (priv->mutex); renatofilho@909: info = g_hash_table_lookup (priv->servers, uuid); renatofilho@909: if (info) renatofilho@909: { renatofilho@909: GMythUPnPIdleData *idle_data; renatofilho@909: renatofilho@909: idle_data = g_new0 (GMythUPnPIdleData, 1); renatofilho@909: idle_data->parent = self; renatofilho@909: idle_data->server = g_object_ref (info); renatofilho@909: renatofilho@909: g_hash_table_remove (priv->servers, uuid); renatofilho@909: renatofilho@909: priv->idle_count++; renatofilho@909: g_idle_add (_idle_emit_device_lost_signal, idle_data); renatofilho@909: } renatofilho@909: g_mutex_unlock (priv->mutex); rosfran@696: rosfran@696: } rosfran@696: renatofilho@909: static GMythBackendInfo* renatofilho@909: _find_service_by_uuid (GMythUPnP *self, renatofilho@909: const gchar *uuid) rosfran@696: { renatofilho@909: GMythUPnPPrivate *priv; renatofilho@909: GMythBackendInfo *info; rosfran@696: renatofilho@909: priv = GMYTH_UPNP_GET_PRIVATE (self); renatofilho@909: info = NULL; rosfran@696: renatofilho@909: g_mutex_lock (priv->mutex); renatofilho@909: info = g_hash_table_lookup (priv->servers, uuid); renatofilho@909: g_mutex_unlock (priv->mutex); rosfran@696: renatofilho@909: return info; melunko@250: } melunko@250: renatofilho@909: static int renatofilho@909: _upnp_event_handler (Upnp_EventType e_type, renatofilho@909: void* e, renatofilho@909: void* data) melunko@250: { renatofilho@909: g_return_val_if_fail (singleton != NULL, 0); rosfran@696: renatofilho@909: switch (e_type) renatofilho@909: { renatofilho@909: case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE: renatofilho@909: case UPNP_DISCOVERY_SEARCH_RESULT: renatofilho@909: { renatofilho@909: struct Upnp_Discovery *d_event; renatofilho@754: renatofilho@909: d_event = (struct Upnp_Discovery *) e; renatofilho@754: renatofilho@909: if (strcmp (d_event->ServiceType, UPNP_SERVICE_FILTER) != 0) renatofilho@909: { renatofilho@915: g_warning ("invalid device : %s", d_event->DeviceId); renatofilho@909: break; renatofilho@909: } renatofilho@754: renatofilho@909: renatofilho@909: if (d_event->ErrCode != UPNP_E_SUCCESS) renatofilho@909: { renatofilho@909: g_warning ("Error in Discovery: %d", d_event->ErrCode); renatofilho@909: break; renatofilho@909: } renatofilho@909: renatofilho@909: if (_find_service_by_uuid (GMYTH_UPNP (singleton), d_event->DeviceId) == NULL) renatofilho@909: _append_mythtv_server_from_loation (singleton, renatofilho@909: d_event->DeviceId, renatofilho@909: d_event->Location); renatofilho@909: renatofilho@909: renatofilho@909: break; renatofilho@754: } renatofilho@909: case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE: renatofilho@909: { renatofilho@909: GMythUPnPPrivate *priv; renatofilho@909: struct Upnp_Discovery *d_event; renatofilho@909: renatofilho@909: d_event = (struct Upnp_Discovery *) e; renatofilho@909: if (d_event->ErrCode != UPNP_E_SUCCESS) renatofilho@909: { renatofilho@909: g_warning ("Error in Discovery: %d", d_event->ErrCode); renatofilho@909: break; renatofilho@909: } renatofilho@909: renatofilho@909: priv = GMYTH_UPNP_GET_PRIVATE (singleton); renatofilho@909: _remove_mythtv_server (singleton, renatofilho@909: d_event->DeviceId); renatofilho@909: renatofilho@909: break; renatofilho@909: renatofilho@909: } renatofilho@909: default: renatofilho@909: break; renatofilho@754: } renatofilho@754: renatofilho@909: return 0; melunko@250: } melunko@250: