/** * 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 "gmyth_upnp_marshal.h" #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 GMYTH_UPNP_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GMYTH_UPNP_TYPE, GMythUPnPPrivate)) struct _GMythUPnPPrivate { GHashTable *mythtv_servers; GMythUPnPDeviceStatus last_status; gboolean upnp_dev_found; gchar *udn; GMutex *mutex; }; 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 void _mythtv_device_found(GMythUPnP * gmyth_upnp, GMythUPnPDeviceStatus status, gchar * dev); static void _clinkc_mythtv_device_found(gchar * udn, GMythUPnPDeviceStatus status); 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); G_DEFINE_TYPE(GMythUPnP, gmyth_upnp, G_TYPE_OBJECT) static GMythUPnP *gmyth_upnp_static = NULL; 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; 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); } static void gmyth_upnp_init(GMythUPnP * gmyth_upnp) { gmyth_upnp->backend_info = NULL; gmyth_upnp->control_point = NULL; gmyth_upnp->priv = GMYTH_UPNP_GET_PRIVATE(gmyth_upnp); gmyth_upnp->priv->mutex = g_mutex_new(); gmyth_upnp->priv->upnp_dev_found = FALSE; 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; } /** Creates a new instance of GMythUPnP. * * @return a new instance of GMythUPnP. */ GMythUPnP * gmyth_upnp_new(GMythBackendInfo * gmyth_backend_info, GMythUPnPDeviceListener handler) { GMythUPnP *gmyth_upnp = GMYTH_UPNP(g_object_new(GMYTH_UPNP_TYPE, NULL)); g_object_ref(gmyth_backend_info); gmyth_upnp->backend_info = gmyth_backend_info; if (!gmyth_upnp_initialize(gmyth_upnp, gmyth_backend_info, handler)) { gmyth_debug("Error initializing the GMythUPnP!!!"); } return gmyth_upnp; } 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; } 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); } gchar * gmyth_upnp_device_status_to_string(GMythUPnPDeviceStatus status) { if (status == CgUpnpDeviceStatusAdded) return "Added"; else if (status == CgUpnpDeviceStatusUpdated) return "Updated"; else if (status == CgUpnpDeviceStatusInvalid) return "Invalid"; else if (status == CgUpnpDeviceStatusRemoved) return "Removed"; return ""; } /** * GObject's signal handler */ static void _mythtv_device_found(GMythUPnP * gmyth_upnp, GMythUPnPDeviceStatus status, gchar * udn) { g_debug("Device: [status = %s, UDN = %s]\n", gmyth_upnp_device_status_to_string(status), udn); } /** * GObject's signal handler */ static void _clinkc_mythtv_device_found(gchar * udn, GMythUPnPDeviceStatus status) { 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); } /** * Create a control point and start it. */ static gboolean gmyth_upnp_initialize(GMythUPnP * gmyth_upnp, GMythBackendInfo * gmyth_backend_info, GMythUPnPDeviceListener device_found_handler) { gboolean ret = TRUE; g_return_val_if_fail(gmyth_backend_info != NULL, FALSE); /* * Create the cybergarage control point */ gmyth_upnp->control_point = cg_upnp_controlpoint_new(); if (device_found_handler != NULL) { GMYTH_UPNP_GET_CLASS(gmyth_upnp)->device_found_handler = device_found_handler; cg_upnp_controlpoint_setdevicelistener(gmyth_upnp->control_point, _clinkc_mythtv_device_found); } /* * 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: return ret; } static void _gmyth_foreach_key_value(gchar * udn, gchar * dev, GList * upnp_servers_list) { GMythUPnPDevice *gmyth_upnp = g_malloc0(sizeof(GMythUPnPDevice)); GMythURI *uri = NULL; gmyth_upnp->uri = (gchar *) (dev); uri = gmyth_uri_new_with_value(gmyth_upnp->uri); gmyth_upnp->host = gmyth_uri_get_host(uri); gmyth_upnp->port = gmyth_uri_get_port(uri); gmyth_upnp->protocol = gmyth_uri_get_protocol(uri); gmyth_debug("MythTV UPnP service [ %s, %d ].", gmyth_upnp->host, gmyth_upnp->port); upnp_servers_list = g_list_append(upnp_servers_list, gmyth_upnp); if (uri != NULL) { g_object_unref(uri); uri = NULL; } } GList * gmyth_upnp_do_search_sync(GMythUPnP * gmyth_upnp) { GList *upnp_servers_list = NULL; guint iter_count = GMYTH_UPNP_MAX_SEARCHS; /* * gmyth_upnp->priv = GMYTH_UPNP_GET_PRIVATE( gmyth_upnp ); */ while (gmyth_upnp->priv->upnp_dev_found == FALSE && (--iter_count > 0)) { gmyth_debug ("UPnP MythTV Client control point is searching MythTV AV Device server...\n"); 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; } /** * 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) { g_return_val_if_fail(mythtv_servers_lst != NULL, FALSE); g_return_val_if_fail(controlPt != NULL, FALSE); *mythtv_servers_lst = g_hash_table_new(g_str_hash, g_str_equal); 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; gmyth_debug("UPnP MythTV AV Device list size = %d\n", numDevices); 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); } ++cntDevs; } 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; } /** 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; }