/*
* Copyright (C) 2006, 2007, 2008 OpenedHand Ltd.
*
* Author: Jorn Baayen <jorn@openedhand.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/**
* SECTION:gupnp-control-point
* @short_description: Class for resource discovery.
*
* #GUPnPControlPoint handles device and service discovery. After creating
* a control point and activating it using gssdp_resource_browser_set_active(),
* the ::device-proxy-available, ::service-proxy-available,
* ::device-proxy-unavailable and ::service-proxy-unavailable signals will
* be emitted whenever the availability of a device or service matching
* the specified discovery target changes.
*/
#include <string.h>
#include "gupnp-control-point.h"
#include "gupnp-context-private.h"
#include "gupnp-resource-factory-private.h"
#include "http-headers.h"
#include "xml-util.h"
#define GUPNP_MAX_DESCRIPTION_DOWNLOAD_RETRIES 4
#define GUPNP_INITIAL_DESCRIPTION_RETRY_TIMEOUT 5
G_DEFINE_TYPE (GUPnPControlPoint,
gupnp_control_point,
GSSDP_TYPE_RESOURCE_BROWSER);
struct _GUPnPControlPointPrivate {
GUPnPResourceFactory *factory;
GList *devices;
GList *services;
GHashTable *doc_cache;
GList *pending_gets;
};
enum {
PROP_0,
PROP_RESOURCE_FACTORY,
};
enum {
DEVICE_PROXY_AVAILABLE,
DEVICE_PROXY_UNAVAILABLE,
SERVICE_PROXY_AVAILABLE,
SERVICE_PROXY_UNAVAILABLE,
SIGNAL_LAST
};
static guint signals[SIGNAL_LAST];
typedef struct {
GUPnPControlPoint *control_point;
char *udn;
char *service_type;
char *description_url;
SoupMessage *message;
GSource *timeout_source;
int tries;
int timeout;
} GetDescriptionURLData;
static void
get_description_url_data_free (GetDescriptionURLData *data)
{
data->control_point->priv->pending_gets =
g_list_remove (data->control_point->priv->pending_gets, data);
if (data->message) {
GUPnPContext *context;
SoupSession *session;
context = gupnp_control_point_get_context (data->control_point);
session = gupnp_context_get_session (context);
soup_session_cancel_message (session,
data->message,
SOUP_STATUS_CANCELLED);
}
if (data->timeout_source) {
g_source_destroy (data->timeout_source);
g_source_unref (data->timeout_source);
}
g_free (data->udn);
g_free (data->service_type);
g_free (data->description_url);
g_slice_free (GetDescriptionURLData, data);
}
static GetDescriptionURLData*
find_get_description_url_data (GUPnPControlPoint *control_point,
const char *udn,
const char *service_type)
{
GList *l = control_point->priv->pending_gets;
while (l) {
GetDescriptionURLData *data = l->data;
if ((g_strcmp0 (udn, data->udn) == 0) &&
(service_type == data->service_type ||
g_strcmp0 (service_type, data->service_type) == 0))
break;
l = g_list_next (l);
}
return l ? l->data : NULL;
}
static void
gupnp_control_point_init (GUPnPControlPoint *control_point)
{
control_point->priv =
G_TYPE_INSTANCE_GET_PRIVATE (control_point,
GUPNP_TYPE_CONTROL_POINT,
GUPnPControlPointPrivate);
control_point->priv->doc_cache =
g_hash_table_new_full (g_str_hash,
g_str_equal,
g_free,
NULL);
}
/* Return TRUE if value == user_data */
static gboolean
find_doc (G_GNUC_UNUSED gpointer key,
gpointer value,
G_GNUC_UNUSED gpointer user_data)
{
return (value == user_data);
}
/* xmlDoc wrapper finalized */
static void
doc_finalized (gpointer user_data,
GObject *where_the_object_was)
{
GUPnPControlPoint *control_point;
control_point = GUPNP_CONTROL_POINT (user_data);
g_hash_table_foreach_remove (control_point->priv->doc_cache,
find_doc,
where_the_object_was);
}
/* Release weak reference on xmlDoc wrapper */
static void
weak_unref_doc (G_GNUC_UNUSED gpointer key,
gpointer value,
gpointer user_data)
{
g_object_weak_unref (G_OBJECT (value), doc_finalized, user_data);
}
static void
gupnp_control_point_dispose (GObject *object)
{
GUPnPControlPoint *control_point;
GSSDPResourceBrowser *browser;
GObjectClass *object_class;
control_point = GUPNP_CONTROL_POINT (object);
browser = GSSDP_RESOURCE_BROWSER (control_point);
gssdp_resource_browser_set_active (browser, FALSE);
if (control_point->priv->factory) {
g_object_unref (control_point->priv->factory);
control_point->priv->factory = NULL;
}
/* Cancel any pending description file GETs */
while (control_point->priv->pending_gets) {
GetDescriptionURLData *data;
data = control_point->priv->pending_gets->data;
get_description_url_data_free (data);
}
/* Release weak references on remaining cached documents */
g_hash_table_foreach (control_point->priv->doc_cache,
weak_unref_doc,
control_point);
/* Call super */
object_class = G_OBJECT_CLASS (gupnp_control_point_parent_class);
object_class->dispose (object);
}
static void
gupnp_control_point_finalize (GObject *object)
{
GUPnPControlPoint *control_point;
GObjectClass *object_class;
control_point = GUPNP_CONTROL_POINT (object);
g_hash_table_destroy (control_point->priv->doc_cache);
/* Call super */
object_class = G_OBJECT_CLASS (gupnp_control_point_parent_class);
object_class->finalize (object);
}
static GList *
find_service_node (GUPnPControlPoint *control_point,
const char *udn,
const char *service_type)
{
GList *l;
l = control_point->priv->services;
while (l) {
GUPnPServiceInfo *info;
info = GUPNP_SERVICE_INFO (l->data);
if ((strcmp (gupnp_service_info_get_udn (info), udn) == 0) &&
(strcmp (gupnp_service_info_get_service_type (info),
service_type) == 0))
break;
l = l->next;
}
return l;
}
static GList *
find_device_node (GUPnPControlPoint *control_point,
const char *udn)
{
GList *l;
l = control_point->priv->devices;
while (l) {
GUPnPDeviceInfo *info;
info = GUPNP_DEVICE_INFO (l->data);
if (strcmp (udn, gupnp_device_info_get_udn (info)) == 0)
break;
l = l->next;
}
return l;
}
static void
create_and_report_service_proxy (GUPnPControlPoint *control_point,
GUPnPXMLDoc *doc,
xmlNode *element,
const char *udn,
const char *service_type,
const char *description_url,
SoupURI *url_base)
{
GUPnPServiceProxy *proxy;
GUPnPResourceFactory *factory;
GUPnPContext *context;
if (find_service_node (control_point, udn, service_type) != NULL)
/* We already have a proxy for this service */
return;
factory = gupnp_control_point_get_resource_factory (control_point);
context = gupnp_control_point_get_context (control_point);
/* Create proxy */
proxy = gupnp_resource_factory_create_service_proxy (factory,
context,
doc,
element,
udn,
service_type,
description_url,
url_base);
control_point->priv->services =
g_list_prepend (control_point->priv->services,
proxy);
g_signal_emit (control_point,
signals[SERVICE_PROXY_AVAILABLE],
0,
proxy);
}
static void
create_and_report_device_proxy (GUPnPControlPoint *control_point,
GUPnPXMLDoc *doc,
xmlNode *element,
const char *udn,
const char *description_url,
SoupURI *url_base)
{
GUPnPDeviceProxy *proxy;
GUPnPResourceFactory *factory;
GUPnPContext *context;
if (find_device_node (control_point, udn) != NULL)
/* We already have a proxy for this device */
return;
factory = gupnp_control_point_get_resource_factory (control_point);
context = gupnp_control_point_get_context (control_point);
proxy = gupnp_resource_factory_create_device_proxy (factory,
context,
doc,
element,
udn,
description_url,
url_base);
control_point->priv->devices = g_list_prepend
(control_point->priv->devices,
proxy);
g_signal_emit (control_point,
signals[DEVICE_PROXY_AVAILABLE],
0,
proxy);
}
static gboolean
compare_service_types_versioned (const char *searched_service,
const char *current_service)
{
const char *searched_version_ptr, *current_version_ptr;
guint searched_version, current_version, searched_length;
guint current_length;
searched_version_ptr = strrchr (searched_service, ':');
if (searched_version_ptr == NULL)
return FALSE;
current_version_ptr = strrchr (current_service, ':');
if (current_version_ptr == NULL)
return FALSE;
searched_length = (searched_version_ptr - searched_service);
current_length = (current_version_ptr - current_service);
if (searched_length != current_length)
return FALSE;
searched_version = (guint) atol (searched_version_ptr + 1);
if (searched_version == 0)
return FALSE;
current_version = (guint) atol (current_version_ptr + 1);
if (current_version == 0)
return FALSE;
if (current_version < searched_version)
return FALSE;
return strncmp (searched_service,
current_service,
searched_length) == 0;
}
/* Search @element for matching services */
static void
process_service_list (xmlNode *element,
GUPnPControlPoint *control_point,
GUPnPXMLDoc *doc,
const char *udn,
const char *service_type,
const char *description_url,
SoupURI *url_base)
{
g_object_ref (control_point);
for (element = element->children; element; element = element->next) {
xmlChar *prop;
gboolean match;
if (strcmp ((char *) element->name, "service") != 0)
continue;
/* See if this is a matching service */
prop = xml_util_get_child_element_content (element,
"serviceType");
if (!prop)
continue;
match = compare_service_types_versioned (service_type,
(char *) prop);
xmlFree (prop);
if (!match)
continue;
/* Match */
create_and_report_service_proxy (control_point,
doc,
element,
udn,
service_type,
description_url,
url_base);
}
g_object_unref (control_point);
}
/* Recursively search @element for matching devices */
static void
process_device_list (xmlNode *element,
GUPnPControlPoint *control_point,
GUPnPXMLDoc *doc,
const char *udn,
const char *service_type,
const char *description_url,
SoupURI *url_base)
{
g_object_ref (control_point);
for (element = element->children; element; element = element->next) {
xmlNode *children;
xmlChar *prop;
gboolean match;
if (strcmp ((char *) element->name, "device") != 0)
continue;
/* Recurse into children */
children = xml_util_get_element (element,
"deviceList",
NULL);
if (children) {
process_device_list (children,
control_point,
doc,
udn,
service_type,
description_url,
url_base);
}
/* See if this is a matching device */
prop = xml_util_get_child_element_content (element, "UDN");
if (!prop)
continue;
match = (strcmp ((char *) prop, udn) == 0);
xmlFree (prop);
if (!match)
continue;
/* Match */
if (service_type) {
/* Dive into serviceList */
children = xml_util_get_element (element,
"serviceList",
NULL);
if (children) {
process_service_list (children,
control_point,
doc,
udn,
service_type,
description_url,
url_base);
}
} else
create_and_report_device_proxy (control_point,
doc,
element,
udn,
description_url,
url_base);
}
g_object_unref (control_point);
}
/*
* Called when the description document is loaded.
*/
static void
description_loaded (GUPnPControlPoint *control_point,
GUPnPXMLDoc *doc,
const char *udn,
const char *service_type,
const char *description_url)
{
xmlNode *element;
SoupURI *url_base;
/* Save the URL base, if any */
element = xml_util_get_element ((xmlNode *) doc->doc,
"root",
NULL);
if (element == NULL) {
g_warning ("No 'root' element found in description document"
" '%s'. Ignoring device '%s'\n",
description_url,
udn);
return;
}
url_base = xml_util_get_child_element_content_uri (element,
"URLBase",
NULL);
if (!url_base)
url_base = soup_uri_new (description_url);
/* Iterate matching devices */
process_device_list (element,
control_point,
doc,
udn,
service_type,
description_url,
url_base);
/* Cleanup */
soup_uri_free (url_base);
}
static gboolean
description_url_retry_timeout (gpointer user_data);
/*
* Description URL downloaded.
*/
static void
got_description_url (SoupSession *session,
SoupMessage *msg,
GetDescriptionURLData *data)
{
GUPnPXMLDoc *doc;
if (msg->status_code == SOUP_STATUS_CANCELLED)
return;
data->message = NULL;
/* Now, make sure again this document is not already cached. If it is,
* we re-use the cached one. */
doc = g_hash_table_lookup (data->control_point->priv->doc_cache,
data->description_url);
if (doc) {
/* Doc was cached */
description_loaded (data->control_point,
doc,
data->udn,
data->service_type,
data->description_url);
get_description_url_data_free (data);
return;
}
/* Not cached */
if (SOUP_STATUS_IS_SUCCESSFUL (msg->status_code)) {
xmlDoc *xml_doc;
/* Parse response */
xml_doc = xmlRecoverMemory (msg->response_body->data,
msg->response_body->length);
if (xml_doc) {
doc = gupnp_xml_doc_new (xml_doc);
description_loaded (data->control_point,
doc,
data->udn,
data->service_type,
data->description_url);
/* Insert into document cache */
g_hash_table_insert
(data->control_point->priv->doc_cache,
g_strdup (data->description_url),
doc);
/* Make sure the document is removed from the cache
* once finalized. */
g_object_weak_ref (G_OBJECT (doc),
doc_finalized,
data->control_point);
/* If no proxy was created, make sure doc is freed. */
g_object_unref (doc);
} else
g_warning ("Failed to parse %s", data->description_url);
get_description_url_data_free (data);
} else {
GMainContext *async_context;
/* Retry GET after a timeout */
async_context = soup_session_get_async_context (session);
data->tries--;
if (data->tries > 0) {
g_warning ("Failed to GET %s: %s, retrying in %d seconds",
data->description_url,
msg->reason_phrase,
data->timeout);
data->timeout_source = g_timeout_source_new_seconds
(data->timeout);
g_source_set_callback (data->timeout_source,
description_url_retry_timeout,
data,
NULL);
g_source_attach (data->timeout_source, async_context);
data->timeout <<= 1;
} else {
g_warning ("Maximum number of retries failed, not trying again");
}
}
}
/*
* Downloads and parses (or takes from cache) @description_url,
* creating:
* - A #GUPnPDeviceProxy for the device specified by @udn if @service_type
* is %NULL.
* - A #GUPnPServiceProxy for the service of type @service_type from the device
* specified by @udn if @service_type is not %NULL.
*/
static void
load_description (GUPnPControlPoint *control_point,
const char *description_url,
const char *udn,
const char *service_type,
guint max_tries,
guint timeout)
{
GUPnPXMLDoc *doc;
doc = g_hash_table_lookup (control_point->priv->doc_cache,
description_url);
if (doc) {
/* Doc was cached */
description_loaded (control_point,
doc,
udn,
service_type,
description_url);
} else {
/* Asynchronously download doc */
GUPnPContext *context;
SoupSession *session;
GetDescriptionURLData *data;
context = gupnp_control_point_get_context (control_point);
session = gupnp_context_get_session (context);
data = g_slice_new (GetDescriptionURLData);
data->tries = max_tries;
data->timeout = timeout;
data->message = soup_message_new (SOUP_METHOD_GET,
description_url);
if (data->message == NULL) {
g_warning ("Invalid description URL: %s",
description_url);
g_slice_free (GetDescriptionURLData, data);
return;
}
http_request_set_accept_language (data->message);
data->control_point = control_point;
data->udn = g_strdup (udn);
data->service_type = g_strdup (service_type);
data->description_url = g_strdup (description_url);
data->timeout_source = NULL;
control_point->priv->pending_gets =
g_list_prepend (control_point->priv->pending_gets,
data);
soup_session_queue_message (session,
data->message,
(SoupSessionCallback)
got_description_url,
data);
}
}
/*
* Retry the description download
*/
static gboolean
description_url_retry_timeout (gpointer user_data)
{
GetDescriptionURLData *data = (GetDescriptionURLData *) user_data;
load_description (data->control_point,
data->description_url,
data->udn,
data->service_type,
data->tries,
data->timeout);
get_description_url_data_free (data);
return FALSE;
}
static gboolean
parse_usn (const char *usn,
char **udn,
char **service_type)
{
gboolean ret;
char **bits;
guint count, i;
ret = FALSE;
*udn = *service_type = NULL;
/* Verify we have a valid USN */
if (strncmp (usn, "uuid:", strlen ("uuid:"))) {
g_warning ("Invalid USN: %s", usn);
return FALSE;
}
/* Parse USN */
bits = g_strsplit (usn, "::", -1);
/* Count elements */
count = g_strv_length (bits);
if (count == 1) {
/* uuid:device-UUID */
*udn = bits[0];
ret = TRUE;
} else if (count == 2) {
char **second_bits;
guint n_second_bits;
second_bits = g_strsplit (bits[1], ":", -1);
n_second_bits = g_strv_length (second_bits);
if (n_second_bits >= 2 &&
!strcmp (second_bits[0], "upnp") &&
!strcmp (second_bits[1], "rootdevice")) {
/* uuid:device-UUID::upnp:rootdevice */
*udn = bits[0];
ret = TRUE;
} else if (n_second_bits >= 3 &&
!strcmp (second_bits[0], "urn")) {
/* uuid:device-UIID::urn:domain-name:service/device:
* type:v */
if (!strcmp (second_bits[2], "device")) {
*udn = bits[0];
ret = TRUE;
} else if (!strcmp (second_bits[2], "service")) {
*udn = bits[0];
*service_type = bits[1];
ret = TRUE;
}
}
g_strfreev (second_bits);
}
if (*udn == NULL)
g_warning ("Invalid USN: %s", usn);
for (i = 0; i < count; i++) {
if ((bits[i] != *udn) &&
(bits[i] != *service_type))
g_free (bits[i]);
}
g_free (bits);
return ret;
}
static void
gupnp_control_point_resource_available (GSSDPResourceBrowser *resource_browser,
const char *usn,
const GList *locations)
{
GUPnPControlPoint *control_point;
char *udn, *service_type;
control_point = GUPNP_CONTROL_POINT (resource_browser);
/* Verify we have a location */
if (!locations) {
g_warning ("No Location header for device with USN %s", usn);
return;
}
/* Parse USN */
if (!parse_usn (usn, &udn, &service_type))
return;
load_description (control_point,
locations->data,
udn,
service_type,
GUPNP_MAX_DESCRIPTION_DOWNLOAD_RETRIES,
GUPNP_INITIAL_DESCRIPTION_RETRY_TIMEOUT);
g_free (udn);
g_free (service_type);
}
static void
gupnp_control_point_resource_unavailable
(GSSDPResourceBrowser *resource_browser,
const char *usn)
{
GUPnPControlPoint *control_point;
char *udn, *service_type;
GetDescriptionURLData *get_data;
control_point = GUPNP_CONTROL_POINT (resource_browser);
/* Parse USN */
if (!parse_usn (usn, &udn, &service_type))
return;
/* Find proxy */
if (service_type) {
GList *l = find_service_node (control_point, udn, service_type);
if (l) {
GUPnPServiceProxy *proxy;
/* Remove proxy */
proxy = GUPNP_SERVICE_PROXY (l->data);
control_point->priv->services =
g_list_delete_link
(control_point->priv->services, l);
g_signal_emit (control_point,
signals[SERVICE_PROXY_UNAVAILABLE],
0,
proxy);
g_object_unref (proxy);
}
} else {
GList *l = find_device_node (control_point, udn);
if (l) {
GUPnPDeviceProxy *proxy;
/* Remove proxy */
proxy = GUPNP_DEVICE_PROXY (l->data);
control_point->priv->devices =
g_list_delete_link
(control_point->priv->devices, l);
g_signal_emit (control_point,
signals[DEVICE_PROXY_UNAVAILABLE],
0,
proxy);
g_object_unref (proxy);
}
}
/* Find the description get request if it has not finished yet and stop
* and remove it */
get_data = find_get_description_url_data (control_point,
udn,
service_type);
if (get_data) {
get_description_url_data_free (get_data);
}
g_free (udn);
g_free (service_type);
}
static void
gupnp_control_point_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
GUPnPControlPoint *control_point;
control_point = GUPNP_CONTROL_POINT (object);
switch (property_id) {
case PROP_RESOURCE_FACTORY:
control_point->priv->factory =
GUPNP_RESOURCE_FACTORY (g_value_dup_object (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gupnp_control_point_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
GUPnPControlPoint *control_point;
control_point = GUPNP_CONTROL_POINT (object);
switch (property_id) {
case PROP_RESOURCE_FACTORY:
g_value_set_object (value,
gupnp_control_point_get_resource_factory (control_point));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gupnp_control_point_class_init (GUPnPControlPointClass *klass)
{
GObjectClass *object_class;
GSSDPResourceBrowserClass *browser_class;
object_class = G_OBJECT_CLASS (klass);
object_class->set_property = gupnp_control_point_set_property;
object_class->get_property = gupnp_control_point_get_property;
object_class->dispose = gupnp_control_point_dispose;
object_class->finalize = gupnp_control_point_finalize;
browser_class = GSSDP_RESOURCE_BROWSER_CLASS (klass);
browser_class->resource_available =
gupnp_control_point_resource_available;
browser_class->resource_unavailable =
gupnp_control_point_resource_unavailable;
g_type_class_add_private (klass, sizeof (GUPnPControlPointPrivate));
/**
* GUPnPControlPoint:resource-factory:
*
* The resource factory to use. Set to NULL for default factory.
**/
g_object_class_install_property
(object_class,
PROP_RESOURCE_FACTORY,
g_param_spec_object ("resource-factory",
"Resource Factory",
"The resource factory to use",
GUPNP_TYPE_RESOURCE_FACTORY,
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_READWRITE |
G_PARAM_STATIC_NAME |
G_PARAM_STATIC_NICK |
G_PARAM_STATIC_BLURB));
/**
* GUPnPControlPoint::device-proxy-available:
* @control_point: The #GUPnPControlPoint that received the signal
* @proxy: The now available #GUPnPDeviceProxy
*
* The ::device-proxy-available signal is emitted whenever a new
* device has become available.
**/
signals[DEVICE_PROXY_AVAILABLE] =
g_signal_new ("device-proxy-available",
GUPNP_TYPE_CONTROL_POINT,
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GUPnPControlPointClass,
device_proxy_available),
NULL,
NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE,
1,
GUPNP_TYPE_DEVICE_PROXY);
/**
* GUPnPControlPoint::device-proxy-unavailable:
* @control_point: The #GUPnPControlPoint that received the signal
* @proxy: The now unavailable #GUPnPDeviceProxy
*
* The ::device-proxy-unavailable signal is emitted whenever a
* device is not available any more.
**/
signals[DEVICE_PROXY_UNAVAILABLE] =
g_signal_new ("device-proxy-unavailable",
GUPNP_TYPE_CONTROL_POINT,
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GUPnPControlPointClass,
device_proxy_unavailable),
NULL,
NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE,
1,
GUPNP_TYPE_DEVICE_PROXY);
/**
* GUPnPControlPoint::service-proxy-available:
* @control_point: The #GUPnPControlPoint that received the signal
* @proxy: The now available #GUPnPServiceProxy
*
* The ::service-proxy-available signal is emitted whenever a new
* service has become available.
**/
signals[SERVICE_PROXY_AVAILABLE] =
g_signal_new ("service-proxy-available",
GUPNP_TYPE_CONTROL_POINT,
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GUPnPControlPointClass,
service_proxy_available),
NULL,
NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE,
1,
GUPNP_TYPE_SERVICE_PROXY);
/**
* GUPnPControlPoint::service-proxy-unavailable:
* @control_point: The #GUPnPControlPoint that received the signal
* @proxy: The now unavailable #GUPnPServiceProxy
*
* The ::service-proxy-unavailable signal is emitted whenever a
* service is not available any more.
**/
signals[SERVICE_PROXY_UNAVAILABLE] =
g_signal_new ("service-proxy-unavailable",
GUPNP_TYPE_CONTROL_POINT,
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GUPnPControlPointClass,
service_proxy_unavailable),
NULL,
NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE,
1,
GUPNP_TYPE_SERVICE_PROXY);
}
/**
* gupnp_control_point_new:
* @context: A #GUPnPContext
* @target: The search target
*
* Create a new #GUPnPControlPoint with the specified @context and @target.
*
* @target should be a service or device name, such as
* <literal>urn:schemas-upnp-org:service:WANIPConnection:1</literal> or
* <literal>urn:schemas-upnp-org:device:MediaRenderer:1</literal>.
*
* Return value: A new #GUPnPControlPoint object.
**/
GUPnPControlPoint *
gupnp_control_point_new (GUPnPContext *context,
const char *target)
{
g_return_val_if_fail (GUPNP_IS_CONTEXT (context), NULL);
g_return_val_if_fail (target, NULL);
return g_object_new (GUPNP_TYPE_CONTROL_POINT,
"client", context,
"target", target,
NULL);
}
/**
* gupnp_control_point_new_full:
* @context: A #GUPnPContext
* @factory: A #GUPnPResourceFactory
* @target: The search target
*
* Create a new #GUPnPControlPoint with the specified @context, @factory and
* @target.
*
* @target should be a service or device name, such as
* <literal>urn:schemas-upnp-org:service:WANIPConnection:1</literal> or
* <literal>urn:schemas-upnp-org:device:MediaRenderer:1</literal>.
*
* Return value: A new #GUPnPControlPoint object.
**/
GUPnPControlPoint *
gupnp_control_point_new_full (GUPnPContext *context,
GUPnPResourceFactory *factory,
const char *target)
{
g_return_val_if_fail (GUPNP_IS_CONTEXT (context), NULL);
g_return_val_if_fail (factory == NULL ||
GUPNP_IS_RESOURCE_FACTORY (factory), NULL);
g_return_val_if_fail (target, NULL);
return g_object_new (GUPNP_TYPE_CONTROL_POINT,
"client", context,
"target", target,
"resource-factory", factory,
NULL);
}
/**
* gupnp_control_point_get_context:
* @control_point: A #GUPnPControlPoint
*
* Get the #GUPnPControlPoint associated with @control_point.
*
* Returns: (transfer none): The #GUPnPContext.
**/
GUPnPContext *
gupnp_control_point_get_context (GUPnPControlPoint *control_point)
{
GSSDPClient *client;
g_return_val_if_fail (GUPNP_IS_CONTROL_POINT (control_point), NULL);
client = gssdp_resource_browser_get_client
(GSSDP_RESOURCE_BROWSER (control_point));
return GUPNP_CONTEXT (client);
}
/**
* gupnp_control_point_list_device_proxies:
* @control_point: A #GUPnPControlPoint
*
* Get the #GList of discovered #GUPnPDeviceProxy objects. Do not free the list
* nor its elements.
*
* Return value: (element-type GUPnP.DeviceProxy) (transfer none): a #GList of
* #GUPnPDeviceProxy objects.
**/
const GList *
gupnp_control_point_list_device_proxies (GUPnPControlPoint *control_point)
{
g_return_val_if_fail (GUPNP_IS_CONTROL_POINT (control_point), NULL);
return (const GList *) control_point->priv->devices;
}
/**
* gupnp_control_point_list_service_proxies:
* @control_point: A #GUPnPControlPoint
*
* Get the #GList of discovered #GUPnPServiceProxy objects. Do not free the
* list nor its elements.
*
* Return value: (element-type GUPnP.ServiceProxy) (transfer none): a #GList
* of #GUPnPServiceProxy objects.
**/
const GList *
gupnp_control_point_list_service_proxies (GUPnPControlPoint *control_point)
{
g_return_val_if_fail (GUPNP_IS_CONTROL_POINT (control_point), NULL);
return (const GList *) control_point->priv->services;
}
/**
* gupnp_control_point_get_resource_factory:
* @control_point: A #GUPnPControlPoint
*
* Get the #GUPnPResourceFactory used by the @control_point.
*
* Returns: (transfer none): A #GUPnPResourceFactory.
**/
GUPnPResourceFactory *
gupnp_control_point_get_resource_factory (GUPnPControlPoint *control_point)
{
g_return_val_if_fail (GUPNP_IS_CONTROL_POINT (control_point), NULL);
if (control_point->priv->factory)
return control_point->priv->factory;
return gupnp_resource_factory_get_default ();
}