gmyth-upnp/src/gmyth_upnp.c
author renatofilho
Thu Jun 14 20:40:47 2007 +0100 (2007-06-14)
branchtrunk
changeset 754 cb885ee44618
parent 706 5cc97240e238
child 909 847da7267234
permissions -rw-r--r--
[svn r760] changed code style to Kernel Normal Form style with tabsize=4
     1 /**
     2  * GMyth Library
     3  *
     4  * @file gmyth/gmyth_upnp.c
     5  * 
     6  * @brief <p> GMythUPnP allows that a MythTV frontend discovers a 
     7  * MythTV backend, using the UPnP architecture.
     8  *
     9  * Copyright (C) 2006 INdT - Instituto Nokia de Tecnologia.
    10  * @author Rosfran Lins Borges <rosfran.borges@indt.org.br>
    11  * 
    12  * This program is free software; you can redistribute it and/or modify
    13  * it under the terms of the GNU Lesser General Public License as published by
    14  * the Free Software Foundation; either version 2 of the License, or
    15  * (at your option) any later version.
    16  *
    17  * This program is distributed in the hope that it will be useful,
    18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
    19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    20  * GNU General Public License for more details.
    21  *
    22  * You should have received a copy of the GNU Lesser General Public License
    23  * along with this program; if not, write to the Free Software
    24  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
    25  *   
    26  */
    27 
    28 #ifdef HAVE_CONFIG_H
    29 #include "config.h"
    30 #endif
    31 
    32 #include "gmyth_upnp.h"
    33 #include "gmyth_upnp_marshal.h"
    34 
    35 #include <arpa/inet.h>
    36 #include <sys/types.h>
    37 #include <sys/socket.h>
    38 #include <netdb.h>
    39 #include <errno.h>
    40 #include <stdlib.h>
    41 
    42 #include <gmyth/gmyth_backendinfo.h>
    43 #include <gmyth/gmyth_socket.h>
    44 #include <gmyth/gmyth_uri.h>
    45 #include <gmyth/gmyth_debug.h>
    46 
    47 /*
    48  * Maximum number of searches in the synchronized search 
    49  */
    50 #define GMYTH_UPNP_MAX_SEARCHS		10
    51 
    52 #define GMYTH_UPNP_GET_PRIVATE(obj) \
    53     (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GMYTH_UPNP_TYPE, GMythUPnPPrivate))
    54 
    55 struct _GMythUPnPPrivate {
    56     GHashTable     *mythtv_servers;
    57     GMythUPnPDeviceStatus last_status;
    58     gboolean        upnp_dev_found;
    59     gchar          *udn;
    60     GMutex         *mutex;
    61 };
    62 
    63 static void     gmyth_upnp_class_init(GMythUPnPClass * klass);
    64 static void     gmyth_upnp_init(GMythUPnP * object);
    65 
    66 static void     gmyth_upnp_dispose(GObject * object);
    67 static void     gmyth_upnp_finalize(GObject * object);
    68 
    69 static void     _mythtv_device_found(GMythUPnP * gmyth_upnp,
    70                                      GMythUPnPDeviceStatus status,
    71                                      gchar * dev);
    72 static void     _clinkc_mythtv_device_found(gchar * udn,
    73                                             GMythUPnPDeviceStatus status);
    74 
    75 static gboolean gmyth_upnp_initialize(GMythUPnP * gmyth_upnp,
    76                                       GMythBackendInfo *
    77                                       gmyth_backend_info,
    78                                       GMythUPnPDeviceListener listener);
    79 
    80 static gboolean gmyth_upnp_got_mythtv_service(CgUpnpControlPoint *
    81                                               controlPt, gchar ** udn,
    82                                               GHashTable **
    83                                               mythtv_servers_lst);
    84 
    85 G_DEFINE_TYPE(GMythUPnP, gmyth_upnp, G_TYPE_OBJECT)
    86 
    87     static GMythUPnP *gmyth_upnp_static = NULL;
    88 
    89     static void
    90                     gmyth_upnp_class_init(GMythUPnPClass * klass)
    91 {
    92     GObjectClass   *gobject_class;
    93     GMythUPnPClass *gupnp_class;
    94 
    95     gobject_class = (GObjectClass *) klass;
    96     gupnp_class = (GMythUPnPClass *) gobject_class;
    97 
    98     gobject_class->dispose = gmyth_upnp_dispose;
    99     gobject_class->finalize = gmyth_upnp_finalize;
   100 
   101     g_type_class_add_private(gobject_class, sizeof(GMythUPnPPrivate));
   102 
   103     gupnp_class->device_found_handler = _mythtv_device_found;
   104 
   105     gupnp_class->device_found_handler_signal_id =
   106         g_signal_new("device-found",
   107                      G_TYPE_FROM_CLASS(gupnp_class),
   108                      G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE |
   109                      G_SIGNAL_NO_HOOKS, 0, NULL, NULL,
   110                      gmyth_upnp_marshal_VOID__INT_STRING, G_TYPE_NONE, 2,
   111                      G_TYPE_INT, G_TYPE_STRING);
   112 
   113 }
   114 
   115 static void
   116 gmyth_upnp_init(GMythUPnP * gmyth_upnp)
   117 {
   118 
   119     gmyth_upnp->backend_info = NULL;
   120 
   121     gmyth_upnp->control_point = NULL;
   122 
   123     gmyth_upnp->priv = GMYTH_UPNP_GET_PRIVATE(gmyth_upnp);
   124     gmyth_upnp->priv->mutex = g_mutex_new();
   125     gmyth_upnp->priv->upnp_dev_found = FALSE;
   126 
   127     g_signal_connect(G_OBJECT(gmyth_upnp), "device-found",
   128                      (GCallback) (GMYTH_UPNP_GET_CLASS(gmyth_upnp)->
   129                                   device_found_handler), NULL);
   130 
   131     gmyth_upnp_static = gmyth_upnp;
   132 
   133 }
   134 
   135 /** Creates a new instance of GMythUPnP.
   136  * 
   137  * @return a new instance of GMythUPnP. 
   138  */
   139 GMythUPnP      *
   140 gmyth_upnp_new(GMythBackendInfo * gmyth_backend_info,
   141                GMythUPnPDeviceListener handler)
   142 {
   143     GMythUPnP      *gmyth_upnp =
   144         GMYTH_UPNP(g_object_new(GMYTH_UPNP_TYPE, NULL));
   145 
   146     g_object_ref(gmyth_backend_info);
   147 
   148     gmyth_upnp->backend_info = gmyth_backend_info;
   149 
   150     if (!gmyth_upnp_initialize(gmyth_upnp, gmyth_backend_info, handler)) {
   151         gmyth_debug("Error initializing the GMythUPnP!!!");
   152     }
   153 
   154     return gmyth_upnp;
   155 }
   156 
   157 static void
   158 gmyth_upnp_dispose(GObject * object)
   159 {
   160     GMythUPnP      *gmyth_upnp = GMYTH_UPNP(object);
   161 
   162     if (gmyth_upnp->control_point != NULL) {
   163         cg_upnp_controlpoint_stop(gmyth_upnp->control_point);
   164         cg_upnp_controlpoint_delete(gmyth_upnp->control_point);
   165         gmyth_upnp->control_point = NULL;
   166     }
   167 
   168     if (gmyth_upnp->priv->mythtv_servers != NULL) {
   169         g_hash_table_destroy(gmyth_upnp->priv->mythtv_servers);
   170         gmyth_upnp->priv->mythtv_servers = NULL;
   171     }
   172 
   173     if (gmyth_upnp->backend_info != NULL) {
   174         g_object_unref(gmyth_upnp->backend_info);
   175         gmyth_upnp->backend_info = NULL;
   176     }
   177 
   178     G_OBJECT_CLASS(gmyth_upnp_parent_class)->dispose(object);
   179 }
   180 
   181 static void
   182 gmyth_upnp_finalize(GObject * object)
   183 {
   184     g_signal_handlers_destroy(object);
   185 
   186     G_OBJECT_CLASS(gmyth_upnp_parent_class)->finalize(object);
   187 }
   188 
   189 gchar          *
   190 gmyth_upnp_device_status_to_string(GMythUPnPDeviceStatus status)
   191 {
   192     if (status == CgUpnpDeviceStatusAdded)
   193         return "Added";
   194     else if (status == CgUpnpDeviceStatusUpdated)
   195         return "Updated";
   196     else if (status == CgUpnpDeviceStatusInvalid)
   197         return "Invalid";
   198     else if (status == CgUpnpDeviceStatusRemoved)
   199         return "Removed";
   200 
   201     return "";
   202 }
   203 
   204 /**
   205  * GObject's signal handler
   206  */
   207 static void
   208 _mythtv_device_found(GMythUPnP * gmyth_upnp, GMythUPnPDeviceStatus status,
   209                      gchar * udn)
   210 {
   211     g_debug("Device: [status = %s, UDN = %s]\n",
   212             gmyth_upnp_device_status_to_string(status), udn);
   213 }
   214 
   215 /**
   216  * GObject's signal handler
   217  */
   218 static void
   219 _clinkc_mythtv_device_found(gchar * udn, GMythUPnPDeviceStatus status)
   220 {
   221     if (gmyth_upnp_static != NULL && udn != NULL)
   222         g_signal_emit(gmyth_upnp_static, GMYTH_UPNP_GET_CLASS(gmyth_upnp_static)->device_found_handler_signal_id, 0,    /* details 
   223                                                                                                                          */
   224                       status, udn);
   225 }
   226 
   227 /**
   228  * Create a control point and start it.
   229  */
   230 static          gboolean
   231 gmyth_upnp_initialize(GMythUPnP * gmyth_upnp,
   232                       GMythBackendInfo * gmyth_backend_info,
   233                       GMythUPnPDeviceListener device_found_handler)
   234 {
   235     gboolean        ret = TRUE;
   236 
   237     g_return_val_if_fail(gmyth_backend_info != NULL, FALSE);
   238 
   239     /*
   240      * Create the cybergarage control point 
   241      */
   242     gmyth_upnp->control_point = cg_upnp_controlpoint_new();
   243 
   244     if (device_found_handler != NULL) {
   245         GMYTH_UPNP_GET_CLASS(gmyth_upnp)->device_found_handler =
   246             device_found_handler;
   247 
   248         cg_upnp_controlpoint_setdevicelistener(gmyth_upnp->control_point,
   249                                                _clinkc_mythtv_device_found);
   250     }
   251 
   252     /*
   253      * Start the control point 
   254      */
   255     if (cg_upnp_controlpoint_start(gmyth_upnp->control_point) == FALSE) {
   256         gmyth_debug("Unable to start UPnP control point!!!");
   257         ret = FALSE;
   258         goto done;
   259     } else {
   260         gmyth_debug("Control point started.");
   261     }
   262 
   263   done:
   264 
   265     return ret;
   266 }
   267 
   268 static void
   269 _gmyth_foreach_key_value(gchar * udn, gchar * dev,
   270                          GList * upnp_servers_list)
   271 {
   272     GMythUPnPDevice *gmyth_upnp = g_malloc0(sizeof(GMythUPnPDevice));
   273 
   274     GMythURI       *uri = NULL;
   275     gmyth_upnp->uri = (gchar *) (dev);
   276     uri = gmyth_uri_new_with_value(gmyth_upnp->uri);
   277 
   278     gmyth_upnp->host = gmyth_uri_get_host(uri);
   279     gmyth_upnp->port = gmyth_uri_get_port(uri);
   280     gmyth_upnp->protocol = gmyth_uri_get_protocol(uri);
   281 
   282     gmyth_debug("MythTV UPnP service [ %s, %d ].", gmyth_upnp->host,
   283                 gmyth_upnp->port);
   284 
   285     upnp_servers_list = g_list_append(upnp_servers_list, gmyth_upnp);
   286 
   287     if (uri != NULL) {
   288         g_object_unref(uri);
   289         uri = NULL;
   290     }
   291 
   292 }
   293 
   294 GList          *
   295 gmyth_upnp_do_search_sync(GMythUPnP * gmyth_upnp)
   296 {
   297     GList          *upnp_servers_list = NULL;
   298     guint           iter_count = GMYTH_UPNP_MAX_SEARCHS;
   299     /*
   300      * gmyth_upnp->priv = GMYTH_UPNP_GET_PRIVATE( gmyth_upnp ); 
   301      */
   302 
   303     while (gmyth_upnp->priv->upnp_dev_found == FALSE && (--iter_count > 0)) {
   304 
   305         gmyth_debug
   306             ("UPnP MythTV Client control point is searching MythTV AV Device server...\n");
   307 
   308         if (gmyth_upnp->control_point != NULL)
   309             cg_upnp_controlpoint_search(gmyth_upnp->control_point,
   310                                         "urn:schemas-upnp-org:service:ContentDirectory:1");
   311 
   312         /*
   313          * just to avoid clinkc pthread concurrency faults 
   314          */
   315         cg_wait(1000);
   316 
   317         /*
   318          * discover if it was found 
   319          */
   320         gmyth_upnp->priv->upnp_dev_found =
   321             gmyth_upnp_got_mythtv_service(gmyth_upnp->control_point,
   322                                           &gmyth_upnp->priv->udn,
   323                                           &gmyth_upnp->priv->
   324                                           mythtv_servers);
   325 
   326     }                           /* while */
   327 
   328     if (gmyth_upnp->priv->upnp_dev_found) {
   329 
   330         gmyth_debug("Found UPnP MythTV AV Device...\n");
   331 
   332         g_hash_table_foreach(gmyth_upnp->priv->mythtv_servers,
   333                              (GHFunc) _gmyth_foreach_key_value,
   334                              upnp_servers_list);
   335 
   336     }
   337     /*
   338      * if - found UPnP device 
   339      */
   340     return upnp_servers_list;
   341 }
   342 
   343 /** 
   344  * Checks if got the MythTV service in the Control Point's device list.
   345  */
   346 static          gboolean
   347 gmyth_upnp_got_mythtv_service(CgUpnpControlPoint * controlPt, gchar ** udn,
   348                               GHashTable ** mythtv_servers_lst)
   349 {
   350 
   351     g_return_val_if_fail(mythtv_servers_lst != NULL, FALSE);
   352     g_return_val_if_fail(controlPt != NULL, FALSE);
   353 
   354     *mythtv_servers_lst = g_hash_table_new(g_str_hash, g_str_equal);
   355 
   356     const gchar    *mythtvFriendlyName = "Myth";
   357     /*
   358      * begin assertion about the size of discovered devices 
   359      */
   360     gint            numDevices =
   361         cg_upnp_controlpoint_getndevices(controlPt);
   362     gint            cntDevs = 0;
   363     CgUpnpDevice   *childDev;
   364     gchar          *devName = NULL,
   365         *dev_url = NULL;
   366     gboolean        upnp_dev_found = FALSE;
   367 
   368     gmyth_debug("UPnP MythTV AV Device list size = %d\n", numDevices);
   369 
   370     for (childDev = cg_upnp_controlpoint_getdevices(controlPt);
   371          childDev != NULL; childDev = cg_upnp_device_next(childDev)) {
   372         devName = cg_upnp_device_getfriendlyname(childDev);
   373         dev_url = cg_upnp_device_getlocationfromssdppacket(childDev);
   374         gmyth_debug
   375             ("Device's friendly name = %s, and  device's URL = %s\n",
   376              devName, dev_url);
   377         if ((g_strstr_len(devName, strlen(devName), mythtvFriendlyName) !=
   378              NULL) == TRUE) {
   379             upnp_dev_found = TRUE;
   380             /*
   381              * stores the last UDN number ID 
   382              */
   383             *udn = cg_upnp_device_getudn(childDev);
   384             /*
   385              *mythtv_servers_lst = g_list_append( *mythtv_servers_lst, dev_url );  */
   386             g_hash_table_insert(*mythtv_servers_lst, (gpointer) * udn,
   387                                 (gpointer) dev_url);
   388         }
   389         ++cntDevs;
   390     }
   391 
   392     if (upnp_dev_found == TRUE) {
   393         gmyth_debug
   394             ("MythTV AV Device found, from a total of %d devices.\n",
   395              cntDevs);
   396     } else if (numDevices == cntDevs) {
   397         gmyth_debug
   398             ("MythTV AV Device not found, from a total of %d devices.\n",
   399              cntDevs);
   400     } else {
   401         gmyth_debug
   402             ("Control Point's MythTV AV Device count is wrong: iterated over %d devices, but there are %d registered devices.\n",
   403              cntDevs, numDevices);
   404     }
   405 
   406     return upnp_dev_found;
   407 
   408 }
   409 
   410 /** Gets the UPnP AV devices server's list associated to this upnp.
   411  * 
   412  * @return The GHashTable* containing all the URI values for each recognized UPnP device,
   413  * 			or NULL if it couldn't recognize any MythTV AV device.
   414  */
   415 GHashTable     *
   416 gmyth_upnp_get_servers(GMythUPnP * gmyth_upnp)
   417 {
   418 
   419     if (NULL == gmyth_upnp || NULL == gmyth_upnp->priv->mythtv_servers) {
   420         gmyth_debug("[%s] GMythUPnP has no MythTV servers recognized.\n",
   421                     __FUNCTION__);
   422         return NULL;
   423     }
   424 
   425     return gmyth_upnp->priv->mythtv_servers;
   426 }
   427 
   428 /** Gets the GMythBackendInfo object associated to this upnp.
   429  * 
   430  * @return The GMythBackendInfo object currently valid or NULL if the settings
   431  * were not opened.
   432  */
   433 GMythBackendInfo *
   434 gmyth_upnp_get_backend_info(GMythUPnP * gmyth_upnp)
   435 {
   436 
   437     if (NULL == gmyth_upnp || NULL == gmyth_upnp->backend_info) {
   438         gmyth_debug("[%s] GMythUPnP not initialized\n", __FUNCTION__);
   439         return NULL;
   440     }
   441 
   442     return gmyth_upnp->backend_info;
   443 }