gmyth-upnp/src/gmyth_upnp.c
author renatofilho
Fri Feb 01 22:17:33 2008 +0000 (2008-02-01)
branchtrunk
changeset 909 847da7267234
parent 754 cb885ee44618
child 915 e612ba1d16ab
permissions -rw-r--r--
[svn r915] updated to use libupnp
     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 
    34 #include <gmyth/gmyth.h>
    35 #include <upnp/upnp.h>
    36 #include <string.h>
    37 
    38 
    39 #define UPNP_SEARCH_TIMEOUT 5
    40 #define UPNP_SERVICE_FILTER  "urn:schemas-mythtv-org:service:MythTv:1"
    41 #define SERVER_ID           "MythTV AV Media Server"
    42 
    43 typedef struct _GMythUPnPPrivate GMythUPnPPrivate;
    44 typedef struct _GMythUPnPIdleData GMythUPnPIdleData;
    45 
    46 #define GMYTH_UPNP_GET_PRIVATE(obj) \
    47     (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GMYTH_UPNP_TYPE, GMythUPnPPrivate))
    48 
    49 enum
    50 {
    51     DEVICE_FOUND,
    52     DEVICE_LOST,
    53     LAST_SIGNAL
    54 };
    55 
    56 struct _GMythUPnPIdleData
    57 {
    58     GMythUPnP *parent;
    59     GMythBackendInfo *server;
    60 };
    61 
    62 struct _GMythUPnPPrivate {
    63     GHashTable     *servers;
    64     GMythUPnPDeviceStatus last_status;
    65     gboolean        upnp_dev_found;
    66     gchar          *udn;
    67     GMutex         *mutex;
    68     gint            idle_count;
    69 
    70     /* upnp */
    71     UpnpClient_Handle client_id;
    72 };
    73 
    74 static void     gmyth_upnp_class_init   (GMythUPnPClass* klass);
    75 static void     gmyth_upnp_init         (GMythUPnP* object);
    76 static void     gmyth_upnp_dispose      (GObject* object);
    77 static void     gmyth_upnp_finalize     (GObject* object);
    78 static GObject* gmyth_upnp_constructor  (GType type,
    79                                          guint n_construct_params,
    80                                          GObjectConstructParam *construct_params);
    81 
    82 
    83 static int      _upnp_event_handler     (Upnp_EventType e_type,
    84                                          void* e,
    85                                          void* data);
    86 
    87 
    88 static int signals[LAST_SIGNAL] = {0};
    89 
    90 static GMythUPnP *singleton = NULL;
    91 
    92 G_DEFINE_TYPE(GMythUPnP, gmyth_upnp, G_TYPE_OBJECT);
    93 
    94 static void
    95 gmyth_upnp_class_init(GMythUPnPClass * klass)
    96 {
    97     GObjectClass   *gobject_class;
    98     GMythUPnPClass *gupnp_class;
    99 
   100     gobject_class = (GObjectClass *) klass;
   101     gupnp_class = (GMythUPnPClass *) gobject_class;
   102 
   103     gobject_class->dispose = gmyth_upnp_dispose;
   104     gobject_class->finalize = gmyth_upnp_finalize;
   105     gobject_class->constructor = gmyth_upnp_constructor;
   106 
   107     g_type_class_add_private (gobject_class, sizeof(GMythUPnPPrivate));
   108 
   109 
   110 
   111     signals[DEVICE_FOUND] = g_signal_new("device-found",
   112                                          G_TYPE_FROM_CLASS(gupnp_class),
   113                                          G_SIGNAL_RUN_LAST,
   114                                          0, NULL, NULL,
   115                                          g_cclosure_marshal_VOID__OBJECT,
   116                                          G_TYPE_NONE, 1,
   117                                          GMYTH_BACKEND_INFO_TYPE);
   118 
   119     signals[DEVICE_LOST] = g_signal_new("device-lost",
   120                                          G_TYPE_FROM_CLASS(gupnp_class),
   121                                          G_SIGNAL_RUN_LAST,
   122                                          0, NULL, NULL,
   123                                          g_cclosure_marshal_VOID__OBJECT,
   124                                          G_TYPE_NONE, 1,
   125                                          GMYTH_BACKEND_INFO_TYPE);
   126 
   127 }
   128 
   129 static void
   130 gmyth_upnp_init(GMythUPnP* self)
   131 {
   132     gint ret;
   133     GMythUPnPPrivate *priv;
   134 
   135     priv = GMYTH_UPNP_GET_PRIVATE (self);
   136 
   137     priv->mutex = g_mutex_new ();
   138     priv->servers = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
   139 
   140     /* initalize upnp client */
   141     ret = UpnpInit (NULL, 0);
   142     if (ret != UPNP_E_SUCCESS)
   143         g_warning ("Fail to inilialize upnp SDK: %d", ret);
   144     else
   145     {
   146         ret = UpnpRegisterClient (_upnp_event_handler,
   147                                   &priv->client_id, &priv->client_id);
   148 
   149         if (ret != UPNP_E_SUCCESS)
   150             g_warning ("Fail to start upnp client: %d", ret);
   151     }
   152 }
   153 
   154 static GObject*
   155 gmyth_upnp_constructor (GType type,
   156                         guint n_construct_params,
   157                         GObjectConstructParam *construct_params)
   158 {
   159     GObject *object;
   160 
   161     if (!singleton)
   162     {
   163         object = G_OBJECT_CLASS (gmyth_upnp_parent_class)->constructor (type,
   164                                                              n_construct_params,
   165                                                              construct_params);
   166 
   167         singleton = GMYTH_UPNP (object);
   168     }
   169     else
   170         object = g_object_ref (G_OBJECT (singleton));
   171 
   172     return object;
   173 }
   174 
   175 static void
   176 gmyth_upnp_dispose(GObject * object)
   177 {
   178     /* finalize upnp client */
   179     UpnpFinish ();
   180     G_OBJECT_CLASS(gmyth_upnp_parent_class)->dispose(object);
   181 }
   182 
   183 static void
   184 gmyth_upnp_finalize(GObject * object)
   185 {
   186     G_OBJECT_CLASS(gmyth_upnp_parent_class)->finalize(object);
   187     singleton = NULL;
   188 }
   189 
   190 
   191 GMythUPnP*
   192 gmyth_upnp_get_instance (void)
   193 {
   194     return GMYTH_UPNP(g_object_new(GMYTH_UPNP_TYPE, NULL));
   195 }
   196 
   197 
   198 void
   199 gmyth_upnp_search (GMythUPnP *self)
   200 {
   201     int ret;
   202     GMythUPnPPrivate *priv;
   203 
   204     priv = GMYTH_UPNP_GET_PRIVATE (self);
   205 
   206     ret = UpnpSearchAsync (priv->client_id,
   207                            UPNP_SEARCH_TIMEOUT,
   208                            UPNP_SERVICE_FILTER,
   209                            NULL);
   210 
   211     if (ret != UPNP_E_SUCCESS)
   212         g_warning ("Fail to start upnp listener: %d", ret);
   213 }
   214 
   215 static gboolean
   216 _idle_emit_device_found_signal (gpointer data)
   217 {
   218     GMythUPnPPrivate *priv;
   219     GMythUPnPIdleData *idle_data;
   220 
   221     idle_data = (GMythUPnPIdleData *) data;
   222     priv = GMYTH_UPNP_GET_PRIVATE (idle_data->parent);
   223 
   224     g_signal_emit (idle_data->parent, signals[DEVICE_FOUND], 0, idle_data->server);
   225 
   226     g_object_unref (idle_data->server);
   227     g_free (idle_data);
   228     priv->idle_count--;
   229 
   230     return FALSE;
   231 }
   232 
   233 static gboolean
   234 _idle_emit_device_lost_signal (gpointer data)
   235 {
   236     GMythUPnPPrivate *priv;
   237     GMythUPnPIdleData *idle_data;
   238 
   239     idle_data = (GMythUPnPIdleData *) data;
   240     priv = GMYTH_UPNP_GET_PRIVATE (idle_data->parent);
   241 
   242     g_signal_emit (idle_data->parent, signals[DEVICE_LOST], 0, idle_data->server);
   243 
   244     g_object_unref (idle_data->server);
   245     g_free (idle_data);
   246     priv->idle_count--;
   247 
   248     return FALSE;
   249 }
   250 
   251 static char*
   252 _xml_get_first_document_item (IXML_Document * doc,
   253                               const gchar *item )
   254 {
   255     IXML_NodeList *node_list = NULL;
   256     IXML_Node *text_node = NULL;
   257     IXML_Node *tmp_node = NULL;
   258 
   259     gchar *ret = NULL;
   260 
   261     node_list = ixmlDocument_getElementsByTagName (doc,
   262                                                   (char *) item);
   263 
   264     if (node_list)
   265     {
   266         if ((tmp_node = ixmlNodeList_item (node_list, 0))) 
   267         {
   268             text_node = ixmlNode_getFirstChild (tmp_node);
   269 
   270             ret = strdup (ixmlNode_getNodeValue (text_node));
   271         }
   272     }
   273 
   274     if (node_list)
   275         ixmlNodeList_free (node_list);
   276 
   277     return ret;
   278 }
   279 
   280 
   281 static void
   282 _append_mythtv_server_from_loation (GMythUPnP *self,
   283                                     const gchar *uuid,
   284                                     const gchar *location)
   285 {
   286     GMythUPnPPrivate *priv;
   287     gchar *base_url;
   288     gchar *end;
   289 
   290     priv = GMYTH_UPNP_GET_PRIVATE (self);
   291 
   292     base_url = g_strdup (location);
   293     end = g_strrstr (base_url, "/");
   294     if (end)
   295     {
   296         gint ret;
   297         IXML_Document *desc_doc;
   298         gchar *info_url;
   299 
   300         end[0] = '\0';
   301         info_url = g_strconcat (base_url,
   302                                 "Myth/GetConnectionInfo",
   303                                 NULL);
   304         g_free (base_url);
   305         desc_doc = NULL;
   306         ret = UpnpDownloadXmlDoc (info_url, &desc_doc);
   307         if (ret != UPNP_E_SUCCESS)
   308         {
   309             g_warning ("Error obtaining device desc: %d", ret);
   310         }
   311         else
   312         {
   313             GMythBackendInfo *info;
   314             GMythUPnPIdleData *idle_data;
   315 
   316             info = gmyth_backend_info_new_full (
   317                 _xml_get_first_document_item (desc_doc, "Host"),
   318                 _xml_get_first_document_item (desc_doc, "UserName"),
   319                 _xml_get_first_document_item (desc_doc, "Password"),
   320                 _xml_get_first_document_item (desc_doc, "Name"),
   321                 // Current mythtv version not export port number
   322                 6543);
   323 
   324             if (desc_doc)
   325                 ixmlDocument_free (desc_doc);
   326 
   327             g_mutex_lock (priv->mutex);
   328             g_hash_table_insert (priv->servers, 
   329                                  g_strdup (uuid),
   330                                  g_object_ref (info));
   331             g_mutex_unlock (priv->mutex);
   332             g_debug ("info url: %s", info_url);
   333             g_free (info_url);
   334 
   335             idle_data = g_new0 (GMythUPnPIdleData, 1);
   336             idle_data->parent = self;
   337             idle_data->server = g_object_ref (info);
   338 
   339             priv->idle_count++;
   340             g_idle_add (_idle_emit_device_found_signal, idle_data);
   341         }
   342     }
   343 }
   344 
   345 static void
   346 _remove_mythtv_server (GMythUPnP *self,
   347                        const gchar *uuid)
   348 {
   349     GMythUPnPPrivate *priv;
   350     GMythBackendInfo *info;
   351 
   352     priv = GMYTH_UPNP_GET_PRIVATE (self);
   353 
   354     g_mutex_lock (priv->mutex);
   355     info = g_hash_table_lookup (priv->servers, uuid);
   356     if (info)
   357     {
   358         GMythUPnPIdleData *idle_data;
   359 
   360         idle_data = g_new0 (GMythUPnPIdleData, 1);
   361         idle_data->parent = self;
   362         idle_data->server = g_object_ref (info);
   363 
   364         g_hash_table_remove (priv->servers, uuid);
   365 
   366         priv->idle_count++;
   367         g_idle_add (_idle_emit_device_lost_signal, idle_data);
   368     }
   369     g_mutex_unlock (priv->mutex);
   370 
   371 }
   372 
   373 static GMythBackendInfo*
   374 _find_service_by_uuid (GMythUPnP *self,
   375                       const gchar *uuid)
   376 {
   377     GMythUPnPPrivate *priv;
   378     GMythBackendInfo *info;
   379 
   380     priv = GMYTH_UPNP_GET_PRIVATE (self);
   381     info = NULL;
   382 
   383     g_mutex_lock (priv->mutex);
   384     info = g_hash_table_lookup (priv->servers, uuid);
   385     g_mutex_unlock (priv->mutex);
   386 
   387     return info;
   388 }
   389 
   390 static int
   391 _upnp_event_handler (Upnp_EventType e_type,
   392                      void* e,
   393                      void* data)
   394 {
   395     g_return_val_if_fail (singleton != NULL, 0);
   396 
   397     switch (e_type)
   398     {
   399         case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
   400         case UPNP_DISCOVERY_SEARCH_RESULT:
   401         {
   402             struct Upnp_Discovery *d_event;
   403 
   404             d_event = (struct Upnp_Discovery *) e;
   405 
   406             g_debug ("TYPE: %s", d_event->ServiceType);
   407 
   408             if (strcmp (d_event->ServiceType, UPNP_SERVICE_FILTER) != 0)
   409             {
   410                 g_debug ("invalid device : %s", d_event->DeviceId);
   411                 break;
   412             }
   413 
   414 
   415             if (d_event->ErrCode != UPNP_E_SUCCESS)
   416             {
   417                 g_warning ("Error in Discovery: %d", d_event->ErrCode);
   418                 break;
   419             }
   420 
   421             if (_find_service_by_uuid (GMYTH_UPNP (singleton), d_event->DeviceId) == NULL)
   422                 _append_mythtv_server_from_loation (singleton,
   423                                                     d_event->DeviceId,
   424                                                     d_event->Location);
   425 
   426 
   427             break;
   428         }
   429         case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
   430         {
   431             GMythUPnPPrivate *priv;
   432             struct Upnp_Discovery *d_event;
   433 
   434             d_event = (struct Upnp_Discovery *) e;
   435             if (d_event->ErrCode != UPNP_E_SUCCESS)
   436             {
   437                 g_warning ("Error in Discovery: %d", d_event->ErrCode);
   438                 break;
   439             }
   440 
   441             priv = GMYTH_UPNP_GET_PRIVATE (singleton);
   442             _remove_mythtv_server (singleton,
   443                                    d_event->DeviceId);
   444 
   445             break;
   446 
   447         }
   448         default:
   449             g_debug ("No handle event: %d", e_type);
   450             break;
   451     }
   452 
   453     return 0;
   454 }
   455