gmyth-upnp/src/gmyth_upnp.c
author renatofilho
Fri Feb 15 13:52:23 2008 +0000 (2008-02-15)
branchtrunk
changeset 915 e612ba1d16ab
parent 909 847da7267234
child 922 04cd5e61dca3
permissions -rw-r--r--
[svn r924] created method gmyth_upnp_get_devices
     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 void
   216 _fill_servers_cb (gpointer key,
   217                   gpointer value,
   218                   gpointer user_data)
   219 {
   220     GList **lst;
   221 
   222     lst = (GList **) user_data;
   223 
   224     *lst = g_list_append (*lst, g_object_ref (value));
   225 }
   226 
   227 GList*
   228 gmyth_upnp_get_devices (GMythUPnP *self)
   229 {
   230     GMythUPnPPrivate *priv;
   231     GList *lst;
   232 
   233     priv = GMYTH_UPNP_GET_PRIVATE (self);
   234     lst = NULL;
   235     g_hash_table_foreach (priv->servers, (GHFunc) _fill_servers_cb, &lst);
   236 
   237     return lst;
   238 }
   239 
   240 static gboolean
   241 _idle_emit_device_found_signal (gpointer data)
   242 {
   243     GMythUPnPPrivate *priv;
   244     GMythUPnPIdleData *idle_data;
   245 
   246     idle_data = (GMythUPnPIdleData *) data;
   247     priv = GMYTH_UPNP_GET_PRIVATE (idle_data->parent);
   248 
   249     g_signal_emit (idle_data->parent, signals[DEVICE_FOUND], 0, idle_data->server);
   250 
   251     g_object_unref (idle_data->server);
   252     g_free (idle_data);
   253     priv->idle_count--;
   254 
   255     return FALSE;
   256 }
   257 
   258 static gboolean
   259 _idle_emit_device_lost_signal (gpointer data)
   260 {
   261     GMythUPnPPrivate *priv;
   262     GMythUPnPIdleData *idle_data;
   263 
   264     idle_data = (GMythUPnPIdleData *) data;
   265     priv = GMYTH_UPNP_GET_PRIVATE (idle_data->parent);
   266 
   267     g_signal_emit (idle_data->parent, signals[DEVICE_LOST], 0, idle_data->server);
   268 
   269     g_object_unref (idle_data->server);
   270     g_free (idle_data);
   271     priv->idle_count--;
   272 
   273     return FALSE;
   274 }
   275 
   276 static char*
   277 _xml_get_first_document_item (IXML_Document * doc,
   278                               const gchar *item )
   279 {
   280     IXML_NodeList *node_list = NULL;
   281     IXML_Node *text_node = NULL;
   282     IXML_Node *tmp_node = NULL;
   283 
   284     gchar *ret = NULL;
   285 
   286     node_list = ixmlDocument_getElementsByTagName (doc,
   287                                                   (char *) item);
   288 
   289     if (node_list)
   290     {
   291         if ((tmp_node = ixmlNodeList_item (node_list, 0))) 
   292         {
   293             text_node = ixmlNode_getFirstChild (tmp_node);
   294 
   295             ret = strdup (ixmlNode_getNodeValue (text_node));
   296         }
   297     }
   298 
   299     if (node_list)
   300         ixmlNodeList_free (node_list);
   301 
   302     return ret;
   303 }
   304 
   305 
   306 static void
   307 _append_mythtv_server_from_loation (GMythUPnP *self,
   308                                     const gchar *uuid,
   309                                     const gchar *location)
   310 {
   311     GMythUPnPPrivate *priv;
   312     gchar *base_url;
   313     gchar *end;
   314 
   315     priv = GMYTH_UPNP_GET_PRIVATE (self);
   316 
   317     base_url = g_strdup (location);
   318     end = g_strrstr (base_url, "/");
   319     if (end)
   320     {
   321         gint ret;
   322         IXML_Document *desc_doc;
   323         gchar *info_url;
   324 
   325         end[0] = '\0';
   326         info_url = g_strconcat (base_url,
   327                                 "Myth/GetConnectionInfo",
   328                                 NULL);
   329         g_free (base_url);
   330         desc_doc = NULL;
   331         ret = UpnpDownloadXmlDoc (info_url, &desc_doc);
   332         if (ret != UPNP_E_SUCCESS)
   333         {
   334             g_warning ("Error obtaining device desc: %d", ret);
   335         }
   336         else
   337         {
   338             GMythBackendInfo *info;
   339             GMythUPnPIdleData *idle_data;
   340 
   341             info = gmyth_backend_info_new_full (
   342                 _xml_get_first_document_item (desc_doc, "Host"),
   343                 _xml_get_first_document_item (desc_doc, "UserName"),
   344                 _xml_get_first_document_item (desc_doc, "Password"),
   345                 _xml_get_first_document_item (desc_doc, "Name"),
   346                 // Current mythtv version not export port number
   347                 6543);
   348 
   349             if (desc_doc)
   350                 ixmlDocument_free (desc_doc);
   351 
   352             g_mutex_lock (priv->mutex);
   353             g_hash_table_insert (priv->servers, 
   354                                  g_strdup (uuid),
   355                                  g_object_ref (info));
   356             g_mutex_unlock (priv->mutex);
   357             g_free (info_url);
   358 
   359             idle_data = g_new0 (GMythUPnPIdleData, 1);
   360             idle_data->parent = self;
   361             idle_data->server = g_object_ref (info);
   362 
   363             priv->idle_count++;
   364             g_idle_add (_idle_emit_device_found_signal, idle_data);
   365         }
   366     }
   367 }
   368 
   369 static void
   370 _remove_mythtv_server (GMythUPnP *self,
   371                        const gchar *uuid)
   372 {
   373     GMythUPnPPrivate *priv;
   374     GMythBackendInfo *info;
   375 
   376     priv = GMYTH_UPNP_GET_PRIVATE (self);
   377 
   378     g_mutex_lock (priv->mutex);
   379     info = g_hash_table_lookup (priv->servers, uuid);
   380     if (info)
   381     {
   382         GMythUPnPIdleData *idle_data;
   383 
   384         idle_data = g_new0 (GMythUPnPIdleData, 1);
   385         idle_data->parent = self;
   386         idle_data->server = g_object_ref (info);
   387 
   388         g_hash_table_remove (priv->servers, uuid);
   389 
   390         priv->idle_count++;
   391         g_idle_add (_idle_emit_device_lost_signal, idle_data);
   392     }
   393     g_mutex_unlock (priv->mutex);
   394 
   395 }
   396 
   397 static GMythBackendInfo*
   398 _find_service_by_uuid (GMythUPnP *self,
   399                       const gchar *uuid)
   400 {
   401     GMythUPnPPrivate *priv;
   402     GMythBackendInfo *info;
   403 
   404     priv = GMYTH_UPNP_GET_PRIVATE (self);
   405     info = NULL;
   406 
   407     g_mutex_lock (priv->mutex);
   408     info = g_hash_table_lookup (priv->servers, uuid);
   409     g_mutex_unlock (priv->mutex);
   410 
   411     return info;
   412 }
   413 
   414 static int
   415 _upnp_event_handler (Upnp_EventType e_type,
   416                      void* e,
   417                      void* data)
   418 {
   419     g_return_val_if_fail (singleton != NULL, 0);
   420 
   421     switch (e_type)
   422     {
   423         case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE:
   424         case UPNP_DISCOVERY_SEARCH_RESULT:
   425         {
   426             struct Upnp_Discovery *d_event;
   427 
   428             d_event = (struct Upnp_Discovery *) e;
   429 
   430             if (strcmp (d_event->ServiceType, UPNP_SERVICE_FILTER) != 0)
   431             {
   432                 g_warning ("invalid device : %s", d_event->DeviceId);
   433                 break;
   434             }
   435 
   436 
   437             if (d_event->ErrCode != UPNP_E_SUCCESS)
   438             {
   439                 g_warning ("Error in Discovery: %d", d_event->ErrCode);
   440                 break;
   441             }
   442 
   443             if (_find_service_by_uuid (GMYTH_UPNP (singleton), d_event->DeviceId) == NULL)
   444                 _append_mythtv_server_from_loation (singleton,
   445                                                     d_event->DeviceId,
   446                                                     d_event->Location);
   447 
   448 
   449             break;
   450         }
   451         case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE:
   452         {
   453             GMythUPnPPrivate *priv;
   454             struct Upnp_Discovery *d_event;
   455 
   456             d_event = (struct Upnp_Discovery *) e;
   457             if (d_event->ErrCode != UPNP_E_SUCCESS)
   458             {
   459                 g_warning ("Error in Discovery: %d", d_event->ErrCode);
   460                 break;
   461             }
   462 
   463             priv = GMYTH_UPNP_GET_PRIVATE (singleton);
   464             _remove_mythtv_server (singleton,
   465                                    d_event->DeviceId);
   466 
   467             break;
   468 
   469         }
   470         default:
   471             break;
   472     }
   473 
   474     return 0;
   475 }
   476