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