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
melunko@250: *
melunko@250: * This program is free software; you can redistribute it and/or modify
melunko@250: * it under the terms of the GNU Lesser General Public License as published by
melunko@250: * the Free Software Foundation; either version 2 of the License, or
melunko@250: * (at your option) any later version.
melunko@250: *
melunko@250: * This program is distributed in the hope that it will be useful,
melunko@250: * but WITHOUT ANY WARRANTY; without even the implied warranty of
melunko@250: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
melunko@250: * GNU General Public License for more details.
melunko@250: *
melunko@250: * You should have received a copy of the GNU Lesser General Public License
melunko@250: * along with this program; if not, write to the Free Software
melunko@250: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
melunko@250: *
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: