/** * 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 * @authon Renato Araujo Oliveira Filho * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; 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; }