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