diff -r cb885ee44618 -r da7ce603d47f gmyth-upnp/src/gmyth_upnp.c --- a/gmyth-upnp/src/gmyth_upnp.c Thu Jun 14 20:40:47 2007 +0100 +++ b/gmyth-upnp/src/gmyth_upnp.c Mon Feb 04 18:23:49 2008 +0000 @@ -30,64 +30,69 @@ #endif #include "gmyth_upnp.h" -#include "gmyth_upnp_marshal.h" -#include -#include -#include -#include -#include -#include +#include +#include +#include -#include -#include -#include -#include -/* - * Maximum number of searches in the synchronized search - */ -#define GMYTH_UPNP_MAX_SEARCHS 10 +#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 *mythtv_servers; + 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_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 void gmyth_upnp_dispose(GObject * object); -static void gmyth_upnp_finalize(GObject * object); -static void _mythtv_device_found(GMythUPnP * gmyth_upnp, - GMythUPnPDeviceStatus status, - gchar * dev); -static void _clinkc_mythtv_device_found(gchar * udn, - GMythUPnPDeviceStatus status); +static int _upnp_event_handler (Upnp_EventType e_type, + void* e, + void* data); -static gboolean gmyth_upnp_initialize(GMythUPnP * gmyth_upnp, - GMythBackendInfo * - gmyth_backend_info, - GMythUPnPDeviceListener listener); -static gboolean gmyth_upnp_got_mythtv_service(CgUpnpControlPoint * - controlPt, gchar ** udn, - GHashTable ** - mythtv_servers_lst); +static int signals[LAST_SIGNAL] = {0}; -G_DEFINE_TYPE(GMythUPnP, gmyth_upnp, G_TYPE_OBJECT) +static GMythUPnP *singleton = NULL; - static GMythUPnP *gmyth_upnp_static = NULL; +G_DEFINE_TYPE(GMythUPnP, gmyth_upnp, G_TYPE_OBJECT); - static void - gmyth_upnp_class_init(GMythUPnPClass * klass) +static void +gmyth_upnp_class_init(GMythUPnPClass * klass) { GObjectClass *gobject_class; GMythUPnPClass *gupnp_class; @@ -97,347 +102,354 @@ 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)); + g_type_class_add_private (gobject_class, sizeof(GMythUPnPPrivate)); - gupnp_class->device_found_handler = _mythtv_device_found; - gupnp_class->device_found_handler_signal_id = - g_signal_new("device-found", - G_TYPE_FROM_CLASS(gupnp_class), - G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | - G_SIGNAL_NO_HOOKS, 0, NULL, NULL, - gmyth_upnp_marshal_VOID__INT_STRING, G_TYPE_NONE, 2, - G_TYPE_INT, G_TYPE_STRING); + + 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 * gmyth_upnp) +gmyth_upnp_init(GMythUPnP* self) { + gint ret; + GMythUPnPPrivate *priv; - gmyth_upnp->backend_info = NULL; + priv = GMYTH_UPNP_GET_PRIVATE (self); - gmyth_upnp->control_point = NULL; + priv->mutex = g_mutex_new (); + priv->servers = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); - gmyth_upnp->priv = GMYTH_UPNP_GET_PRIVATE(gmyth_upnp); - gmyth_upnp->priv->mutex = g_mutex_new(); - gmyth_upnp->priv->upnp_dev_found = FALSE; + /* 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); - g_signal_connect(G_OBJECT(gmyth_upnp), "device-found", - (GCallback) (GMYTH_UPNP_GET_CLASS(gmyth_upnp)-> - device_found_handler), NULL); - - gmyth_upnp_static = gmyth_upnp; - + if (ret != UPNP_E_SUCCESS) + g_warning ("Fail to start upnp client: %d", ret); + } } -/** Creates a new instance of GMythUPnP. - * - * @return a new instance of GMythUPnP. - */ -GMythUPnP * -gmyth_upnp_new(GMythBackendInfo * gmyth_backend_info, - GMythUPnPDeviceListener handler) +static GObject* +gmyth_upnp_constructor (GType type, + guint n_construct_params, + GObjectConstructParam *construct_params) { - GMythUPnP *gmyth_upnp = - GMYTH_UPNP(g_object_new(GMYTH_UPNP_TYPE, NULL)); + GObject *object; - g_object_ref(gmyth_backend_info); + if (!singleton) + { + object = G_OBJECT_CLASS (gmyth_upnp_parent_class)->constructor (type, + n_construct_params, + construct_params); - gmyth_upnp->backend_info = gmyth_backend_info; + singleton = GMYTH_UPNP (object); + } + else + object = g_object_ref (G_OBJECT (singleton)); - if (!gmyth_upnp_initialize(gmyth_upnp, gmyth_backend_info, handler)) { - gmyth_debug("Error initializing the GMythUPnP!!!"); - } - - return gmyth_upnp; + return object; } static void gmyth_upnp_dispose(GObject * object) { - GMythUPnP *gmyth_upnp = GMYTH_UPNP(object); - - if (gmyth_upnp->control_point != NULL) { - cg_upnp_controlpoint_stop(gmyth_upnp->control_point); - cg_upnp_controlpoint_delete(gmyth_upnp->control_point); - gmyth_upnp->control_point = NULL; - } - - if (gmyth_upnp->priv->mythtv_servers != NULL) { - g_hash_table_destroy(gmyth_upnp->priv->mythtv_servers); - gmyth_upnp->priv->mythtv_servers = NULL; - } - - if (gmyth_upnp->backend_info != NULL) { - g_object_unref(gmyth_upnp->backend_info); - gmyth_upnp->backend_info = NULL; - } - + /* finalize upnp client */ + UpnpFinish (); G_OBJECT_CLASS(gmyth_upnp_parent_class)->dispose(object); } static void gmyth_upnp_finalize(GObject * object) { - g_signal_handlers_destroy(object); - G_OBJECT_CLASS(gmyth_upnp_parent_class)->finalize(object); + singleton = NULL; } -gchar * -gmyth_upnp_device_status_to_string(GMythUPnPDeviceStatus status) + +GMythUPnP* +gmyth_upnp_get_instance (void) { - if (status == CgUpnpDeviceStatusAdded) - return "Added"; - else if (status == CgUpnpDeviceStatusUpdated) - return "Updated"; - else if (status == CgUpnpDeviceStatusInvalid) - return "Invalid"; - else if (status == CgUpnpDeviceStatusRemoved) - return "Removed"; - - return ""; + return GMYTH_UPNP(g_object_new(GMYTH_UPNP_TYPE, NULL)); } -/** - * GObject's signal handler - */ -static void -_mythtv_device_found(GMythUPnP * gmyth_upnp, GMythUPnPDeviceStatus status, - gchar * udn) + +void +gmyth_upnp_search (GMythUPnP *self) { - g_debug("Device: [status = %s, UDN = %s]\n", - gmyth_upnp_device_status_to_string(status), udn); + 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); } -/** - * GObject's signal handler - */ -static void -_clinkc_mythtv_device_found(gchar * udn, GMythUPnPDeviceStatus status) +static gboolean +_idle_emit_device_found_signal (gpointer data) { - if (gmyth_upnp_static != NULL && udn != NULL) - g_signal_emit(gmyth_upnp_static, GMYTH_UPNP_GET_CLASS(gmyth_upnp_static)->device_found_handler_signal_id, 0, /* details - */ - status, udn); + 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; } -/** - * Create a control point and start it. - */ -static gboolean -gmyth_upnp_initialize(GMythUPnP * gmyth_upnp, - GMythBackendInfo * gmyth_backend_info, - GMythUPnPDeviceListener device_found_handler) +static gboolean +_idle_emit_device_lost_signal (gpointer data) { - gboolean ret = TRUE; + GMythUPnPPrivate *priv; + GMythUPnPIdleData *idle_data; - g_return_val_if_fail(gmyth_backend_info != NULL, FALSE); + idle_data = (GMythUPnPIdleData *) data; + priv = GMYTH_UPNP_GET_PRIVATE (idle_data->parent); - /* - * Create the cybergarage control point - */ - gmyth_upnp->control_point = cg_upnp_controlpoint_new(); + g_signal_emit (idle_data->parent, signals[DEVICE_LOST], 0, idle_data->server); - if (device_found_handler != NULL) { - GMYTH_UPNP_GET_CLASS(gmyth_upnp)->device_found_handler = - device_found_handler; + g_object_unref (idle_data->server); + g_free (idle_data); + priv->idle_count--; - cg_upnp_controlpoint_setdevicelistener(gmyth_upnp->control_point, - _clinkc_mythtv_device_found); + 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)); + } } - /* - * Start the control point - */ - if (cg_upnp_controlpoint_start(gmyth_upnp->control_point) == FALSE) { - gmyth_debug("Unable to start UPnP control point!!!"); - ret = FALSE; - goto done; - } else { - gmyth_debug("Control point started."); - } - - done: + if (node_list) + ixmlNodeList_free (node_list); return ret; } + static void -_gmyth_foreach_key_value(gchar * udn, gchar * dev, - GList * upnp_servers_list) +_append_mythtv_server_from_loation (GMythUPnP *self, + const gchar *uuid, + const gchar *location) { - GMythUPnPDevice *gmyth_upnp = g_malloc0(sizeof(GMythUPnPDevice)); + GMythUPnPPrivate *priv; + gchar *base_url; + gchar *end; - GMythURI *uri = NULL; - gmyth_upnp->uri = (gchar *) (dev); - uri = gmyth_uri_new_with_value(gmyth_upnp->uri); + priv = GMYTH_UPNP_GET_PRIVATE (self); - gmyth_upnp->host = gmyth_uri_get_host(uri); - gmyth_upnp->port = gmyth_uri_get_port(uri); - gmyth_upnp->protocol = gmyth_uri_get_protocol(uri); + base_url = g_strdup (location); + end = g_strrstr (base_url, "/"); + if (end) + { + gint ret; + IXML_Document *desc_doc; + gchar *info_url; - gmyth_debug("MythTV UPnP service [ %s, %d ].", gmyth_upnp->host, - gmyth_upnp->port); + 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; - upnp_servers_list = g_list_append(upnp_servers_list, gmyth_upnp); + 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 (uri != NULL) { - g_object_unref(uri); - uri = NULL; + 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_debug ("info url: %s", info_url); + 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); } -GList * -gmyth_upnp_do_search_sync(GMythUPnP * gmyth_upnp) +static GMythBackendInfo* +_find_service_by_uuid (GMythUPnP *self, + const gchar *uuid) { - GList *upnp_servers_list = NULL; - guint iter_count = GMYTH_UPNP_MAX_SEARCHS; - /* - * gmyth_upnp->priv = GMYTH_UPNP_GET_PRIVATE( gmyth_upnp ); - */ + GMythUPnPPrivate *priv; + GMythBackendInfo *info; - while (gmyth_upnp->priv->upnp_dev_found == FALSE && (--iter_count > 0)) { + priv = GMYTH_UPNP_GET_PRIVATE (self); + info = NULL; - gmyth_debug - ("UPnP MythTV Client control point is searching MythTV AV Device server...\n"); + g_mutex_lock (priv->mutex); + info = g_hash_table_lookup (priv->servers, uuid); + g_mutex_unlock (priv->mutex); - if (gmyth_upnp->control_point != NULL) - cg_upnp_controlpoint_search(gmyth_upnp->control_point, - "urn:schemas-upnp-org:service:ContentDirectory:1"); - - /* - * just to avoid clinkc pthread concurrency faults - */ - cg_wait(1000); - - /* - * discover if it was found - */ - gmyth_upnp->priv->upnp_dev_found = - gmyth_upnp_got_mythtv_service(gmyth_upnp->control_point, - &gmyth_upnp->priv->udn, - &gmyth_upnp->priv-> - mythtv_servers); - - } /* while */ - - if (gmyth_upnp->priv->upnp_dev_found) { - - gmyth_debug("Found UPnP MythTV AV Device...\n"); - - g_hash_table_foreach(gmyth_upnp->priv->mythtv_servers, - (GHFunc) _gmyth_foreach_key_value, - upnp_servers_list); - - } - /* - * if - found UPnP device - */ - return upnp_servers_list; + return info; } -/** - * Checks if got the MythTV service in the Control Point's device list. - */ -static gboolean -gmyth_upnp_got_mythtv_service(CgUpnpControlPoint * controlPt, gchar ** udn, - GHashTable ** mythtv_servers_lst) +static int +_upnp_event_handler (Upnp_EventType e_type, + void* e, + void* data) { + g_return_val_if_fail (singleton != NULL, 0); - g_return_val_if_fail(mythtv_servers_lst != NULL, FALSE); - g_return_val_if_fail(controlPt != NULL, FALSE); + switch (e_type) + { + case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE: + case UPNP_DISCOVERY_SEARCH_RESULT: + { + struct Upnp_Discovery *d_event; - *mythtv_servers_lst = g_hash_table_new(g_str_hash, g_str_equal); + d_event = (struct Upnp_Discovery *) e; - const gchar *mythtvFriendlyName = "Myth"; - /* - * begin assertion about the size of discovered devices - */ - gint numDevices = - cg_upnp_controlpoint_getndevices(controlPt); - gint cntDevs = 0; - CgUpnpDevice *childDev; - gchar *devName = NULL, - *dev_url = NULL; - gboolean upnp_dev_found = FALSE; + g_debug ("TYPE: %s", d_event->ServiceType); - gmyth_debug("UPnP MythTV AV Device list size = %d\n", numDevices); + if (strcmp (d_event->ServiceType, UPNP_SERVICE_FILTER) != 0) + { + g_debug ("invalid device : %s", d_event->DeviceId); + break; + } - for (childDev = cg_upnp_controlpoint_getdevices(controlPt); - childDev != NULL; childDev = cg_upnp_device_next(childDev)) { - devName = cg_upnp_device_getfriendlyname(childDev); - dev_url = cg_upnp_device_getlocationfromssdppacket(childDev); - gmyth_debug - ("Device's friendly name = %s, and device's URL = %s\n", - devName, dev_url); - if ((g_strstr_len(devName, strlen(devName), mythtvFriendlyName) != - NULL) == TRUE) { - upnp_dev_found = TRUE; - /* - * stores the last UDN number ID - */ - *udn = cg_upnp_device_getudn(childDev); - /* - *mythtv_servers_lst = g_list_append( *mythtv_servers_lst, dev_url ); */ - g_hash_table_insert(*mythtv_servers_lst, (gpointer) * udn, - (gpointer) dev_url); + + 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; } - ++cntDevs; + 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: + g_debug ("No handle event: %d", e_type); + break; } - if (upnp_dev_found == TRUE) { - gmyth_debug - ("MythTV AV Device found, from a total of %d devices.\n", - cntDevs); - } else if (numDevices == cntDevs) { - gmyth_debug - ("MythTV AV Device not found, from a total of %d devices.\n", - cntDevs); - } else { - gmyth_debug - ("Control Point's MythTV AV Device count is wrong: iterated over %d devices, but there are %d registered devices.\n", - cntDevs, numDevices); - } - - return upnp_dev_found; - + return 0; } -/** Gets the UPnP AV devices server's list associated to this upnp. - * - * @return The GHashTable* containing all the URI values for each recognized UPnP device, - * or NULL if it couldn't recognize any MythTV AV device. - */ -GHashTable * -gmyth_upnp_get_servers(GMythUPnP * gmyth_upnp) -{ - - if (NULL == gmyth_upnp || NULL == gmyth_upnp->priv->mythtv_servers) { - gmyth_debug("[%s] GMythUPnP has no MythTV servers recognized.\n", - __FUNCTION__); - return NULL; - } - - return gmyth_upnp->priv->mythtv_servers; -} - -/** Gets the GMythBackendInfo object associated to this upnp. - * - * @return The GMythBackendInfo object currently valid or NULL if the settings - * were not opened. - */ -GMythBackendInfo * -gmyth_upnp_get_backend_info(GMythUPnP * gmyth_upnp) -{ - - if (NULL == gmyth_upnp || NULL == gmyth_upnp->backend_info) { - gmyth_debug("[%s] GMythUPnP not initialized\n", __FUNCTION__); - return NULL; - } - - return gmyth_upnp->backend_info; -}