diff options
author | Regis Merlino <regis.merlino@intel.com> | 2013-03-11 10:54:56 -0700 |
---|---|---|
committer | Regis Merlino <regis.merlino@intel.com> | 2013-03-11 10:54:56 -0700 |
commit | c5a606affb909010ab43c1ef00e5750546fd1bb3 (patch) | |
tree | 19f1780f78caee4bfa33c9c42aa3f0b95dc68d29 /libdleyna | |
parent | 8de18eed056cefc2b91e6bb16e00e52378718ac0 (diff) | |
download | dleyna-renderer-c5a606affb909010ab43c1ef00e5750546fd1bb3.tar.gz |
[Architecture] Change directory structure to enable build from a master project
Signed-off-by: Regis Merlino <regis.merlino@intel.com>
Diffstat (limited to 'libdleyna')
-rw-r--r-- | libdleyna/renderer/Makefile.am | 61 | ||||
-rw-r--r-- | libdleyna/renderer/async.c | 72 | ||||
-rw-r--r-- | libdleyna/renderer/async.h | 54 | ||||
-rw-r--r-- | libdleyna/renderer/control-point-renderer.h | 30 | ||||
-rw-r--r-- | libdleyna/renderer/device.c | 2373 | ||||
-rw-r--r-- | libdleyna/renderer/device.h | 149 | ||||
-rw-r--r-- | libdleyna/renderer/dleyna-renderer-1.0.pc.in | 11 | ||||
-rw-r--r-- | libdleyna/renderer/dleyna-renderer-service.conf.in | 37 | ||||
-rw-r--r-- | libdleyna/renderer/host-service.c | 567 | ||||
-rw-r--r-- | libdleyna/renderer/host-service.h | 43 | ||||
-rw-r--r-- | libdleyna/renderer/prop-defs.h | 70 | ||||
-rw-r--r-- | libdleyna/renderer/server.c | 917 | ||||
-rw-r--r-- | libdleyna/renderer/server.h | 40 | ||||
-rw-r--r-- | libdleyna/renderer/task.c | 378 | ||||
-rw-r--r-- | libdleyna/renderer/task.h | 174 | ||||
-rw-r--r-- | libdleyna/renderer/upnp.c | 752 | ||||
-rw-r--r-- | libdleyna/renderer/upnp.h | 103 |
17 files changed, 5831 insertions, 0 deletions
diff --git a/libdleyna/renderer/Makefile.am b/libdleyna/renderer/Makefile.am new file mode 100644 index 0000000..94d7b9e --- /dev/null +++ b/libdleyna/renderer/Makefile.am @@ -0,0 +1,61 @@ +libdleyna_rendererincdir = $(includedir)/dleyna-1.0/libdleyna/renderer + +DLEYNA_RENDERER_VERSION = 1:0:0 + +AM_CFLAGS = $(GLIB_CFLAGS) \ + $(GIO_CFLAGS) \ + $(DLEYNA_CORE_CFLAGS) \ + $(GSSDP_CFLAGS) \ + $(GUPNP_CFLAGS) \ + $(GUPNPAV_CFLAGS) \ + $(GUPNPDLNA_CFLAGS) \ + $(SOUP_CFLAGS) \ + -include config.h + +ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS} + +lib_LTLIBRARIES = libdleyna-renderer-1.0.la + +libdleyna_rendererinc_HEADERS = control-point-renderer.h + +libdleyna_renderer_1_0_la_LDFLAGS = -version-info $(DLEYNA_RENDERER_VERSION) \ + -no-undefined + +libdleyna_renderer_1_0_la_SOURCES = $(libdleyna_rendererinc_HEADERS) \ + async.c \ + device.c \ + host-service.c \ + server.c \ + task.c \ + upnp.c + +libdleyna_renderer_1_0_la_LIBADD = $(GLIB_LIBS) \ + $(GIO_LIBS) \ + $(DLEYNA_CORE_LIBS) \ + $(GSSDP_LIBS) \ + $(GUPNP_LIBS) \ + $(GUPNPAV_LIBS) \ + $(GUPNPDLNA_LIBS) \ + $(SOUP_LIBS) + +MAINTAINERCLEANFILES = Makefile.in \ + aclocal.m4 \ + configure \ + config.h.in \ + config.h.in~ \ + build-aux/depcomp \ + build-aux/compile \ + build-aux/missing \ + build-aux/install-sh + +sysconf_DATA = dleyna-renderer-service.conf + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = dleyna-renderer-1.0.pc + +EXTRA_DIST = $(sysconf_DATA) +CLEANFILES = $(pkgconfig_DATA) dleyna-renderer-service.conf +DISTCLEANFILES = $(pkgconfig_DATA) dleyna-renderer-service.conf + +maintainer-clean-local: + rm -rf build-aux diff --git a/libdleyna/renderer/async.c b/libdleyna/renderer/async.c new file mode 100644 index 0000000..03d48a6 --- /dev/null +++ b/libdleyna/renderer/async.c @@ -0,0 +1,72 @@ +/* + * dLeyna + * + * Copyright (C) 2012-2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Mark Ryan <mark.d.ryan@intel.com> + * + */ + +#include <libdleyna/core/error.h> +#include <libdleyna/core/log.h> + +#include "async.h" + +void dlr_async_task_delete(dlr_async_task_t *task) +{ + if (task->free_private) + task->free_private(task->private); + if (task->cancellable) + g_object_unref(task->cancellable); +} + +gboolean dlr_async_task_complete(gpointer user_data) +{ + dlr_async_task_t *cb_data = user_data; + + DLEYNA_LOG_DEBUG("Enter. Error %p", (void *)cb_data->error); + DLEYNA_LOG_DEBUG_NL(); + + if (cb_data->proxy != NULL) + g_object_remove_weak_pointer((G_OBJECT(cb_data->proxy)), + (gpointer *)&cb_data->proxy); + + cb_data->cb(&cb_data->task, cb_data->error); + + return FALSE; +} + +void dlr_async_task_cancelled(GCancellable *cancellable, gpointer user_data) +{ + dlr_async_task_t *cb_data = user_data; + + if (cb_data->proxy != NULL) + gupnp_service_proxy_cancel_action(cb_data->proxy, + cb_data->action); + + if (!cb_data->error) + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_CANCELLED, + "Operation cancelled."); + + (void) g_idle_add(dlr_async_task_complete, cb_data); +} + +void dlr_async_task_cancel(dlr_async_task_t *task) +{ + if (task->cancellable) + g_cancellable_cancel(task->cancellable); +} diff --git a/libdleyna/renderer/async.h b/libdleyna/renderer/async.h new file mode 100644 index 0000000..cf494c7 --- /dev/null +++ b/libdleyna/renderer/async.h @@ -0,0 +1,54 @@ +/* + * dLeyna + * + * Copyright (C) 2012-2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Mark Ryan <mark.d.ryan@intel.com> + * + */ + +#ifndef DLR_ASYNC_H__ +#define DLR_ASYNC_H__ + +#include <libgupnp/gupnp-control-point.h> + +#include "device.h" +#include "task.h" +#include "upnp.h" + +typedef struct dlr_async_task_t_ dlr_async_task_t; +struct dlr_async_task_t_ { + dlr_task_t task; /* pseudo inheritance - MUST be first field */ + dlr_upnp_task_complete_t cb; + GError *error; + GUPnPServiceProxyAction *action; + GUPnPServiceProxy *proxy; + GCancellable *cancellable; + gulong cancel_id; + gpointer private; + GDestroyNotify free_private; + dlr_device_t *device; +}; + +gboolean dlr_async_task_complete(gpointer user_data); + +void dlr_async_task_cancelled(GCancellable *cancellable, gpointer user_data); + +void dlr_async_task_delete(dlr_async_task_t *task); + +void dlr_async_task_cancel(dlr_async_task_t *task); + +#endif /* DLR_ASYNC_H__ */ diff --git a/libdleyna/renderer/control-point-renderer.h b/libdleyna/renderer/control-point-renderer.h new file mode 100644 index 0000000..37feb23 --- /dev/null +++ b/libdleyna/renderer/control-point-renderer.h @@ -0,0 +1,30 @@ +/* + * dLeyna + * + * Copyright (C) 2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Regis Merlino <regis.merlino@intel.com> + * + */ + +#ifndef DLEYNA_CONTROL_POINT_RENDERER_H__ +#define DLEYNA_CONTROL_POINT_RENDERER_H__ + +#include <libdleyna/core/control-point.h> + +const dleyna_control_point_t *dleyna_control_point_get_renderer(void); + +#endif /* DLEYNA_CONTROL_POINT_RENDERER_H__ */ diff --git a/libdleyna/renderer/device.c b/libdleyna/renderer/device.c new file mode 100644 index 0000000..7ff06b8 --- /dev/null +++ b/libdleyna/renderer/device.c @@ -0,0 +1,2373 @@ +/* + * dLeyna + * + * Copyright (C) 2012-2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Mark Ryan <mark.d.ryan@intel.com> + * + */ + + +#include <string.h> +#include <math.h> + +#include <libgupnp/gupnp-control-point.h> +#include <libgupnp-av/gupnp-av.h> + +#include <libdleyna/core/error.h> +#include <libdleyna/core/log.h> +#include <libdleyna/core/service-task.h> + +#include "async.h" +#include "device.h" +#include "prop-defs.h" +#include "server.h" + +typedef void (*dlr_device_local_cb_t)(dlr_async_task_t *cb_data); + +typedef struct dlr_device_data_t_ dlr_device_data_t; +struct dlr_device_data_t_ { + dlr_device_local_cb_t local_cb; +}; + +/* Private structure used in chain task */ +typedef struct prv_new_device_ct_t_ prv_new_device_ct_t; +struct prv_new_device_ct_t_ { + dlr_device_t *dev; + const dleyna_connector_dispatch_cb_t *dispatch_table; +}; + +static void prv_last_change_cb(GUPnPServiceProxy *proxy, + const char *variable, + GValue *value, + gpointer user_data); + +static void prv_sink_change_cb(GUPnPServiceProxy *proxy, + const char *variable, + GValue *value, + gpointer user_data); + +static void prv_rc_last_change_cb(GUPnPServiceProxy *proxy, + const char *variable, + GValue *value, + gpointer user_data); + +static void prv_props_update(dlr_device_t *device, dlr_task_t *task); + +static void prv_unref_variant(gpointer variant) +{ + GVariant *var = variant; + if (var) + g_variant_unref(var); +} + +static void prv_props_init(dlr_props_t *props) +{ + props->root_props = g_hash_table_new_full(g_str_hash, g_str_equal, + NULL, prv_unref_variant); + props->player_props = g_hash_table_new_full(g_str_hash, g_str_equal, + NULL, prv_unref_variant); + props->device_props = g_hash_table_new_full(g_str_hash, g_str_equal, + NULL, prv_unref_variant); + props->synced = FALSE; +} + +static void prv_props_free(dlr_props_t *props) +{ + g_hash_table_unref(props->root_props); + g_hash_table_unref(props->player_props); + g_hash_table_unref(props->device_props); +} + +static void prv_service_proxies_free(dlr_service_proxies_t *service_proxies) +{ + if (service_proxies->av_proxy) + g_object_unref(service_proxies->av_proxy); + if (service_proxies->rc_proxy) + g_object_unref(service_proxies->rc_proxy); + if (service_proxies->cm_proxy) + g_object_unref(service_proxies->cm_proxy); +} + +static void prv_context_unsubscribe(dlr_device_context_t *ctx) +{ + DLEYNA_LOG_DEBUG("Enter"); + + if (ctx->timeout_id_cm) { + (void) g_source_remove(ctx->timeout_id_cm); + ctx->timeout_id_cm = 0; + } + if (ctx->timeout_id_av) { + (void) g_source_remove(ctx->timeout_id_av); + ctx->timeout_id_av = 0; + } + if (ctx->timeout_id_rc) { + (void) g_source_remove(ctx->timeout_id_rc); + ctx->timeout_id_rc = 0; + } + + if (ctx->subscribed_cm) { + (void) gupnp_service_proxy_remove_notify( + ctx->service_proxies.cm_proxy, "SinkProtocolInfo", + prv_sink_change_cb, ctx->device); + + gupnp_service_proxy_set_subscribed( + ctx->service_proxies.cm_proxy, FALSE); + + ctx->subscribed_cm = FALSE; + } + if (ctx->subscribed_av) { + (void) gupnp_service_proxy_remove_notify( + ctx->service_proxies.av_proxy, "LastChange", + prv_last_change_cb, ctx->device); + + gupnp_service_proxy_set_subscribed( + ctx->service_proxies.av_proxy, FALSE); + + ctx->subscribed_av = FALSE; + } + if (ctx->subscribed_rc) { + (void) gupnp_service_proxy_remove_notify( + ctx->service_proxies.rc_proxy, "LastChange", + prv_rc_last_change_cb, ctx->device); + + gupnp_service_proxy_set_subscribed( + ctx->service_proxies.rc_proxy, FALSE); + + ctx->subscribed_rc = FALSE; + } + + DLEYNA_LOG_DEBUG("Exit"); +} + +static void prv_dlr_context_delete(gpointer context) +{ + dlr_device_context_t *ctx = context; + + if (ctx) { + prv_context_unsubscribe(ctx); + + g_free(ctx->ip_address); + if (ctx->device_proxy) + g_object_unref(ctx->device_proxy); + prv_service_proxies_free(&ctx->service_proxies); + g_free(ctx); + } +} + +static void prv_change_props(GHashTable *props, + const gchar *key, + GVariant *value, + GVariantBuilder *changed_props_vb) +{ + g_hash_table_insert(props, (gpointer) key, value); + if (changed_props_vb) + g_variant_builder_add(changed_props_vb, "{sv}", key, value); +} + +static void prv_emit_signal_properties_changed(dlr_device_t *device, + const char *interface, + GVariant *changed_props) +{ +#if DLR_LOG_LEVEL & DLR_LOG_LEVEL_DEBUG + gchar *params; +#endif + GVariant *val = g_variant_ref_sink(g_variant_new("(s@a{sv}as)", + interface, + changed_props, + NULL)); + + DLEYNA_LOG_DEBUG("Emitted Signal: %s.%s - ObjectPath: %s", + DLR_INTERFACE_PROPERTIES, + DLR_INTERFACE_PROPERTIES_CHANGED, + device->path); + +#if DLR_LOG_LEVEL & DLR_LOG_LEVEL_DEBUG + params = g_variant_print(val, FALSE); + DLEYNA_LOG_DEBUG("Params: %s", params); + g_free(params); +#endif + + dlr_renderer_get_connector()->notify(device->connection, + device->path, + DLR_INTERFACE_PROPERTIES, + DLR_INTERFACE_PROPERTIES_CHANGED, + val, + NULL); + + g_variant_unref(val); +} + +static void prv_merge_meta_data(dlr_device_t *device, + const gchar *key, + GVariant *value, + GVariantBuilder *changed_props_vb) +{ + GVariant *current_meta_data; + GVariantIter viter; + GVariantBuilder *vb; + GVariant *val; + gchar *vkey; + gboolean replaced = FALSE; + GVariant *new_val; + + vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); + + current_meta_data = g_hash_table_lookup(device->props.player_props, + DLR_INTERFACE_PROP_METADATA); + if (current_meta_data) { + g_variant_iter_init(&viter, current_meta_data); + while (g_variant_iter_next(&viter, "{&sv}", &vkey, &val)) { + if (!strcmp(key, vkey)) { + new_val = value; + replaced = TRUE; + } else { + new_val = val; + } + g_variant_builder_add(vb, "{sv}", vkey, new_val); + g_variant_unref(val); + } + } + + if (!replaced) + g_variant_builder_add(vb, "{sv}", key, value); + + val = g_variant_ref_sink(g_variant_builder_end(vb)); + prv_change_props(device->props.player_props, + DLR_INTERFACE_PROP_METADATA, + val, + changed_props_vb); + g_variant_builder_unref(vb); +} + +static void prv_context_new(const gchar *ip_address, + GUPnPDeviceProxy *proxy, + dlr_device_t *device, + dlr_device_context_t **context) +{ + const gchar *cm_type = + "urn:schemas-upnp-org:service:ConnectionManager"; + const gchar *av_type = + "urn:schemas-upnp-org:service:AVTransport"; + const gchar *rc_type = + "urn:schemas-upnp-org:service:RenderingControl"; + dlr_device_context_t *ctx = g_new(dlr_device_context_t, 1); + dlr_service_proxies_t *service_proxies = &ctx->service_proxies; + + ctx->ip_address = g_strdup(ip_address); + ctx->device_proxy = proxy; + ctx->device = device; + ctx->subscribed_av = FALSE; + ctx->subscribed_cm = FALSE; + ctx->subscribed_rc = FALSE; + ctx->timeout_id_av = 0; + ctx->timeout_id_cm = 0; + ctx->timeout_id_rc = 0; + + g_object_ref(proxy); + + service_proxies->cm_proxy = (GUPnPServiceProxy *) + gupnp_device_info_get_service((GUPnPDeviceInfo *)proxy, + cm_type); + service_proxies->av_proxy = (GUPnPServiceProxy *) + gupnp_device_info_get_service((GUPnPDeviceInfo *)proxy, + av_type); + service_proxies->rc_proxy = (GUPnPServiceProxy *) + gupnp_device_info_get_service((GUPnPDeviceInfo *)proxy, + rc_type); + + *context = ctx; +} + +static dlr_device_context_t *prv_device_get_subscribed_context( + const dlr_device_t *device) +{ + dlr_device_context_t *context; + unsigned int i; + + for (i = 0; i < device->contexts->len; ++i) { + context = g_ptr_array_index(device->contexts, i); + if (context->subscribed_av || context->subscribed_cm || + context->subscribed_rc) + goto on_found; + } + + return NULL; + +on_found: + + return context; +} + +static void prv_device_append_new_context(dlr_device_t *device, + const gchar *ip_address, + GUPnPDeviceProxy *proxy) +{ + dlr_device_context_t *new_context; + + prv_context_new(ip_address, proxy, device, &new_context); + g_ptr_array_add(device->contexts, new_context); +} + +static void prv_device_subscribe_context(dlr_device_t *device) +{ + dlr_device_context_t *subscribed_context; + dlr_device_context_t *preferred_context; + + subscribed_context = prv_device_get_subscribed_context(device); + preferred_context = dlr_device_get_context(device); + + if (subscribed_context != preferred_context) { + if (subscribed_context) { + DLEYNA_LOG_DEBUG( + "Subscription switch from <%s> to <%s>", + subscribed_context->ip_address, + preferred_context->ip_address); + prv_context_unsubscribe(subscribed_context); + } + dlr_device_subscribe_to_service_changes(device); + } +} + +void dlr_device_append_new_context(dlr_device_t *device, + const gchar *ip_address, + GUPnPDeviceProxy *proxy) +{ + prv_device_append_new_context(device, ip_address, proxy); + prv_device_subscribe_context(device); +} + +void dlr_device_delete(void *device) +{ + unsigned int i; + dlr_device_t *dev = device; + + if (dev) { + if (dev->timeout_id) + (void) g_source_remove(dev->timeout_id); + + for (i = 0; i < DLR_INTERFACE_INFO_MAX && dev->ids[i]; ++i) + (void) dlr_renderer_get_connector()->unpublish_object( + dev->connection, + dev->ids[i]); + g_ptr_array_unref(dev->contexts); + g_free(dev->path); + prv_props_free(&dev->props); + + if (dev->transport_play_speeds != NULL) + g_ptr_array_free(dev->transport_play_speeds, TRUE); + g_free(dev->rate); + g_free(dev); + } +} + +void dlr_device_unsubscribe(void *device) +{ + unsigned int i; + dlr_device_t *dev = device; + dlr_device_context_t *context; + + if (dev) { + for (i = 0; i < dev->contexts->len; ++i) { + context = g_ptr_array_index(dev->contexts, i); + prv_context_unsubscribe(context); + } + } +} + +static gboolean prv_re_enable_cm_subscription(gpointer user_data) +{ + dlr_device_context_t *context = user_data; + + context->timeout_id_cm = 0; + + return FALSE; +} + +static void prv_cm_subscription_lost_cb(GUPnPServiceProxy *proxy, + const GError *reason, + gpointer user_data) +{ + dlr_device_context_t *context = user_data; + dlr_service_proxies_t *service_proxies = &context->service_proxies; + + if (!context->timeout_id_cm) { + gupnp_service_proxy_set_subscribed(service_proxies->cm_proxy, + TRUE); + context->timeout_id_cm = g_timeout_add_seconds(10, + prv_re_enable_cm_subscription, + context); + } else { + g_source_remove(context->timeout_id_cm); + (void) gupnp_service_proxy_remove_notify( + service_proxies->cm_proxy, "SinkProtocolInfo", + prv_sink_change_cb, context->device); + + context->timeout_id_cm = 0; + context->subscribed_cm = FALSE; + } +} + +static gboolean prv_re_enable_av_subscription(gpointer user_data) +{ + dlr_device_context_t *context = user_data; + + context->timeout_id_av = 0; + + return FALSE; +} + +static void prv_av_subscription_lost_cb(GUPnPServiceProxy *proxy, + const GError *reason, + gpointer user_data) +{ + dlr_device_context_t *context = user_data; + dlr_service_proxies_t *service_proxies = &context->service_proxies; + + if (!context->timeout_id_av) { + gupnp_service_proxy_set_subscribed(service_proxies->av_proxy, + TRUE); + context->timeout_id_av = g_timeout_add_seconds(10, + prv_re_enable_av_subscription, + context); + } else { + g_source_remove(context->timeout_id_av); + (void) gupnp_service_proxy_remove_notify( + service_proxies->av_proxy, "LastChange", + prv_last_change_cb, context->device); + + context->timeout_id_av = 0; + context->subscribed_av = FALSE; + } +} + +static gboolean prv_re_enable_rc_subscription(gpointer user_data) +{ + dlr_device_context_t *context = user_data; + + context->timeout_id_rc = 0; + + return FALSE; +} + +static void prv_rc_subscription_lost_cb(GUPnPServiceProxy *proxy, + const GError *reason, + gpointer user_data) +{ + dlr_device_context_t *context = user_data; + dlr_service_proxies_t *service_proxies = &context->service_proxies; + + if (!context->timeout_id_rc) { + gupnp_service_proxy_set_subscribed(service_proxies->rc_proxy, + TRUE); + context->timeout_id_rc = g_timeout_add_seconds(10, + prv_re_enable_rc_subscription, + context); + } else { + g_source_remove(context->timeout_id_rc); + (void) gupnp_service_proxy_remove_notify( + service_proxies->rc_proxy, "LastChange", + prv_rc_last_change_cb, context->device); + + context->timeout_id_rc = 0; + context->subscribed_rc = FALSE; + } +} + +void dlr_device_subscribe_to_service_changes(dlr_device_t *device) +{ + dlr_device_context_t *context; + dlr_service_proxies_t *service_proxies; + + context = dlr_device_get_context(device); + service_proxies = &context->service_proxies; + + DLEYNA_LOG_DEBUG("Subscribing through context <%s>", + context->ip_address); + + if (service_proxies->cm_proxy) { + gupnp_service_proxy_set_subscribed(service_proxies->cm_proxy, + TRUE); + (void) gupnp_service_proxy_add_notify(service_proxies->cm_proxy, + "SinkProtocolInfo", + G_TYPE_STRING, + prv_sink_change_cb, + device); + context->subscribed_cm = TRUE; + + g_signal_connect(service_proxies->cm_proxy, + "subscription-lost", + G_CALLBACK(prv_cm_subscription_lost_cb), + context); + } + + if (service_proxies->av_proxy) { + gupnp_service_proxy_set_subscribed(service_proxies->av_proxy, + TRUE); + (void) gupnp_service_proxy_add_notify(service_proxies->av_proxy, + "LastChange", + G_TYPE_STRING, + prv_last_change_cb, + device); + context->subscribed_av = TRUE; + + g_signal_connect(service_proxies->av_proxy, + "subscription-lost", + G_CALLBACK(prv_av_subscription_lost_cb), + context); + } + + if (service_proxies->rc_proxy) { + gupnp_service_proxy_set_subscribed(service_proxies->rc_proxy, + TRUE); + (void) gupnp_service_proxy_add_notify(service_proxies->rc_proxy, + "LastChange", + G_TYPE_STRING, + prv_rc_last_change_cb, + device); + context->subscribed_rc = TRUE; + + g_signal_connect(service_proxies->av_proxy, + "subscription-lost", + G_CALLBACK(prv_rc_subscription_lost_cb), + context); + } +} + +static void prv_as_prop_from_hash_table(const gchar *prop_name, + GHashTable *values, GHashTable *props) +{ + GVariantBuilder vb; + GHashTableIter iter; + gpointer key; + GVariant *val; + + g_variant_builder_init(&vb, G_VARIANT_TYPE("as")); + g_hash_table_iter_init(&iter, values); + + while (g_hash_table_iter_next(&iter, &key, NULL)) + g_variant_builder_add(&vb, "s", key); + + val = g_variant_ref_sink(g_variant_builder_end(&vb)); + g_hash_table_insert(props, (gchar *)prop_name, val); +} + +static void prv_process_protocol_info(dlr_device_t *device, + const gchar *protocol_info) +{ + GVariant *val; + gchar **entries; + gchar **type_info; + unsigned int i; + GHashTable *protocols; + GHashTable *types; + const char http_prefix[] = "http-"; + + DLEYNA_LOG_DEBUG("Enter"); + DLEYNA_LOG_DEBUG("prv_process_protocol_info: %s", protocol_info); + + protocols = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, + NULL); + types = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + + val = g_variant_ref_sink(g_variant_new_string(protocol_info)); + g_hash_table_insert(device->props.device_props, + DLR_INTERFACE_PROP_PROTOCOL_INFO, + val); + + entries = g_strsplit(protocol_info, ",", 0); + + for (i = 0; entries[i]; ++i) { + type_info = g_strsplit(entries[i], ":", 0); + + if (type_info[0] && type_info[1] && type_info[2]) { + if (!g_ascii_strncasecmp(http_prefix, type_info[0], + sizeof(http_prefix) - 1)) { + type_info[0][sizeof(http_prefix) - 2] = 0; + } + + g_hash_table_insert(protocols, + g_ascii_strdown(type_info[0], -1), + NULL); + g_hash_table_insert(types, + g_ascii_strdown(type_info[2], -1), + NULL); + } + + g_strfreev(type_info); + } + + g_strfreev(entries); + + prv_as_prop_from_hash_table(DLR_INTERFACE_PROP_SUPPORTED_URIS, + protocols, + device->props.root_props); + + prv_as_prop_from_hash_table(DLR_INTERFACE_PROP_SUPPORTED_MIME, + types, + device->props.root_props); + + g_hash_table_unref(types); + g_hash_table_unref(protocols); + + DLEYNA_LOG_DEBUG("Exit"); +} + +static void prv_get_protocol_info_cb(GUPnPServiceProxy *proxy, + GUPnPServiceProxyAction *action, + gpointer user_data) +{ + gchar *result = NULL; + GError *error = NULL; + prv_new_device_ct_t *priv_t = (prv_new_device_ct_t *)user_data; + + DLEYNA_LOG_DEBUG("Enter"); + + if (!gupnp_service_proxy_end_action(proxy, action, &error, "Sink", + G_TYPE_STRING, &result, NULL)) { + DLEYNA_LOG_WARNING("GetProtocolInfo operation failed: %s", + error->message); + goto on_error; + } + + prv_process_protocol_info(priv_t->dev, result); + +on_error: + + if (error) + g_error_free(error); + + g_free(result); + + DLEYNA_LOG_DEBUG("Exit"); +} + +static GUPnPServiceProxyAction *prv_get_protocol_info( + dleyna_service_task_t *task, + GUPnPServiceProxy *proxy, + gboolean *failed) +{ + *failed = FALSE; + + return gupnp_service_proxy_begin_action( + proxy, "GetProtocolInfo", + dleyna_service_task_begin_action_cb, + task, NULL); +} + +static GUPnPServiceProxyAction *prv_subscribe(dleyna_service_task_t *task, + GUPnPServiceProxy *proxy, + gboolean *failed) +{ + dlr_device_t *device; + + DLEYNA_LOG_DEBUG("Enter"); + + device = (dlr_device_t *)dleyna_service_task_get_user_data(task); + + prv_device_subscribe_context(device); + + *failed = FALSE; + + DLEYNA_LOG_DEBUG("Exit"); + + return NULL; +} + +static GUPnPServiceProxyAction *prv_declare(dleyna_service_task_t *task, + GUPnPServiceProxy *proxy, + gboolean *failed) +{ + unsigned int i; + dlr_device_t *device; + prv_new_device_ct_t *priv_t; + const dleyna_connector_dispatch_cb_t *table; + + DLEYNA_LOG_DEBUG("Enter"); + + *failed = FALSE; + + priv_t = (prv_new_device_ct_t *)dleyna_service_task_get_user_data(task); + device = priv_t->dev; + + table = priv_t->dispatch_table; + + for (i = 0; i < DLR_INTERFACE_INFO_MAX; ++i) { + device->ids[i] = dlr_renderer_get_connector()->publish_object( + device->connection, + device->path, + FALSE, + i, + table + i); + + if (!device->ids[i]) { + *failed = TRUE; + goto on_error; + } + } + +on_error: + +DLEYNA_LOG_DEBUG("Exit"); + + return NULL; +} + +dlr_device_t *dlr_device_new( + dleyna_connector_id_t connection, + GUPnPDeviceProxy *proxy, + const gchar *ip_address, + guint counter, + const dleyna_connector_dispatch_cb_t *dispatch_table, + const dleyna_task_queue_key_t *queue_id) +{ + dlr_device_t *dev = g_new0(dlr_device_t, 1); + prv_new_device_ct_t *priv_t; + gchar *new_path; + dlr_device_context_t *context; + GUPnPServiceProxy *s_proxy; + + DLEYNA_LOG_DEBUG("New Device on %s", ip_address); + + new_path = g_strdup_printf("%s/%u", DLEYNA_SERVER_PATH, counter); + DLEYNA_LOG_DEBUG("Server Path %s", new_path); + + dev = g_new0(dlr_device_t, 1); + priv_t = g_new0(prv_new_device_ct_t, 1); + + dev->connection = connection; + dev->contexts = g_ptr_array_new_with_free_func(prv_dlr_context_delete); + dev->path = new_path; + dev->rate = g_strdup("1"); + + priv_t->dev = dev; + priv_t->dispatch_table = dispatch_table; + + prv_props_init(&dev->props); + + prv_device_append_new_context(dev, ip_address, proxy); + + context = dlr_device_get_context(dev); + s_proxy = context->service_proxies.cm_proxy; + + dleyna_service_task_add(queue_id, prv_get_protocol_info, s_proxy, + prv_get_protocol_info_cb, NULL, priv_t); + + dleyna_service_task_add(queue_id, prv_subscribe, s_proxy, + NULL, NULL, dev); + + dleyna_service_task_add(queue_id, prv_declare, s_proxy, + NULL, g_free, priv_t); + + dleyna_task_queue_start(queue_id); + + DLEYNA_LOG_DEBUG("Exit"); + + return dev; +} + +dlr_device_t *dlr_device_from_path(const gchar *path, GHashTable *device_list) +{ + GHashTableIter iter; + gpointer value; + dlr_device_t *device; + dlr_device_t *retval = NULL; + + g_hash_table_iter_init(&iter, device_list); + while (g_hash_table_iter_next(&iter, NULL, &value)) { + device = value; + if (!strcmp(device->path, path)) { + retval = device; + break; + } + } + + return retval; +} + +dlr_device_context_t *dlr_device_get_context(dlr_device_t *device) +{ + dlr_device_context_t *context; + unsigned int i; + const char ip4_local_prefix[] = "127.0.0."; + + for (i = 0; i < device->contexts->len; ++i) { + context = g_ptr_array_index(device->contexts, i); + if (!strncmp(context->ip_address, ip4_local_prefix, + sizeof(ip4_local_prefix) - 1) || + !strcmp(context->ip_address, "::1") || + !strcmp(context->ip_address, "0:0:0:0:0:0:0:1")) + break; + } + + if (i == device->contexts->len) + context = g_ptr_array_index(device->contexts, 0); + + return context; +} + +static void prv_get_prop(dlr_async_task_t *cb_data) +{ + dlr_task_get_prop_t *get_prop = &cb_data->task.ut.get_prop; + GVariant *res = NULL; + + DLEYNA_LOG_DEBUG("Enter"); + + if (!strcmp(get_prop->interface_name, + DLEYNA_SERVER_INTERFACE_RENDERER_DEVICE)) { + res = g_hash_table_lookup(cb_data->device->props.device_props, + get_prop->prop_name); + } else if (!strcmp(get_prop->interface_name, DLR_INTERFACE_SERVER)) { + res = g_hash_table_lookup(cb_data->device->props.root_props, + get_prop->prop_name); + } else if (!strcmp(get_prop->interface_name, DLR_INTERFACE_PLAYER)) { + res = g_hash_table_lookup(cb_data->device->props.player_props, + get_prop->prop_name); + } else if (!strcmp(get_prop->interface_name, "")) { + res = g_hash_table_lookup(cb_data->device->props.root_props, + get_prop->prop_name); + if (!res) + res = g_hash_table_lookup( + cb_data->device->props.player_props, + get_prop->prop_name); + + if (!res) + res = g_hash_table_lookup( + cb_data->device->props.device_props, + get_prop->prop_name); + } else { + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_UNKNOWN_INTERFACE, + "Unknown Interface"); + } + + if (!res) { + if (!cb_data->error) + cb_data->error = + g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_UNKNOWN_PROPERTY, + "Property not defined for object"); + } else { + cb_data->task.result = g_variant_ref(res); + } + + DLEYNA_LOG_DEBUG("Exit"); +} + +static void prv_add_props(GHashTable *props, GVariantBuilder *vb) +{ + GHashTableIter iter; + gpointer key; + gpointer value; + + g_hash_table_iter_init(&iter, props); + + while (g_hash_table_iter_next(&iter, &key, &value)) + g_variant_builder_add(vb, "{sv}", (gchar *)key, + (GVariant *)value); +} + +static void prv_get_props(dlr_async_task_t *cb_data) +{ + dlr_task_get_props_t *get_props = &cb_data->task.ut.get_props; + GVariantBuilder *vb; + + DLEYNA_LOG_DEBUG("Enter"); + + vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); + + if (!strcmp(get_props->interface_name, + DLEYNA_SERVER_INTERFACE_RENDERER_DEVICE)) { + prv_add_props(cb_data->device->props.device_props, vb); + } else if (!strcmp(get_props->interface_name, DLR_INTERFACE_SERVER)) { + prv_add_props(cb_data->device->props.root_props, vb); + prv_add_props(cb_data->device->props.device_props, vb); + } else if (!strcmp(get_props->interface_name, DLR_INTERFACE_PLAYER)) { + prv_add_props(cb_data->device->props.player_props, vb); + } else if (!strcmp(get_props->interface_name, "")) { + prv_add_props(cb_data->device->props.root_props, vb); + prv_add_props(cb_data->device->props.player_props, vb); + prv_add_props(cb_data->device->props.device_props, vb); + } else { + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_UNKNOWN_INTERFACE, + "Unknown Interface"); + goto on_error; + } + + cb_data->task.result = g_variant_ref_sink(g_variant_builder_end(vb)); + +on_error: + + g_variant_builder_unref(vb); + + DLEYNA_LOG_DEBUG("Exit"); +} + +static const gchar *prv_map_transport_state(const gchar *upnp_state) +{ + const gchar *retval; + + if (!strcmp(upnp_state, "PLAYING")) + retval = "Playing"; + else if (!strcmp(upnp_state, "PAUSED_PLAYBACK") || + !strcmp(upnp_state, "PAUSED_RECORDING")) + retval = "Paused"; + else + retval = "Stopped"; + + return retval; +} + +static gdouble prv_map_transport_speed(const gchar *upnp_speed) +{ + gdouble retval = 1; + gchar **parts = NULL; + gint num; + gint dom; + + if (upnp_speed[0]) { + parts = g_strsplit(upnp_speed, "/", 0); + if (!parts[0]) + goto on_error; + + g_strstrip(parts[0]); + num = atoi(parts[0]); + + if (parts[1]) { + if (parts[2]) + goto on_error; + + g_strstrip(parts[1]); + dom = atoi(parts[1]); + if (dom == 0) + goto on_error; + + retval = num / (gdouble) dom; + } else { + retval = num; + } + } + +on_error: + + if (parts) + g_strfreev(parts); + + return retval; +} + +static void prv_add_actions(dlr_device_t *device, + const gchar *actions, + GVariantBuilder *changed_props_vb) +{ + gchar **parts; + unsigned int i = 0; + GVariant *true_val; + GVariant *false_val; + gboolean play = FALSE; + gboolean ppause = FALSE; + gboolean seek = FALSE; + gboolean next = FALSE; + gboolean previous = FALSE; + GVariant *val; + + parts = g_strsplit(actions, ",", 0); + + true_val = g_variant_ref_sink(g_variant_new_boolean(TRUE)); + false_val = g_variant_ref_sink(g_variant_new_boolean(FALSE)); + + while (parts[i]) { + g_strstrip(parts[i]); + + if (!strcmp(parts[i], "Play")) + play = TRUE; + else if (!strcmp(parts[i], "Pause")) + ppause = TRUE; + else if (!strcmp(parts[i], "Seek")) + seek = TRUE; + else if (!strcmp(parts[i], "Next")) + next = TRUE; + else if (!strcmp(parts[i], "Previous")) + previous = TRUE; + ++i; + } + + g_variant_ref(false_val); + prv_change_props(device->props.player_props, + DLR_INTERFACE_PROP_CAN_CONTROL, false_val, + changed_props_vb); + + val = play ? true_val : false_val; + g_variant_ref(val); + prv_change_props(device->props.player_props, + DLR_INTERFACE_PROP_CAN_PLAY, val, + changed_props_vb); + + val = ppause ? true_val : false_val; + g_variant_ref(val); + prv_change_props(device->props.player_props, + DLR_INTERFACE_PROP_CAN_PAUSE, val, + changed_props_vb); + + val = seek ? true_val : false_val; + g_variant_ref(val); + prv_change_props(device->props.player_props, + DLR_INTERFACE_PROP_CAN_SEEK, val, + changed_props_vb); + + val = next ? true_val : false_val; + g_variant_ref(val); + prv_change_props(device->props.player_props, + DLR_INTERFACE_PROP_CAN_NEXT, val, + changed_props_vb); + + val = previous ? true_val : false_val; + g_variant_ref(val); + prv_change_props(device->props.player_props, + DLR_INTERFACE_PROP_CAN_PREVIOUS, val, + changed_props_vb); + + g_variant_unref(true_val); + g_variant_unref(false_val); + g_strfreev(parts); +} + +static void prv_add_all_actions(dlr_device_t *device, + GVariantBuilder *changed_props_vb) +{ + GVariant *val; + + val = g_variant_ref_sink(g_variant_new_boolean(TRUE)); + prv_change_props(device->props.player_props, + DLR_INTERFACE_PROP_CAN_PLAY, val, + changed_props_vb); + prv_change_props(device->props.player_props, + DLR_INTERFACE_PROP_CAN_PAUSE, g_variant_ref(val), + changed_props_vb); + prv_change_props(device->props.player_props, + DLR_INTERFACE_PROP_CAN_SEEK, g_variant_ref(val), + changed_props_vb); + prv_change_props(device->props.player_props, + DLR_INTERFACE_PROP_CAN_NEXT, g_variant_ref(val), + changed_props_vb); + prv_change_props(device->props.player_props, + DLR_INTERFACE_PROP_CAN_PREVIOUS, g_variant_ref(val), + changed_props_vb); + prv_change_props(device->props.player_props, + DLR_INTERFACE_PROP_CAN_CONTROL, g_variant_ref(val), + changed_props_vb); +} + +static gint64 prv_duration_to_int64(const gchar *duration) +{ + gchar **parts; + unsigned int i = 0; + unsigned int count; + gint64 pos = 0; + + parts = g_strsplit(duration, ":", 0); + for (count = 0; parts[count]; ++count) + ; + + if (count != 3) + goto on_error; + + /* TODO: This does not handle fractional seconds */ + + i = 1; + do { + --count; + g_strstrip(parts[count]); + pos += atoi(parts[count]) * i; + i *= 60; + } while (count > 0); + + pos *= 1000000; + +on_error: + + g_strfreev(parts); + + return pos; +} + +static gchar *prv_int64_to_duration(gint64 micro_seconds) +{ + GString *retval; + unsigned int seconds; + + if (micro_seconds < 0) { + retval = g_string_new("-"); + micro_seconds = -micro_seconds; + } else { + retval = g_string_new(""); + } + + /* TODO: This does not handle fractional seconds */ + + seconds = micro_seconds / 1000000; + g_string_append_printf(retval, "%02u:%02u:%02u", + seconds / 3600, + (seconds / 60) % 60, + seconds % 60); + + return g_string_free(retval, FALSE); +} + +static void prv_add_reltime(dlr_device_t *device, + const gchar *reltime, + GVariantBuilder *changed_props_vb) +{ + GVariant *val; + gint64 pos = prv_duration_to_int64(reltime); + + val = g_variant_ref_sink(g_variant_new_int64(pos)); + prv_change_props(device->props.player_props, + DLR_INTERFACE_PROP_POSITION, val, + changed_props_vb); +} + +static void prv_found_item(GUPnPDIDLLiteParser *parser, + GUPnPDIDLLiteObject *object, + gpointer user_data) +{ + GVariantBuilder *vb = user_data; + gchar *track_id; + int track_number = gupnp_didl_lite_object_get_track_number(object); + GVariant *value; + const gchar *str_value; + GVariantBuilder *artists_vb; + GVariantBuilder *album_artists_vb; + GList *artists; + GList *head; + const gchar *artist_name; + const gchar *artist_role; + + track_id = g_strdup_printf(DLEYNA_SERVER_OBJECT"/track/%u", + track_number != -1 ? track_number : 0); + + value = g_variant_new_string(track_id); + g_variant_builder_add(vb, "{sv}", "mpris:trackid", value); + g_free(track_id); + + if (track_number != -1) { + value = g_variant_new_int32(track_number); + g_variant_builder_add(vb, "{sv}", "mpris:trackNumber", value); + } + + str_value = gupnp_didl_lite_object_get_title(object); + if (str_value) { + value = g_variant_new_string(str_value); + g_variant_builder_add(vb, "{sv}", "xesam:title", value); + } + + str_value = gupnp_didl_lite_object_get_album_art(object); + if (str_value) { + value = g_variant_new_string(str_value); + g_variant_builder_add(vb, "{sv}", "mpris:artUrl", value); + } + + str_value = gupnp_didl_lite_object_get_album(object); + if (str_value) { + value = g_variant_new_string(str_value); + g_variant_builder_add(vb, "{sv}", "xesam:album", value); + } + + str_value = gupnp_didl_lite_object_get_genre(object); + if (str_value) { + value = g_variant_new_string(str_value); + g_variant_builder_add(vb, "{sv}", "xesam:genre", value); + } + + artists = gupnp_didl_lite_object_get_artists(object); + head = artists; + + if (artists) { + artists_vb = g_variant_builder_new(G_VARIANT_TYPE("as")); + album_artists_vb = g_variant_builder_new(G_VARIANT_TYPE("as")); + do { + artist_name = + gupnp_didl_lite_contributor_get_name( + artists->data); + artist_role = gupnp_didl_lite_contributor_get_role( + artists->data); + if (!artist_role) + g_variant_builder_add(artists_vb, "s", + artist_name); + else if (!strcmp(artist_role, "AlbumArtist")) + g_variant_builder_add(album_artists_vb, "s", + artist_name); + g_object_unref(artists->data); + artists = g_list_next(artists); + } while (artists); + g_list_free(head); + value = g_variant_builder_end(artists_vb); + g_variant_builder_add(vb, "{sv}", "xesam:artist", value); + value = g_variant_builder_end(album_artists_vb); + g_variant_builder_add(vb, "{sv}", "xesam:albumArtist", value); + g_variant_builder_unref(artists_vb); + g_variant_builder_unref(album_artists_vb); + } +} + +static void prv_add_track_meta_data(dlr_device_t *device, + const gchar *metadata, + const gchar *duration, + const gchar *uri, + GVariantBuilder *changed_props_vb) +{ + gchar *didl = g_strdup_printf("<DIDL-Lite>%s</DIDL-Lite>", metadata); + GUPnPDIDLLiteParser *parser = NULL; + GVariantBuilder *vb; + GError *upnp_error = NULL; + GVariant *val; + gint error_code; + + parser = gupnp_didl_lite_parser_new(); + + vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); + + if (duration) { + val = g_variant_new_int64(prv_duration_to_int64(duration)); + g_variant_builder_add(vb, "{sv}", "mpris:length", val); + } + + if (uri) { + val = g_variant_new_string(uri); + g_variant_builder_add(vb, "{sv}", "xesam:url", val); + } + + g_signal_connect(parser, "object-available" , + G_CALLBACK(prv_found_item), vb); + + if (!gupnp_didl_lite_parser_parse_didl(parser, didl, &upnp_error)) { + error_code = upnp_error->code; + g_error_free(upnp_error); + if (error_code != GUPNP_XML_ERROR_EMPTY_NODE) + goto on_error; + } + + prv_change_props(device->props.player_props, + DLR_INTERFACE_PROP_METADATA, + g_variant_ref_sink(g_variant_builder_end(vb)), + changed_props_vb); + +on_error: + + if (parser) + g_object_unref(parser); + + g_variant_builder_unref(vb); + g_free(didl); +} + +static void prv_last_change_cb(GUPnPServiceProxy *proxy, + const char *variable, + GValue *value, + gpointer user_data) +{ + GUPnPLastChangeParser *parser; + dlr_device_t *device = user_data; + GVariantBuilder *changed_props_vb; + GVariant *changed_props; + gchar *meta_data = NULL; + gchar *actions = NULL; + gchar *play_speed = NULL; + gchar *state = NULL; + gchar *duration = NULL; + gchar *uri = NULL; + guint tracks_number = G_MAXUINT; + guint current_track = G_MAXUINT; + GVariant *val; + + parser = gupnp_last_change_parser_new(); + + if (!gupnp_last_change_parser_parse_last_change( + parser, 0, + g_value_get_string(value), + NULL, + "CurrentTrackMetaData", G_TYPE_STRING, &meta_data, + "CurrentTransportActions", G_TYPE_STRING, &actions, + "TransportPlaySpeed", G_TYPE_STRING, &play_speed, + "TransportState", G_TYPE_STRING, &state, + "CurrentTrackDuration", G_TYPE_STRING, &duration, + "CurrentTrackURI", G_TYPE_STRING, &uri, + "NumberOfTracks", G_TYPE_UINT, &tracks_number, + "CurrentTrack", G_TYPE_UINT, ¤t_track, + NULL)) + goto on_error; + + changed_props_vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); + + if (meta_data) { + prv_add_track_meta_data(device, + meta_data, + duration, + uri, + changed_props_vb); + g_free(meta_data); + } else { + if (duration) { + val = g_variant_new_int64(prv_duration_to_int64( + duration)); + val = g_variant_ref_sink(val); + prv_merge_meta_data(device, + "mpris:length", + val, + changed_props_vb); + g_variant_unref(val); + } + + if (uri) { + val = g_variant_ref_sink(g_variant_new_string(uri)); + prv_merge_meta_data(device, + "xesam:url", + val, + changed_props_vb); + g_variant_unref(val); + } + } + + g_free(duration); + g_free(uri); + + if (actions) { + prv_add_actions(device, actions, changed_props_vb); + g_free(actions); + } + + if (play_speed) { + val = g_variant_ref_sink( + g_variant_new_double( + prv_map_transport_speed(play_speed))); + prv_change_props(device->props.player_props, + DLR_INTERFACE_PROP_RATE, val, + changed_props_vb); + + g_free(device->rate); + device->rate = play_speed; + } + + if (state) { + val = g_variant_ref_sink( + g_variant_new_string( + prv_map_transport_state(state))); + prv_change_props(device->props.player_props, + DLR_INTERFACE_PROP_PLAYBACK_STATUS, val, + changed_props_vb); + g_free(state); + } + + if (tracks_number != G_MAXUINT) { + val = g_variant_ref_sink(g_variant_new_uint32(tracks_number)); + prv_change_props(device->props.player_props, + DLR_INTERFACE_PROP_NUMBER_OF_TRACKS, val, + changed_props_vb); + } + + if (current_track != G_MAXUINT) { + val = g_variant_ref_sink(g_variant_new_uint32(current_track)); + prv_change_props(device->props.player_props, + DLR_INTERFACE_PROP_CURRENT_TRACK, val, + changed_props_vb); + } + + changed_props = g_variant_ref_sink( + g_variant_builder_end(changed_props_vb)); + prv_emit_signal_properties_changed(device, + DLR_INTERFACE_PLAYER, + changed_props); + g_variant_unref(changed_props); + g_variant_builder_unref(changed_props_vb); + +on_error: + + g_object_unref(parser); +} + +static void prv_rc_last_change_cb(GUPnPServiceProxy *proxy, + const char *variable, + GValue *value, + gpointer user_data) +{ + GUPnPLastChangeParser *parser; + dlr_device_t *device = user_data; + GVariantBuilder *changed_props_vb; + GVariant *changed_props; + GVariant *val; + guint device_volume; + double mpris_volume; + + parser = gupnp_last_change_parser_new(); + + if (!gupnp_last_change_parser_parse_last_change( + parser, 0, + g_value_get_string(value), + NULL, + "Volume", G_TYPE_UINT, &device_volume, + NULL)) + goto on_error; + + if (device->props.synced == FALSE) + prv_props_update(device, NULL); + + if (device->max_volume == 0) + goto on_error; + + changed_props_vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); + + mpris_volume = (double)device_volume / (double)device->max_volume; + val = g_variant_ref_sink(g_variant_new_double(mpris_volume)); + prv_change_props(device->props.player_props, + DLR_INTERFACE_PROP_VOLUME, val, + changed_props_vb); + + changed_props = g_variant_ref_sink( + g_variant_builder_end(changed_props_vb)); + prv_emit_signal_properties_changed(device, + DLR_INTERFACE_PLAYER, + changed_props); + g_variant_unref(changed_props); + g_variant_builder_unref(changed_props_vb); + +on_error: + + g_object_unref(parser); +} + +static void prv_sink_change_cb(GUPnPServiceProxy *proxy, + const char *variable, + GValue *value, + gpointer user_data) +{ + dlr_device_t *device = user_data; + const gchar *sink; + + sink = g_value_get_string(value); + + if (sink) + prv_process_protocol_info(device, sink); +} + +static void prv_get_position_info_cb(GUPnPServiceProxy *proxy, + GUPnPServiceProxyAction *action, + gpointer user_data) +{ + gchar *rel_pos = NULL; + dlr_async_task_t *cb_data = user_data; + GError *upnp_error = NULL; + dlr_device_data_t *device_data = cb_data->private; + GVariantBuilder *changed_props_vb; + GVariant *changed_props; + + if (!gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action, + &upnp_error, + "RelTime", + G_TYPE_STRING, &rel_pos, NULL)) { + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OPERATION_FAILED, + "GetPositionInfo operation failed: %s", + upnp_error->message); + g_error_free(upnp_error); + + goto on_error; + } + + changed_props_vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); + + g_strstrip(rel_pos); + prv_add_reltime(cb_data->device, rel_pos, changed_props_vb); + g_free(rel_pos); + + changed_props = g_variant_ref_sink( + g_variant_builder_end(changed_props_vb)); + prv_emit_signal_properties_changed(cb_data->device, + DLR_INTERFACE_PLAYER, + changed_props); + g_variant_unref(changed_props); + g_variant_builder_unref(changed_props_vb); + +on_error: + + device_data->local_cb(cb_data); +} + +static void prv_get_position_info(dlr_async_task_t *cb_data) +{ + dlr_device_context_t *context; + + context = dlr_device_get_context(cb_data->device); + + cb_data->cancel_id = + g_cancellable_connect(cb_data->cancellable, + G_CALLBACK(dlr_async_task_cancelled), + cb_data, NULL); + cb_data->proxy = context->service_proxies.av_proxy; + + g_object_add_weak_pointer((G_OBJECT(context->service_proxies.av_proxy)), + (gpointer *)&cb_data->proxy); + + cb_data->action = + gupnp_service_proxy_begin_action(cb_data->proxy, + "GetPositionInfo", + prv_get_position_info_cb, + cb_data, + "InstanceID", G_TYPE_INT, 0, + NULL); +} + +/***********************************************************************/ +/* Rational numbers parameters of the following functions are formed */ +/* like this : «2» «5/6». A decimal notation like «2.6» is not allowed */ +/***********************************************************************/ +static inline long prv_rational_get_numerator(const char *r) +{ + return strtol(r, NULL, 10); +} + +static long prv_rational_get_denominator(const char *r) +{ + char *div_pos = strstr(r, "/"); + if (div_pos == NULL) + goto exit; + + return strtol(div_pos + 1, NULL, 10); + +exit: + return 1; +} + +static double prv_rational_to_double(const char *r, double precision) +{ + long p; + long q; + double result; + + p = prv_rational_get_numerator(r); + if (p == 0) + goto error; + + q = prv_rational_get_denominator(r); + if (q == 0) + goto error; + + result = (double)p/(double)q; + + if (precision != 0) + result = round(result/precision) * precision; + + return result; + +error: + return 0.0; +} + +static inline gboolean prv_rational_is_invalid(const char *val) +{ + return (prv_rational_get_numerator(val) == 0) || + (prv_rational_get_denominator(val) == 0); +} + +static gint prv_compare_rationals(const gchar *a, const gchar *b) +{ + long a_numerator = prv_rational_get_numerator(a); + long b_numerator = prv_rational_get_numerator(b); + long a_denominator = prv_rational_get_denominator(a); + long b_denominator = prv_rational_get_denominator(b); + + return (a_numerator * b_denominator) - (b_numerator * a_denominator); +} + +static void prv_get_rates_values(const GUPnPServiceStateVariableInfo *svi, + GVariant **mpris_tp_speeds, + GPtrArray **upnp_tp_speeds, + double *min_rate, double *max_rate) +{ + char *rate; + char *min_rate_str; + char *max_rate_str; + GList *list; + GVariantBuilder vb; + const double precision = 0.01; + + if ((svi == NULL) || (svi->allowed_values == NULL)) + goto exit; + + g_variant_builder_init(&vb, G_VARIANT_TYPE("ad")); + + list = svi->allowed_values; + + min_rate_str = list->data; + max_rate_str = min_rate_str; + + if (*upnp_tp_speeds != NULL) + g_ptr_array_free(*upnp_tp_speeds, TRUE); + + *upnp_tp_speeds = g_ptr_array_new_with_free_func(g_free); + + for (; list != NULL; list = list->next) { + rate = (char *)list->data; + + if (prv_rational_is_invalid(rate)) + continue; + + g_ptr_array_add(*upnp_tp_speeds, g_strdup(rate)); + + g_variant_builder_add(&vb, "d", + prv_rational_to_double(rate, precision)); + + if (prv_compare_rationals(min_rate_str, rate) > 0) + min_rate_str = rate; + else if (prv_compare_rationals(max_rate_str, rate) < 0) + max_rate_str = rate; + } + + *mpris_tp_speeds = g_variant_builder_end(&vb); + + *min_rate = prv_rational_to_double(min_rate_str, precision); + *max_rate = prv_rational_to_double(max_rate_str, precision); + +exit: + return; +} + +static void prv_get_av_service_states_values(GUPnPServiceProxy *av_proxy, + GVariant **mpris_tp_speeds, + GPtrArray **upnp_tp_speeds, + double *min_rate, + double *max_rate) +{ + const GUPnPServiceStateVariableInfo *svi; + GUPnPServiceIntrospection *introspection; + GError *error = NULL; + + introspection = gupnp_service_info_get_introspection( + GUPNP_SERVICE_INFO(av_proxy), + &error); + if (error != NULL) { + DLEYNA_LOG_DEBUG( + "failed to fetch AV service introspection file"); + + g_error_free(error); + + goto exit; + } + + svi = gupnp_service_introspection_get_state_variable( + introspection, + "TransportPlaySpeed"); + + prv_get_rates_values(svi, + mpris_tp_speeds, upnp_tp_speeds, + min_rate, max_rate); + + g_object_unref(introspection); + +exit: + + return; +} + +static void prv_get_rc_service_states_values(GUPnPServiceProxy *rc_proxy, + guint *max_volume) +{ + const GUPnPServiceStateVariableInfo *svi; + GUPnPServiceIntrospection *introspection; + GError *error = NULL; + + introspection = gupnp_service_info_get_introspection( + GUPNP_SERVICE_INFO(rc_proxy), + &error); + if (error != NULL) { + DLEYNA_LOG_DEBUG( + "failed to fetch RC service introspection file"); + + g_error_free(error); + + goto exit; + } + + svi = gupnp_service_introspection_get_state_variable(introspection, + "Volume"); + if (svi != NULL) + *max_volume = g_value_get_uint(&svi->maximum); + + g_object_unref(introspection); + +exit: + + return; +} + +static void prv_update_device_props(GUPnPDeviceInfo *proxy, GHashTable *props) +{ + GVariant *val; + gchar *str; + + val = g_variant_ref_sink(g_variant_new_string( + gupnp_device_info_get_device_type(proxy))); + g_hash_table_insert(props, DLR_INTERFACE_PROP_DEVICE_TYPE, val); + + val = g_variant_ref_sink(g_variant_new_string( + gupnp_device_info_get_udn(proxy))); + g_hash_table_insert(props, DLR_INTERFACE_PROP_UDN, val); + + str = gupnp_device_info_get_friendly_name(proxy); + val = g_variant_ref_sink(g_variant_new_string(str)); + g_hash_table_insert(props, DLR_INTERFACE_PROP_FRIENDLY_NAME, val); + g_free(str); + + str = gupnp_device_info_get_icon_url(proxy, NULL, -1, -1, -1, FALSE, + NULL, NULL, NULL, NULL); + val = g_variant_ref_sink(g_variant_new_string(str)); + g_hash_table_insert(props, DLR_INTERFACE_PROP_ICON_URL, val); + g_free(str); + + str = gupnp_device_info_get_manufacturer(proxy); + val = g_variant_ref_sink(g_variant_new_string(str)); + g_hash_table_insert(props, DLR_INTERFACE_PROP_MANUFACTURER, val); + g_free(str); + + str = gupnp_device_info_get_manufacturer_url(proxy); + val = g_variant_ref_sink(g_variant_new_string(str)); + g_hash_table_insert(props, DLR_INTERFACE_PROP_MANUFACTURER_URL, val); + g_free(str); + + str = gupnp_device_info_get_model_description(proxy); + val = g_variant_ref_sink(g_variant_new_string(str)); + g_hash_table_insert(props, DLR_INTERFACE_PROP_MODEL_DESCRIPTION, val); + g_free(str); + + str = gupnp_device_info_get_model_name(proxy); + val = g_variant_ref_sink(g_variant_new_string(str)); + g_hash_table_insert(props, DLR_INTERFACE_PROP_MODEL_NAME, val); + g_free(str); + + str = gupnp_device_info_get_model_number(proxy); + val = g_variant_ref_sink(g_variant_new_string(str)); + g_hash_table_insert(props, DLR_INTERFACE_PROP_MODEL_NUMBER, val); + g_free(str); + + str = gupnp_device_info_get_serial_number(proxy); + val = g_variant_ref_sink(g_variant_new_string(str)); + g_hash_table_insert(props, DLR_INTERFACE_PROP_SERIAL_NUMBER, val); + g_free(str); + + str = gupnp_device_info_get_presentation_url(proxy); + val = g_variant_ref_sink(g_variant_new_string(str)); + g_hash_table_insert(props, DLR_INTERFACE_PROP_PRESENTATION_URL, val); + g_free(str); + +} + +static void prv_props_update(dlr_device_t *device, dlr_task_t *task) +{ + GVariant *val; + GUPnPDeviceInfo *info; + dlr_device_context_t *context; + dlr_service_proxies_t *service_proxies; + dlr_props_t *props = &device->props; + GVariantBuilder *changed_props_vb; + GVariant *changed_props; + double min_rate = 0; + double max_rate = 0; + GVariant *mpris_transport_play_speeds = NULL; + + context = dlr_device_get_context(device); + + val = g_variant_ref_sink(g_variant_new_boolean(FALSE)); + g_hash_table_insert(props->root_props, DLR_INTERFACE_PROP_CAN_QUIT, + val); + + g_hash_table_insert(props->root_props, DLR_INTERFACE_PROP_CAN_RAISE, + g_variant_ref(val)); + + g_hash_table_insert(props->root_props, + DLR_INTERFACE_PROP_CAN_SET_FULLSCREEN, + g_variant_ref(val)); + + g_hash_table_insert(props->root_props, + DLR_INTERFACE_PROP_HAS_TRACK_LIST, + g_variant_ref(val)); + + info = (GUPnPDeviceInfo *)context->device_proxy; + + prv_update_device_props(info, props->device_props); + + val = g_hash_table_lookup(props->device_props, + DLR_INTERFACE_PROP_FRIENDLY_NAME); + g_hash_table_insert(props->root_props, DLR_INTERFACE_PROP_IDENTITY, + g_variant_ref(val)); + + changed_props_vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); + + service_proxies = &context->service_proxies; + + if (service_proxies->av_proxy) + prv_get_av_service_states_values(service_proxies->av_proxy, + &mpris_transport_play_speeds, + &device->transport_play_speeds, + &min_rate, + &max_rate); + + if (service_proxies->rc_proxy) + prv_get_rc_service_states_values(service_proxies->rc_proxy, + &device->max_volume); + + if (min_rate != 0) { + val = g_variant_ref_sink(g_variant_new_double(min_rate)); + prv_change_props(device->props.player_props, + DLR_INTERFACE_PROP_MINIMUM_RATE, val, + changed_props_vb); + } + + if (max_rate != 0) { + val = g_variant_ref_sink(g_variant_new_double(max_rate)); + prv_change_props(device->props.player_props, + DLR_INTERFACE_PROP_MAXIMUM_RATE, + val, changed_props_vb); + } + + if (mpris_transport_play_speeds != NULL) { + val = g_variant_ref_sink(mpris_transport_play_speeds); + prv_change_props(device->props.player_props, + DLR_INTERFACE_PROP_TRANSPORT_PLAY_SPEEDS, + val, changed_props_vb); + } + + prv_add_all_actions(device, changed_props_vb); + device->props.synced = TRUE; + + changed_props = g_variant_ref_sink( + g_variant_builder_end(changed_props_vb)); + prv_emit_signal_properties_changed(device, + DLR_INTERFACE_PLAYER, + changed_props); + g_variant_unref(changed_props); + g_variant_builder_unref(changed_props_vb); +} + +static void prv_complete_get_prop(dlr_async_task_t *cb_data) +{ + prv_get_prop(cb_data); + (void) g_idle_add(dlr_async_task_complete, cb_data); + g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); +} + +static void prv_complete_get_props(dlr_async_task_t *cb_data) +{ + prv_get_props(cb_data); + (void) g_idle_add(dlr_async_task_complete, cb_data); + g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); +} + +static void prv_simple_call_cb(GUPnPServiceProxy *proxy, + GUPnPServiceProxyAction *action, + gpointer user_data) +{ + dlr_async_task_t *cb_data = user_data; + GError *upnp_error = NULL; + + if (!gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action, + &upnp_error, NULL)) { + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OPERATION_FAILED, + "Operation failed: %s", + upnp_error->message); + g_error_free(upnp_error); + } + + (void) g_idle_add(dlr_async_task_complete, cb_data); + g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); +} + +static void prv_set_volume(dlr_async_task_t *cb_data, GVariant *params) +{ + double volume; + + volume = g_variant_get_double(params) * cb_data->device->max_volume; + + DLEYNA_LOG_INFO("Set device volume to %d/%d", (guint)volume, + cb_data->device->max_volume); + + cb_data->action = + gupnp_service_proxy_begin_action(cb_data->proxy, "SetVolume", + prv_simple_call_cb, cb_data, + "InstanceID", G_TYPE_INT, 0, + "Channel", + G_TYPE_STRING, "Master", + "DesiredVolume", + G_TYPE_UINT, (guint) volume, + NULL); +} + +static GVariant *prv_get_rate_value_from_double(GVariant *params, + gchar **upnp_rate, + dlr_async_task_t *cb_data) +{ + GVariant *val = NULL; + GVariant *tps; + GVariantIter iter; + double tps_value; + double mpris_rate; + GPtrArray *upnp_tp_speeds; + int i; + + tps = g_hash_table_lookup(cb_data->device->props.player_props, + DLR_INTERFACE_PROP_TRANSPORT_PLAY_SPEEDS); + + if (tps == NULL) { + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OPERATION_FAILED, + "TransportPlaySpeeds list is empty"); + goto exit; + } + + mpris_rate = g_variant_get_double(params); + + upnp_tp_speeds = cb_data->device->transport_play_speeds; + + i = 0; + + g_variant_iter_init(&iter, tps); + while (g_variant_iter_next(&iter, "d", &tps_value)) { + + if (fabs(mpris_rate - tps_value) <= 0.01) { + val = g_variant_ref_sink( + g_variant_new_double(tps_value)); + + *upnp_rate = g_ptr_array_index(upnp_tp_speeds, i); + + break; + } + + i++; + } + + if (val == NULL) + cb_data->error = + g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_BAD_QUERY, + "Value %.2f not in TransportPlaySpeeds", + mpris_rate); + +exit: + + return val; +} + +static void prv_set_rate(GVariant *params, dlr_async_task_t *cb_data) +{ + GVariant *val; + gchar *rate; + + if (g_variant_is_of_type(params, G_VARIANT_TYPE_DOUBLE) == FALSE) { + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_BAD_QUERY, + "Parameter is not a double"); + goto exit; + } + + rate = NULL; + + val = prv_get_rate_value_from_double(params, &rate, cb_data); + if (val == NULL) + goto exit; + + g_free(cb_data->device->rate); + cb_data->device->rate = g_strdup(rate); + + DLEYNA_LOG_INFO("Set device rate to %s", cb_data->device->rate); + + prv_change_props(cb_data->device->props.player_props, + DLR_INTERFACE_PROP_RATE, val, NULL); + +exit: + + return; +} + +void dlr_device_set_prop(dlr_device_t *device, dlr_task_t *task, + dlr_upnp_task_complete_t cb) +{ + dlr_async_task_t *cb_data = (dlr_async_task_t *)task; + dlr_device_context_t *context; + dlr_task_set_prop_t *set_prop = &task->ut.set_prop; + + cb_data->cb = cb; + cb_data->device = device; + + if (g_strcmp0(set_prop->interface_name, DLR_INTERFACE_PLAYER) != 0 && + g_strcmp0(set_prop->interface_name, "") != 0) { + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_UNKNOWN_INTERFACE, + "Interface %s not managed for property setting", + set_prop->interface_name); + goto exit; + } + + if (g_strcmp0(set_prop->prop_name, DLR_INTERFACE_PROP_RATE) == 0) { + prv_set_rate(set_prop->params, cb_data); + goto exit; + } + + if (g_strcmp0(set_prop->prop_name, DLR_INTERFACE_PROP_VOLUME) != 0) { + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_UNKNOWN_PROPERTY, + "Property %s not managed for setting", + set_prop->prop_name); + goto exit; + } + + context = dlr_device_get_context(device); + + cb_data->cancel_id = + g_cancellable_connect(cb_data->cancellable, + G_CALLBACK(dlr_async_task_cancelled), + cb_data, NULL); + cb_data->proxy = context->service_proxies.rc_proxy; + + g_object_add_weak_pointer((G_OBJECT(context->service_proxies.rc_proxy)), + (gpointer *)&cb_data->proxy); + + prv_set_volume(cb_data, set_prop->params); + return; + +exit: + + g_idle_add(dlr_async_task_complete, cb_data); +} + +void dlr_device_get_prop(dlr_device_t *device, dlr_task_t *task, + dlr_upnp_task_complete_t cb) +{ + dlr_async_task_t *cb_data = (dlr_async_task_t *)task; + dlr_task_get_prop_t *get_prop = &task->ut.get_prop; + dlr_device_data_t *device_cb_data; + + /* Need to check to see if the property is DLR_INTERFACE_PROP_POSITION. + If it is we need to call GetPositionInfo. This value is not evented. + Otherwise we can just update the value straight away. */ + + if ((!strcmp(get_prop->interface_name, DLR_INTERFACE_PLAYER) || + !strcmp(get_prop->interface_name, "")) && + (!strcmp(task->ut.get_prop.prop_name, + DLR_INTERFACE_PROP_POSITION))) { + /* Need to read the current position. This property is not + evented */ + + device_cb_data = g_new(dlr_device_data_t, 1); + device_cb_data->local_cb = prv_complete_get_prop; + + cb_data->cb = cb; + cb_data->private = device_cb_data; + cb_data->free_private = g_free; + cb_data->device = device; + + prv_get_position_info(cb_data); + } else { + cb_data->cb = cb; + cb_data->device = device; + + if (!device->props.synced) + prv_props_update(device, task); + + prv_get_prop(cb_data); + (void) g_idle_add(dlr_async_task_complete, cb_data); + } +} + +void dlr_device_get_all_props(dlr_device_t *device, dlr_task_t *task, + dlr_upnp_task_complete_t cb) +{ + dlr_async_task_t *cb_data = (dlr_async_task_t *)task; + dlr_task_get_props_t *get_props = &task->ut.get_props; + dlr_device_data_t *device_cb_data; + + if (!device->props.synced) + prv_props_update(device, task); + + if ((!strcmp(get_props->interface_name, DLR_INTERFACE_PLAYER) || + !strcmp(get_props->interface_name, ""))) { + + /* Need to read the current position. This property is not + evented */ + + device_cb_data = g_new(dlr_device_data_t, 1); + device_cb_data->local_cb = prv_complete_get_props; + + cb_data->cb = cb; + cb_data->private = device_cb_data; + cb_data->device = device; + cb_data->free_private = g_free; + + prv_get_position_info(cb_data); + } else { + cb_data->cb = cb; + cb_data->device = device; + + prv_get_props(cb_data); + (void) g_idle_add(dlr_async_task_complete, cb_data); + } +} + +void dlr_device_play(dlr_device_t *device, dlr_task_t *task, + dlr_upnp_task_complete_t cb) +{ + dlr_device_context_t *context; + dlr_async_task_t *cb_data = (dlr_async_task_t *)task; + + DLEYNA_LOG_INFO("Play at speed %s", device->rate); + + context = dlr_device_get_context(device); + cb_data->cb = cb; + cb_data->device = device; + + cb_data->cancel_id = + g_cancellable_connect(cb_data->cancellable, + G_CALLBACK(dlr_async_task_cancelled), + cb_data, NULL); + cb_data->proxy = context->service_proxies.av_proxy; + + g_object_add_weak_pointer((G_OBJECT(context->service_proxies.av_proxy)), + (gpointer *)&cb_data->proxy); + + cb_data->action = + gupnp_service_proxy_begin_action(cb_data->proxy, + "Play", + prv_simple_call_cb, + cb_data, + "InstanceID", G_TYPE_INT, 0, + "Speed", G_TYPE_STRING, + device->rate, NULL); +} + +void dlr_device_play_pause(dlr_device_t *device, dlr_task_t *task, + dlr_upnp_task_complete_t cb) +{ + GVariant *state; + + state = g_hash_table_lookup(device->props.player_props, + DLR_INTERFACE_PROP_PLAYBACK_STATUS); + + if (state && !strcmp(g_variant_get_string(state, NULL), "Playing")) + dlr_device_pause(device, task, cb); + else + dlr_device_play(device, task, cb); +} + +static void prv_simple_command(dlr_device_t *device, dlr_task_t *task, + const gchar *command_name, + dlr_upnp_task_complete_t cb) +{ + dlr_device_context_t *context; + dlr_async_task_t *cb_data = (dlr_async_task_t *)task; + + DLEYNA_LOG_INFO("%s", command_name); + + context = dlr_device_get_context(device); + cb_data->cb = cb; + cb_data->device = device; + + cb_data->cancel_id = + g_cancellable_connect(cb_data->cancellable, + G_CALLBACK(dlr_async_task_cancelled), + cb_data, NULL); + cb_data->proxy = context->service_proxies.av_proxy; + + g_object_add_weak_pointer((G_OBJECT(context->service_proxies.av_proxy)), + (gpointer *)&cb_data->proxy); + + cb_data->action = + gupnp_service_proxy_begin_action(cb_data->proxy, + command_name, + prv_simple_call_cb, + cb_data, + "InstanceID", G_TYPE_INT, 0, + NULL); +} + +void dlr_device_pause(dlr_device_t *device, dlr_task_t *task, + dlr_upnp_task_complete_t cb) +{ + prv_simple_command(device, task, "Pause", cb); +} + +void dlr_device_stop(dlr_device_t *device, dlr_task_t *task, + dlr_upnp_task_complete_t cb) +{ + prv_simple_command(device, task, "Stop", cb); +} + +void dlr_device_next(dlr_device_t *device, dlr_task_t *task, + dlr_upnp_task_complete_t cb) +{ + prv_simple_command(device, task, "Next", cb); +} + +void dlr_device_previous(dlr_device_t *device, dlr_task_t *task, + dlr_upnp_task_complete_t cb) +{ + prv_simple_command(device, task, "Previous", cb); +} + +void dlr_device_open_uri(dlr_device_t *device, dlr_task_t *task, + dlr_upnp_task_complete_t cb) +{ + dlr_device_context_t *context; + dlr_async_task_t *cb_data = (dlr_async_task_t *)task; + dlr_task_open_uri_t *open_uri_data = &task->ut.open_uri; + + DLEYNA_LOG_INFO("URI: %s", open_uri_data->uri); + + context = dlr_device_get_context(device); + cb_data->cb = cb; + cb_data->device = device; + + cb_data->cancel_id = + g_cancellable_connect(cb_data->cancellable, + G_CALLBACK(dlr_async_task_cancelled), + cb_data, NULL); + cb_data->proxy = context->service_proxies.av_proxy; + + g_object_add_weak_pointer((G_OBJECT(context->service_proxies.av_proxy)), + (gpointer *)&cb_data->proxy); + + cb_data->action = + gupnp_service_proxy_begin_action(cb_data->proxy, + "SetAVTransportURI", + prv_simple_call_cb, + cb_data, + "InstanceID", G_TYPE_INT, 0, + "CurrentURI", G_TYPE_STRING, + open_uri_data->uri, + "CurrentURIMetaData", + G_TYPE_STRING, "", + NULL); +} + +static void prv_device_set_position(dlr_device_t *device, dlr_task_t *task, + const gchar *pos_type, + dlr_upnp_task_complete_t cb) +{ + dlr_device_context_t *context; + dlr_async_task_t *cb_data = (dlr_async_task_t *)task; + dlr_task_seek_t *seek_data = &task->ut.seek; + gchar *position; + + context = dlr_device_get_context(device); + cb_data->cb = cb; + cb_data->device = device; + + if (!strcmp(pos_type, "TRACK_NR")) + position = g_strdup_printf("%u", seek_data->track_number); + else + position = prv_int64_to_duration(seek_data->position); + + DLEYNA_LOG_INFO("set %s position : %s", pos_type, position); + + cb_data->cancel_id = + g_cancellable_connect(cb_data->cancellable, + G_CALLBACK(dlr_async_task_cancelled), + cb_data, NULL); + cb_data->cancellable = cb_data->cancellable; + cb_data->proxy = context->service_proxies.av_proxy; + + g_object_add_weak_pointer((G_OBJECT(context->service_proxies.av_proxy)), + (gpointer *)&cb_data->proxy); + + cb_data->action = + gupnp_service_proxy_begin_action(cb_data->proxy, + "Seek", + prv_simple_call_cb, + cb_data, + "InstanceID", G_TYPE_INT, 0, + "Unit", G_TYPE_STRING, + pos_type, + "Target", + G_TYPE_STRING, position, + NULL); + + g_free(position); +} + +void dlr_device_seek(dlr_device_t *device, dlr_task_t *task, + dlr_upnp_task_complete_t cb) +{ + prv_device_set_position(device, task, "REL_TIME", cb); +} + +void dlr_device_set_position(dlr_device_t *device, dlr_task_t *task, + dlr_upnp_task_complete_t cb) +{ + prv_device_set_position(device, task, "ABS_TIME", cb); +} + +void dlr_device_goto_track(dlr_device_t *device, dlr_task_t *task, + dlr_upnp_task_complete_t cb) +{ + prv_device_set_position(device, task, "TRACK_NR", cb); +} + +void dlr_device_host_uri(dlr_device_t *device, dlr_task_t *task, + dlr_host_service_t *host_service, + dlr_upnp_task_complete_t cb) +{ + dlr_device_context_t *context; + dlr_async_task_t *cb_data = (dlr_async_task_t *)task; + dlr_task_host_uri_t *host_uri = &task->ut.host_uri; + gchar *url; + GError *error = NULL; + + context = dlr_device_get_context(device); + url = dlr_host_service_add(host_service, context->ip_address, + host_uri->client, host_uri->uri, + &error); + + cb_data->cb = cb; + cb_data->device = device; + if (url) { + cb_data->task.result = g_variant_ref_sink( + g_variant_new_string(url)); + g_free(url); + } else { + cb_data->error = error; + } + + (void) g_idle_add(dlr_async_task_complete, cb_data); +} + +void dlr_device_remove_uri(dlr_device_t *device, dlr_task_t *task, + dlr_host_service_t *host_service, + dlr_upnp_task_complete_t cb) +{ + dlr_device_context_t *context; + dlr_async_task_t *cb_data = (dlr_async_task_t *)task; + dlr_task_host_uri_t *host_uri = &task->ut.host_uri; + + context = dlr_device_get_context(device); + cb_data->cb = cb; + cb_data->device = device; + + if (!dlr_host_service_remove(host_service, context->ip_address, + host_uri->client, host_uri->uri)) { + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OBJECT_NOT_FOUND, + "File not hosted for specified device"); + } + + (void) g_idle_add(dlr_async_task_complete, cb_data); +} diff --git a/libdleyna/renderer/device.h b/libdleyna/renderer/device.h new file mode 100644 index 0000000..a0c1bf0 --- /dev/null +++ b/libdleyna/renderer/device.h @@ -0,0 +1,149 @@ +/* + * dLeyna + * + * Copyright (C) 2012-2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Mark Ryan <mark.d.ryan@intel.com> + * + */ + +#ifndef DLR_DEVICE_H__ +#define DLR_DEVICE_H__ + +#include <gio/gio.h> +#include <glib.h> + +#include <libgupnp/gupnp-service-proxy.h> +#include <libgupnp/gupnp-device-proxy.h> + +#include <libdleyna/core/connector.h> + +#include "host-service.h" +#include "server.h" +#include "upnp.h" + +typedef struct dlr_service_proxies_t_ dlr_service_proxies_t; +struct dlr_service_proxies_t_ { + GUPnPServiceProxy *cm_proxy; + GUPnPServiceProxy *av_proxy; + GUPnPServiceProxy *rc_proxy; +}; + +typedef struct dlr_device_context_t_ dlr_device_context_t; +struct dlr_device_context_t_ { + gchar *ip_address; + GUPnPDeviceProxy *device_proxy; + dlr_service_proxies_t service_proxies; + dlr_device_t *device; + gboolean subscribed_av; + gboolean subscribed_cm; + gboolean subscribed_rc; + guint timeout_id_av; + guint timeout_id_cm; + guint timeout_id_rc; +}; + +typedef struct dlr_props_t_ dlr_props_t; +struct dlr_props_t_ { + GHashTable *root_props; + GHashTable *player_props; + GHashTable *device_props; + gboolean synced; +}; + +struct dlr_device_t_ { + dleyna_connector_id_t connection; + guint ids[DLR_INTERFACE_INFO_MAX]; + gchar *path; + GPtrArray *contexts; + dlr_props_t props; + guint timeout_id; + guint max_volume; + GPtrArray *transport_play_speeds; + gchar *rate; +}; + +dlr_device_t *dlr_device_new( + dleyna_connector_id_t connection, + GUPnPDeviceProxy *proxy, + const gchar *ip_address, + guint counter, + const dleyna_connector_dispatch_cb_t *dispatch_table, + const dleyna_task_queue_key_t *queue_id); + +void dlr_device_delete(void *device); + +void dlr_device_unsubscribe(void *device); + +void dlr_device_append_new_context(dlr_device_t *device, + const gchar *ip_address, + GUPnPDeviceProxy *proxy); + +dlr_device_t *dlr_device_from_path(const gchar *path, GHashTable *device_list); + +dlr_device_context_t *dlr_device_get_context(dlr_device_t *device); + +void dlr_device_subscribe_to_service_changes(dlr_device_t *device); + + +void dlr_device_set_prop(dlr_device_t *device, dlr_task_t *task, + dlr_upnp_task_complete_t cb); + +void dlr_device_get_prop(dlr_device_t *device, dlr_task_t *task, + dlr_upnp_task_complete_t cb); + +void dlr_device_get_all_props(dlr_device_t *device, dlr_task_t *task, + dlr_upnp_task_complete_t cb); + +void dlr_device_play(dlr_device_t *device, dlr_task_t *task, + dlr_upnp_task_complete_t cb); + +void dlr_device_pause(dlr_device_t *device, dlr_task_t *task, + dlr_upnp_task_complete_t cb); + +void dlr_device_play_pause(dlr_device_t *device, dlr_task_t *task, + dlr_upnp_task_complete_t cb); + +void dlr_device_stop(dlr_device_t *device, dlr_task_t *task, + dlr_upnp_task_complete_t cb); + +void dlr_device_next(dlr_device_t *device, dlr_task_t *task, + dlr_upnp_task_complete_t cb); + +void dlr_device_previous(dlr_device_t *device, dlr_task_t *task, + dlr_upnp_task_complete_t cb); + +void dlr_device_open_uri(dlr_device_t *device, dlr_task_t *task, + dlr_upnp_task_complete_t cb); + +void dlr_device_seek(dlr_device_t *device, dlr_task_t *task, + dlr_upnp_task_complete_t cb); + +void dlr_device_set_position(dlr_device_t *device, dlr_task_t *task, + dlr_upnp_task_complete_t cb); + +void dlr_device_goto_track(dlr_device_t *device, dlr_task_t *task, + dlr_upnp_task_complete_t cb); + +void dlr_device_host_uri(dlr_device_t *device, dlr_task_t *task, + dlr_host_service_t *host_service, + dlr_upnp_task_complete_t cb); + +void dlr_device_remove_uri(dlr_device_t *device, dlr_task_t *task, + dlr_host_service_t *host_service, + dlr_upnp_task_complete_t cb); + +#endif /* DLR_DEVICE_H__ */ diff --git a/libdleyna/renderer/dleyna-renderer-1.0.pc.in b/libdleyna/renderer/dleyna-renderer-1.0.pc.in new file mode 100644 index 0000000..3e6857a --- /dev/null +++ b/libdleyna/renderer/dleyna-renderer-1.0.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libexecdir=@libexecdir@ +includedir=${prefix}/include +libdir=@libdir@ + +Name: @PACKAGE@ +Description: UPnP & DLNA renderer library +Libs: -L${libdir} -ldleyna-renderer-1.0 +Requires.private: glib-2.0 gio-2.0 libsoup-2.4 gupnp-1.0 gupnp-av-1.0 dleyna-core-1.0 +Version: @VERSION@
\ No newline at end of file diff --git a/libdleyna/renderer/dleyna-renderer-service.conf.in b/libdleyna/renderer/dleyna-renderer-service.conf.in new file mode 100644 index 0000000..2edc4e3 --- /dev/null +++ b/libdleyna/renderer/dleyna-renderer-service.conf.in @@ -0,0 +1,37 @@ +# Configuration file for dleyna-renderer +# +# +# +# General configuration options +[general] + +# true: Service always stay in memory running +# false: Service quit when the last client disconnects. +never-quit=@never_quit@ + +# IPC connector name +connector-name=@with_connector_name@ + +# Log configuration options +[log] + +# Define the logging output method. 3 technologies are defined: +# +# 0=Syslog +# 1=GLib +# 2=File +log-type=@with_log_type@ + +# Comma-separated list of logging level. +# Log levels are: 1=critical, 2=error, 3=warning, 4=message, 5=info, 6=debug +# +# Allowed values for log-levels are +# 0 = disabled +# 7 = default (=1,2,5) +# 8 = all (=1,2,3,4,5,6) +# 1,..,6 = a comma separated list of log level +# +# IMPORTANT: This log level is a subset of the log level defined at compile time +# You can't enable levels disabled at compile time +# level=8 means all level flags defined at compile time. +log-level=@with_log_level@ diff --git a/libdleyna/renderer/host-service.c b/libdleyna/renderer/host-service.c new file mode 100644 index 0000000..42ee74e --- /dev/null +++ b/libdleyna/renderer/host-service.c @@ -0,0 +1,567 @@ +/* + * dLeyna + * + * Copyright (C) 2012-2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Mark Ryan <mark.d.ryan@intel.com> + * + */ + + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <libsoup/soup.h> +#include <glib.h> + +#include <libgupnp-av/gupnp-dlna.h> +#include <libgupnp-dlna/gupnp-dlna-profile.h> +#include <libgupnp-dlna/gupnp-dlna-profile-guesser.h> + +#include <libdleyna/core/error.h> +#include <libdleyna/core/log.h> + +#include "host-service.h" + +#define DLR_HOST_SERVICE_ROOT "/dleynarenderer" + +typedef struct dlr_host_file_t_ dlr_host_file_t; +struct dlr_host_file_t_ { + unsigned int id; + GPtrArray *clients; + gchar *mime_type; + GMappedFile *mapped_file; + unsigned int mapped_count; + gchar *path; + gchar *dlna_header; +}; + +typedef struct dlr_host_server_t_ dlr_host_server_t; +struct dlr_host_server_t_ { + GHashTable *files; + SoupServer *soup_server; + unsigned int counter; +}; + +struct dlr_host_service_t_ { + GHashTable *servers; +}; + +static gchar *prv_compute_dlna_header(const gchar *filename) +{ + gchar *uri; + GString *header; + GError *error = NULL; + GUPnPDLNAProfile *profile; + GUPnPDLNAProfileGuesser *guesser; + gboolean relaxed_mode = TRUE; + gboolean extended_mode = TRUE; + const char *profile_name; + const char *mime_type; + GUPnPDLNAOperation operation; + GUPnPDLNAFlags flags; + GUPnPDLNAConversion conversion; + + header = g_string_new(""); + + guesser = gupnp_dlna_profile_guesser_new(relaxed_mode, extended_mode); + + uri = g_filename_to_uri(filename, NULL, &error); + if (uri == NULL) { + DLEYNA_LOG_WARNING("Unable to convert filename: %s", filename); + + if (error) { + DLEYNA_LOG_WARNING("Error: %s", error->message); + + g_error_free(error); + } + + goto on_error; + } + + profile = gupnp_dlna_profile_guesser_guess_profile_sync(guesser, + uri, + 5000, + NULL, + &error); + if (profile == NULL) { + DLEYNA_LOG_WARNING("Unable to guess profile for URI: %s", uri); + + if (error) { + DLEYNA_LOG_WARNING("Error: %s", error->message); + + g_error_free(error); + } + + goto on_error; + } + + profile_name = gupnp_dlna_profile_get_name(profile); + if (profile_name != NULL) + g_string_append_printf(header, "DLNA.ORG_PN=%s;", profile_name); + + operation = GUPNP_DLNA_OPERATION_RANGE; + g_string_append_printf(header, "DLNA.ORG_OP=%.2x;", operation); + + conversion = GUPNP_DLNA_CONVERSION_NONE; + g_string_append_printf(header, "DLNA.ORG_CI=%.2x;", conversion); + + mime_type = gupnp_dlna_profile_get_mime(profile); + if (mime_type != NULL) { + flags = GUPNP_DLNA_FLAGS_BACKGROUND_TRANSFER_MODE; + flags |= GUPNP_DLNA_FLAGS_CONNECTION_STALL; + flags |= GUPNP_DLNA_FLAGS_DLNA_V15; + + if (g_content_type_is_a(mime_type, "image/*")) { + flags |= GUPNP_DLNA_FLAGS_INTERACTIVE_TRANSFER_MODE; + } else if (g_content_type_is_a(mime_type, "audio/*") || + g_content_type_is_a(mime_type, "video/*")) { + flags |= GUPNP_DLNA_FLAGS_STREAMING_TRANSFER_MODE; + } else { + DLEYNA_LOG_WARNING("Unsupported Mime Type: %s", + mime_type); + + goto on_error; + } + + g_string_append_printf(header, "DLNA.ORG_FLAGS=%.8x", flags); + g_string_append_printf(header, "000000000000000000000000"); + } else { + DLEYNA_LOG_WARNING("Unable to discover mime_type"); + } + +on_error: + + DLEYNA_LOG_DEBUG("contentFeatures.dlna.org: %s", header->str); + + g_object_unref(guesser); + + g_free(uri); + + return g_string_free(header, FALSE); +} + +static void prv_host_file_delete(gpointer host_file) +{ + dlr_host_file_t *hf = host_file; + unsigned int i; + + if (hf) { + g_free(hf->path); + for (i = 0; i < hf->mapped_count; ++i) + g_mapped_file_unref(hf->mapped_file); + + g_ptr_array_unref(hf->clients); + + g_free(hf->mime_type); + g_free(hf->dlna_header); + g_free(hf); + } +} + +static dlr_host_file_t *prv_host_file_new(const gchar *file, unsigned int id, + GError **error) +{ + dlr_host_file_t *hf = NULL; + gchar *extension; + gchar *content_type = NULL; + + if (!g_file_test(file, G_FILE_TEST_IS_REGULAR | G_FILE_TEST_EXISTS)) { + *error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OBJECT_NOT_FOUND, + "File %s does not exist or is not a regular file", + file); + goto on_error; + } + + hf = g_new0(dlr_host_file_t, 1); + hf->id = id; + hf->clients = g_ptr_array_new_with_free_func(g_free); + + content_type = g_content_type_guess(file, NULL, 0, NULL); + + if (!content_type) { + *error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_BAD_MIME, + "Unable to determine Content Type for %s", + file); + goto on_error; + } + + hf->mime_type = g_content_type_get_mime_type(content_type); + + if (!hf->mime_type) { + *error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_BAD_MIME, + "Unable to determine MIME Type for %s", + file); + goto on_error; + } + + g_free(content_type); + + extension = strrchr(file, '.'); + hf->path = g_strdup_printf(DLR_HOST_SERVICE_ROOT"/%d%s", + hf->id, extension ? extension : ""); + + hf->dlna_header = prv_compute_dlna_header(file); + + return hf; + +on_error: + + g_free(content_type); + prv_host_file_delete(hf); + + return NULL; +} + +static void prv_host_server_delete(gpointer host_server) +{ + dlr_host_server_t *server = host_server; + + if (server) { + soup_server_quit(server->soup_server); + g_object_unref(server->soup_server); + g_hash_table_unref(server->files); + g_free(server); + } +} + +static dlr_host_file_t *prv_host_server_find_file(dlr_host_server_t *hs, + const gchar *url, + const gchar **file_name) +{ + dlr_host_file_t *retval = NULL; + GHashTableIter iter; + gpointer key; + gpointer value; + + g_hash_table_iter_init(&iter, hs->files); + + while (g_hash_table_iter_next(&iter, &key, &value)) { + if (!strcmp(((dlr_host_file_t *)value)->path, url)) { + retval = value; + *file_name = key; + break; + } + } + + return retval; +} + +static void prv_soup_message_finished_cb(SoupMessage *msg, gpointer user_data) +{ + dlr_host_file_t *hf = user_data; + + if (hf->mapped_count > 0) { + g_mapped_file_unref(hf->mapped_file); + --hf->mapped_count; + + if (hf->mapped_count == 0) + hf->mapped_file = NULL; + } +} + +static void prv_soup_server_cb(SoupServer *server, SoupMessage *msg, + const char *path, GHashTable *query, + SoupClientContext *client, gpointer user_data) +{ + dlr_host_file_t *hf; + dlr_host_server_t *hs = user_data; + const gchar *file_name; + const char *hdr; + + if ((msg->method != SOUP_METHOD_GET) && + (msg->method != SOUP_METHOD_HEAD)) { + soup_message_set_status(msg, SOUP_STATUS_NOT_IMPLEMENTED); + goto on_error; + } + + hf = prv_host_server_find_file(hs, path, &file_name); + + if (!hf) { + soup_message_set_status(msg, SOUP_STATUS_NOT_FOUND); + goto on_error; + } + + hdr = soup_message_headers_get_one(msg->request_headers, + "getContentFeatures.dlna.org"); + + if (hdr) { + if (strcmp(hdr, "1") != 0) { + soup_message_set_status(msg, SOUP_STATUS_BAD_REQUEST); + goto on_error; + } + + if ((hf->dlna_header) && strlen(hf->dlna_header) > 0) + soup_message_headers_append(msg->response_headers, + "contentFeatures.dlna.org", + hf->dlna_header); + } + + if (hf->mapped_file) { + g_mapped_file_ref(hf->mapped_file); + ++hf->mapped_count; + } else { + hf->mapped_file = g_mapped_file_new(file_name, + FALSE, + NULL); + + if (!hf->mapped_file) { + soup_message_set_status(msg, + SOUP_STATUS_NOT_FOUND); + goto on_error; + } + + hf->mapped_count = 1; + } + + if (msg->method == SOUP_METHOD_GET) { + g_signal_connect(msg, "finished", + G_CALLBACK(prv_soup_message_finished_cb), hf); + + soup_message_set_response( + msg, hf->mime_type, + SOUP_MEMORY_STATIC, + g_mapped_file_get_contents(hf->mapped_file), + g_mapped_file_get_length(hf->mapped_file)); + } else { + soup_message_headers_set_content_type(msg->response_headers, + hf->mime_type, NULL); + + soup_message_headers_set_content_length( + msg->response_headers, + g_mapped_file_get_length(hf->mapped_file)); + } + + soup_message_set_status(msg, SOUP_STATUS_OK); + +on_error: + + return; +} + +static dlr_host_server_t *prv_host_server_new(const gchar *device_if, + GError **error) +{ + dlr_host_server_t *server = NULL; + SoupAddress *addr; + + addr = soup_address_new(device_if, SOUP_ADDRESS_ANY_PORT); + + if (soup_address_resolve_sync(addr, NULL) != SOUP_STATUS_OK) { + *error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_HOST_FAILED, + "Unable to create host server on %s", + device_if); + goto on_error; + } + + server = g_new(dlr_host_server_t, 1); + server->files = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, prv_host_file_delete); + + server->soup_server = soup_server_new(SOUP_SERVER_INTERFACE, addr, + NULL); + soup_server_add_handler(server->soup_server, DLR_HOST_SERVICE_ROOT, + prv_soup_server_cb, server, NULL); + soup_server_run_async(server->soup_server); + server->counter = 0; + +on_error: + + g_object_unref(addr); + + return server; +} + +void dlr_host_service_new(dlr_host_service_t **host_service) +{ + dlr_host_service_t *hs; + + hs = g_new(dlr_host_service_t, 1); + hs->servers = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, prv_host_server_delete); + + *host_service = hs; +} + +static gchar *prv_add_new_file(dlr_host_server_t *server, const gchar *client, + const gchar *device_if, const gchar *file, + GError **error) +{ + unsigned int i; + dlr_host_file_t *hf; + gchar *str; + + hf = g_hash_table_lookup(server->files, file); + + if (!hf) { + hf = prv_host_file_new(file, server->counter++, error); + + if (!hf) + goto on_error; + + g_ptr_array_add(hf->clients, g_strdup(client)); + g_hash_table_insert(server->files, g_strdup(file), hf); + } else { + for (i = 0; i < hf->clients->len; ++i) + if (!strcmp(g_ptr_array_index(hf->clients, i), client)) + break; + + if (i == hf->clients->len) + g_ptr_array_add(hf->clients, g_strdup(client)); + } + + str = g_strdup_printf("http://%s:%d%s", device_if, + soup_server_get_port(server->soup_server), + hf->path); + + return str; + +on_error: + + return NULL; +} + +gchar *dlr_host_service_add(dlr_host_service_t *host_service, + const gchar *device_if, const gchar *client, + const gchar *file, GError **error) +{ + dlr_host_server_t *server; + gchar *retval = NULL; + + server = g_hash_table_lookup(host_service->servers, device_if); + + if (!server) { + server = prv_host_server_new(device_if, error); + + if (!server) + goto on_error; + + g_hash_table_insert(host_service->servers, g_strdup(device_if), + server); + } + + retval = prv_add_new_file(server, client, device_if, file, error); + +on_error: + + return retval; +} + +static gboolean prv_remove_client(dlr_host_service_t *host_service, + const gchar *client, + dlr_host_server_t *server, + const gchar *device_if, + const gchar *file, + dlr_host_file_t *hf) +{ + unsigned int i; + gboolean retval = FALSE; + + for (i = 0; i < hf->clients->len; ++i) + if (!strcmp(g_ptr_array_index(hf->clients, i), client)) + break; + + if (i == hf->clients->len) + goto on_error; + + g_ptr_array_remove_index(hf->clients, i); + + retval = TRUE; + +on_error: + + return retval; +} + +gboolean dlr_host_service_remove(dlr_host_service_t *host_service, + const gchar *device_if, const gchar *client, + const gchar *file) +{ + gboolean retval = FALSE; + dlr_host_file_t *hf; + dlr_host_server_t *server; + + server = g_hash_table_lookup(host_service->servers, device_if); + + if (!server) + goto on_error; + + hf = g_hash_table_lookup(server->files, file); + + if (!hf) + goto on_error; + + retval = prv_remove_client(host_service, client, server, + device_if, file, hf); + if (!retval) + goto on_error; + + if (hf->clients->len == 0) + g_hash_table_remove(server->files, file); + + if (g_hash_table_size(server->files) == 0) + g_hash_table_remove(host_service->servers, device_if); + +on_error: + + return retval; +} + +void dlr_host_service_lost_client(dlr_host_service_t *host_service, + const gchar *client) +{ + GHashTableIter iter; + GHashTableIter iter2; + gpointer value; + gpointer key; + gpointer value2; + gpointer key2; + dlr_host_server_t *server; + dlr_host_file_t *hf; + + g_hash_table_iter_init(&iter, host_service->servers); + + while (g_hash_table_iter_next(&iter, &key, &value)) { + server = value; + g_hash_table_iter_init(&iter2, server->files); + + while (g_hash_table_iter_next(&iter2, &key2, &value2)) { + hf = value2; + + if (!prv_remove_client(host_service, client, server, + key, key2, hf)) + continue; + + if (hf->clients->len > 0) + continue; + + g_hash_table_iter_remove(&iter2); + } + + if (g_hash_table_size(server->files) == 0) + g_hash_table_iter_remove(&iter); + } +} + +void dlr_host_service_delete(dlr_host_service_t *host_service) +{ + if (host_service) { + g_hash_table_unref(host_service->servers); + g_free(host_service); + } +} diff --git a/libdleyna/renderer/host-service.h b/libdleyna/renderer/host-service.h new file mode 100644 index 0000000..a05a529 --- /dev/null +++ b/libdleyna/renderer/host-service.h @@ -0,0 +1,43 @@ +/* + * dLeyna + * + * Copyright (C) 2012-2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Mark Ryan <mark.d.ryan@intel.com> + * + */ + +#ifndef DLR_HOST_SERVICE_H__ +#define DLR_HOST_SERVICE_H__ + +typedef struct dlr_host_service_t_ dlr_host_service_t; + +void dlr_host_service_new(dlr_host_service_t **host_service); + +gchar *dlr_host_service_add(dlr_host_service_t *host_service, + const gchar *device_if, const gchar *client, + const gchar *file, GError **error); + +gboolean dlr_host_service_remove(dlr_host_service_t *host_service, + const gchar *device_if, const gchar *client, + const gchar *file); + +void dlr_host_service_lost_client(dlr_host_service_t *host_service, + const gchar *client); + +void dlr_host_service_delete(dlr_host_service_t *host_service); + +#endif /*DLR_HOST_SERVICE_H__ */ diff --git a/libdleyna/renderer/prop-defs.h b/libdleyna/renderer/prop-defs.h new file mode 100644 index 0000000..ae2f313 --- /dev/null +++ b/libdleyna/renderer/prop-defs.h @@ -0,0 +1,70 @@ +/* + * dLeyna + * + * Copyright (C) 2012-2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Mark Ryan <mark.d.ryan@intel.com> + * + */ + +#ifndef DLR_PROPS_DEFS_H__ +#define DLR_PROPS_DEFS_H__ + +#define DLR_INTERFACE_PROPERTIES "org.freedesktop.DBus.Properties" +#define DLR_INTERFACE_SERVER "org.mpris.MediaPlayer2" +#define DLR_INTERFACE_PLAYER "org.mpris.MediaPlayer2.Player" + +#define DLR_INTERFACE_PROPERTIES_CHANGED "PropertiesChanged" + +#define DLR_INTERFACE_PROP_CAN_QUIT "CanQuit" +#define DLR_INTERFACE_PROP_CAN_RAISE "CanRaise" +#define DLR_INTERFACE_PROP_CAN_SET_FULLSCREEN "CanSetFullscreen" +#define DLR_INTERFACE_PROP_HAS_TRACK_LIST "HasTrackList" +#define DLR_INTERFACE_PROP_IDENTITY "Identity" +#define DLR_INTERFACE_PROP_SUPPORTED_URIS "SupportedUriSchemes" +#define DLR_INTERFACE_PROP_SUPPORTED_MIME "SupportedMimeTypes" + +#define DLR_INTERFACE_PROP_PLAYBACK_STATUS "PlaybackStatus" +#define DLR_INTERFACE_PROP_RATE "Rate" +#define DLR_INTERFACE_PROP_CAN_PLAY "CanPlay" +#define DLR_INTERFACE_PROP_CAN_SEEK "CanSeek" +#define DLR_INTERFACE_PROP_CAN_CONTROL "CanControl" +#define DLR_INTERFACE_PROP_CAN_PAUSE "CanPause" +#define DLR_INTERFACE_PROP_CAN_NEXT "CanGoNext" +#define DLR_INTERFACE_PROP_CAN_PREVIOUS "CanGoPrevious" +#define DLR_INTERFACE_PROP_POSITION "Position" +#define DLR_INTERFACE_PROP_METADATA "Metadata" +#define DLR_INTERFACE_PROP_TRANSPORT_PLAY_SPEEDS "TransportPlaySpeeds" +#define DLR_INTERFACE_PROP_MINIMUM_RATE "MinimumRate" +#define DLR_INTERFACE_PROP_MAXIMUM_RATE "MaximumRate" +#define DLR_INTERFACE_PROP_VOLUME "Volume" +#define DLR_INTERFACE_PROP_CURRENT_TRACK "CurrentTrack" +#define DLR_INTERFACE_PROP_NUMBER_OF_TRACKS "NumberOfTracks" + +#define DLR_INTERFACE_PROP_DEVICE_TYPE "DeviceType" +#define DLR_INTERFACE_PROP_UDN "UDN" +#define DLR_INTERFACE_PROP_FRIENDLY_NAME "FriendlyName" +#define DLR_INTERFACE_PROP_ICON_URL "IconURL" +#define DLR_INTERFACE_PROP_MANUFACTURER "Manufacturer" +#define DLR_INTERFACE_PROP_MANUFACTURER_URL "ManufacturerUrl" +#define DLR_INTERFACE_PROP_MODEL_DESCRIPTION "ModelDescription" +#define DLR_INTERFACE_PROP_MODEL_NAME "ModelName" +#define DLR_INTERFACE_PROP_MODEL_NUMBER "ModelNumber" +#define DLR_INTERFACE_PROP_SERIAL_NUMBER "SerialNumber" +#define DLR_INTERFACE_PROP_PRESENTATION_URL "PresentationURL" +#define DLR_INTERFACE_PROP_PROTOCOL_INFO "ProtocolInfo" + +#endif /* DLR_PROPS_DEFS_H__ */ diff --git a/libdleyna/renderer/server.c b/libdleyna/renderer/server.c new file mode 100644 index 0000000..75bb358 --- /dev/null +++ b/libdleyna/renderer/server.c @@ -0,0 +1,917 @@ +/* + * dLeyna + * + * Copyright (C) 2012-2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Mark Ryan <mark.d.ryan@intel.com> + * + */ + + +#include <signal.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <sys/signalfd.h> + +#include <libdleyna/core/connector.h> +#include <libdleyna/core/control-point.h> +#include <libdleyna/core/error.h> +#include <libdleyna/core/log.h> +#include <libdleyna/core/task-processor.h> + +#include "async.h" +#include "control-point-renderer.h" +#include "device.h" +#include "prop-defs.h" +#include "server.h" +#include "upnp.h" + +#ifdef UA_PREFIX + #define DLR_PRG_NAME UA_PREFIX " dLeyna/" VERSION +#else + #define DLR_PRG_NAME "dLeyna/" VERSION +#endif + +#define DLR_INTERFACE_GET_VERSION "GetVersion" +#define DLR_INTERFACE_GET_SERVERS "GetServers" +#define DLR_INTERFACE_RELEASE "Release" + +#define DLR_INTERFACE_FOUND_SERVER "FoundServer" +#define DLR_INTERFACE_LOST_SERVER "LostServer" + +#define DLR_INTERFACE_HOST_FILE "HostFile" +#define DLR_INTERFACE_REMOVE_FILE "RemoveFile" + +#define DLR_INTERFACE_VERSION "Version" +#define DLR_INTERFACE_SERVERS "Servers" + +#define DLR_INTERFACE_PATH "Path" +#define DLR_INTERFACE_URI "Uri" +#define DLR_INTERFACE_ID "Id" + +#define DLR_INTERFACE_CHANGED_PROPERTIES "changed_properties" +#define DLR_INTERFACE_INVALIDATED_PROPERTIES "invalidated_properties" +#define DLR_INTERFACE_GET "Get" +#define DLR_INTERFACE_GET_ALL "GetAll" +#define DLR_INTERFACE_SET "Set" +#define DLR_INTERFACE_INTERFACE_NAME "interface_name" +#define DLR_INTERFACE_PROPERTY_NAME "property_name" +#define DLR_INTERFACE_PROPERTIES_VALUE "properties" +#define DLR_INTERFACE_VALUE "value" +#define DLR_INTERFACE_OFFSET "offset" +#define DLR_INTERFACE_POSITION "position" +#define DLR_INTERFACE_TRACKID "trackid" +#define DLR_INTERFACE_TRACK_NUMBER "TrackNumber" + +#define DLR_INTERFACE_RAISE "Raise" +#define DLR_INTERFACE_QUIT "Quit" +#define DLR_INTERFACE_PLAY "Play" +#define DLR_INTERFACE_PLAY_PAUSE "PlayPause" +#define DLR_INTERFACE_NEXT "Next" +#define DLR_INTERFACE_PREVIOUS "Previous" +#define DLR_INTERFACE_PAUSE "Pause" +#define DLR_INTERFACE_STOP "Stop" +#define DLR_INTERFACE_OPEN_URI "OpenUri" +#define DLR_INTERFACE_SEEK "Seek" +#define DLR_INTERFACE_SET_POSITION "SetPosition" +#define DLR_INTERFACE_GOTO_TRACK "GotoTrack" + +#define DLR_INTERFACE_CANCEL "Cancel" + +typedef struct dlr_context_t_ dlr_context_t; +struct dlr_context_t_ { + guint dlr_id; + dleyna_connector_id_t connection; + guint watchers; + dleyna_task_processor_t *processor; + const dleyna_connector_t *connector; + dlr_upnp_t *upnp; + dleyna_settings_t *settings; +}; + +static dlr_context_t g_context; + +static const gchar g_root_introspection[] = + "<node>" + " <interface name='"DLEYNA_SERVER_INTERFACE_MANAGER"'>" + " <method name='"DLR_INTERFACE_GET_VERSION"'>" + " <arg type='s' name='"DLR_INTERFACE_VERSION"'" + " direction='out'/>" + " </method>" + " <method name='"DLR_INTERFACE_RELEASE"'>" + " </method>" + " <method name='"DLR_INTERFACE_GET_SERVERS"'>" + " <arg type='as' name='"DLR_INTERFACE_SERVERS"'" + " direction='out'/>" + " </method>" + " <signal name='"DLR_INTERFACE_FOUND_SERVER"'>" + " <arg type='s' name='"DLR_INTERFACE_PATH"'/>" + " </signal>" + " <signal name='"DLR_INTERFACE_LOST_SERVER"'>" + " <arg type='s' name='"DLR_INTERFACE_PATH"'/>" + " </signal>" + " </interface>" + "</node>"; + +static const gchar g_server_introspection[] = + "<node>" + " <interface name='"DLR_INTERFACE_PROPERTIES"'>" + " <method name='"DLR_INTERFACE_GET"'>" + " <arg type='s' name='"DLR_INTERFACE_INTERFACE_NAME"'" + " direction='in'/>" + " <arg type='s' name='"DLR_INTERFACE_PROPERTY_NAME"'" + " direction='in'/>" + " <arg type='v' name='"DLR_INTERFACE_VALUE"'" + " direction='out'/>" + " </method>" + " <method name='"DLR_INTERFACE_GET_ALL"'>" + " <arg type='s' name='"DLR_INTERFACE_INTERFACE_NAME"'" + " direction='in'/>" + " <arg type='a{sv}' name='"DLR_INTERFACE_PROPERTIES_VALUE"'" + " direction='out'/>" + " </method>" + " <method name='"DLR_INTERFACE_SET"'>" + " <arg type='s' name='"DLR_INTERFACE_INTERFACE_NAME"'" + " direction='in'/>" + " <arg type='s' name='"DLR_INTERFACE_PROPERTY_NAME"'" + " direction='in'/>" + " <arg type='v' name='"DLR_INTERFACE_VALUE"'" + " direction='in'/>" + " </method>" + " <signal name='"DLR_INTERFACE_PROPERTIES_CHANGED"'>" + " <arg type='s' name='"DLR_INTERFACE_INTERFACE_NAME"'/>" + " <arg type='a{sv}' name='"DLR_INTERFACE_CHANGED_PROPERTIES"'/>" + " <arg type='as' name='"DLR_INTERFACE_INVALIDATED_PROPERTIES"'/>" + " </signal>" + " </interface>" + " <interface name='"DLR_INTERFACE_SERVER"'>" + " <method name='"DLR_INTERFACE_RAISE"'>" + " </method>" + " <method name='"DLR_INTERFACE_QUIT"'>" + " </method>" + " <property type='b' name='"DLR_INTERFACE_PROP_CAN_QUIT"'" + " access='read'/>" + " <property type='b' name='"DLR_INTERFACE_PROP_CAN_RAISE"'" + " access='read'/>" + " <property type='b' name='"DLR_INTERFACE_PROP_CAN_SET_FULLSCREEN"'" + " access='read'/>" + " <property type='b' name='"DLR_INTERFACE_PROP_HAS_TRACK_LIST"'" + " access='read'/>" + " <property type='s' name='"DLR_INTERFACE_PROP_IDENTITY"'" + " access='read'/>" + " <property type='as' name='"DLR_INTERFACE_PROP_SUPPORTED_URIS"'" + " access='read'/>" + " <property type='as' name='"DLR_INTERFACE_PROP_SUPPORTED_MIME"'" + " access='read'/>" + " </interface>" + " <interface name='"DLR_INTERFACE_PLAYER"'>" + " <method name='"DLR_INTERFACE_PLAY"'>" + " </method>" + " <method name='"DLR_INTERFACE_PAUSE"'>" + " </method>" + " <method name='"DLR_INTERFACE_PLAY_PAUSE"'>" + " </method>" + " <method name='"DLR_INTERFACE_STOP"'>" + " </method>" + " <method name='"DLR_INTERFACE_NEXT"'>" + " </method>" + " <method name='"DLR_INTERFACE_PREVIOUS"'>" + " </method>" + " <method name='"DLR_INTERFACE_OPEN_URI"'>" + " <arg type='s' name='"DLR_INTERFACE_URI"'" + " direction='in'/>" + " </method>" + " <method name='"DLR_INTERFACE_SEEK"'>" + " <arg type='x' name='"DLR_INTERFACE_OFFSET"'" + " direction='in'/>" + " </method>" + " <method name='"DLR_INTERFACE_SET_POSITION"'>" + " <arg type='o' name='"DLR_INTERFACE_TRACKID"'" + " direction='in'/>" + " <arg type='x' name='"DLR_INTERFACE_POSITION"'" + " direction='in'/>" + " </method>" + " <method name='"DLR_INTERFACE_GOTO_TRACK"'>" + " <arg type='u' name='"DLR_INTERFACE_TRACK_NUMBER"'" + " direction='in'/>" + " </method>" + " <property type='s' name='"DLR_INTERFACE_PROP_PLAYBACK_STATUS"'" + " access='read'/>" + " <property type='d' name='"DLR_INTERFACE_PROP_RATE"'" + " access='readwrite'/>" + " <property type='d' name='"DLR_INTERFACE_PROP_MINIMUM_RATE"'" + " access='read'/>" + " <property type='d' name='"DLR_INTERFACE_PROP_MAXIMUM_RATE"'" + " access='read'/>" + " <property type='ad'" + " name='"DLR_INTERFACE_PROP_TRANSPORT_PLAY_SPEEDS"'" + " access='read'/>" + " <property type='d' name='"DLR_INTERFACE_PROP_VOLUME"'" + " access='readwrite'/>" + " <property type='b' name='"DLR_INTERFACE_PROP_CAN_PLAY"'" + " access='read'/>" + " <property type='b' name='"DLR_INTERFACE_PROP_CAN_SEEK"'" + " access='read'/>" + " <property type='b' name='"DLR_INTERFACE_PROP_CAN_CONTROL"'" + " access='read'/>" + " <property type='b' name='"DLR_INTERFACE_PROP_CAN_PAUSE"'" + " access='read'/>" + " <property type='b' name='"DLR_INTERFACE_PROP_CAN_NEXT"'" + " access='read'/>" + " <property type='b' name='"DLR_INTERFACE_PROP_CAN_PREVIOUS"'" + " access='read'/>" + " <property type='x' name='"DLR_INTERFACE_PROP_POSITION"'" + " access='read'/>" + " <property type='a{sv}' name='"DLR_INTERFACE_PROP_METADATA"'" + " access='read'/>" + " <property type='u' name='"DLR_INTERFACE_PROP_CURRENT_TRACK"'" + " access='read'/>" + " <property type='u' name='"DLR_INTERFACE_PROP_NUMBER_OF_TRACKS"'" + " access='read'/>" + " </interface>" + " <interface name='"DLEYNA_INTERFACE_PUSH_HOST"'>" + " <method name='"DLR_INTERFACE_HOST_FILE"'>" + " <arg type='s' name='"DLR_INTERFACE_PATH"'" + " direction='in'/>" + " <arg type='s' name='"DLR_INTERFACE_URI"'" + " direction='out'/>" + " </method>" + " <method name='"DLR_INTERFACE_REMOVE_FILE"'>" + " <arg type='s' name='"DLR_INTERFACE_PATH"'" + " direction='in'/>" + " </method>" + " </interface>" + " <interface name='"DLEYNA_SERVER_INTERFACE_RENDERER_DEVICE"'>" + " <method name='"DLR_INTERFACE_CANCEL"'>" + " </method>" + " <property type='s' name='"DLR_INTERFACE_PROP_DEVICE_TYPE"'" + " access='read'/>" + " <property type='s' name='"DLR_INTERFACE_PROP_UDN"'" + " access='read'/>" + " <property type='s' name='"DLR_INTERFACE_PROP_FRIENDLY_NAME"'" + " access='read'/>" + " <property type='s' name='"DLR_INTERFACE_PROP_ICON_URL"'" + " access='read'/>" + " <property type='s' name='"DLR_INTERFACE_PROP_MANUFACTURER"'" + " access='read'/>" + " <property type='s' name='"DLR_INTERFACE_PROP_MANUFACTURER_URL"'" + " access='read'/>" + " <property type='s' name='"DLR_INTERFACE_PROP_MODEL_DESCRIPTION"'" + " access='read'/>" + " <property type='s' name='"DLR_INTERFACE_PROP_MODEL_NAME"'" + " access='read'/>" + " <property type='s' name='"DLR_INTERFACE_PROP_MODEL_NUMBER"'" + " access='read'/>" + " <property type='s' name='"DLR_INTERFACE_PROP_SERIAL_NUMBER"'" + " access='read'/>" + " <property type='s' name='"DLR_INTERFACE_PROP_PRESENTATION_URL"'" + " access='read'/>" + " <property type='s' name='"DLR_INTERFACE_PROP_PROTOCOL_INFO"'" + " access='read'/>" + " </interface>" + "</node>"; + +static void prv_process_task(dleyna_task_atom_t *task, gpointer user_data); + +static void prv_dlr_method_call(dleyna_connector_id_t conn, + const gchar *sender, + const gchar *object, + const gchar *interface, + const gchar *method, + GVariant *parameters, + dleyna_connector_msg_id_t invocation); + +static void prv_dlr_device_method_call(dleyna_connector_id_t conn, + const gchar *sender, + const gchar *object, + const gchar *interface, + const gchar *method, + GVariant *parameters, + dleyna_connector_msg_id_t invocation); + +static void prv_props_method_call(dleyna_connector_id_t conn, + const gchar *sender, + const gchar *object, + const gchar *interface, + const gchar *method, + GVariant *parameters, + dleyna_connector_msg_id_t invocation); + +static void prv_dlr_player_method_call(dleyna_connector_id_t conn, + const gchar *sender, + const gchar *object, + const gchar *interface, + const gchar *method, + GVariant *parameters, + dleyna_connector_msg_id_t invocation); + +static void prv_dlr_push_host_method_call(dleyna_connector_id_t conn, + const gchar *sender, + const gchar *object, + const gchar *interface, + const gchar *method, + GVariant *parameters, + dleyna_connector_msg_id_t invocation); + +static void prv_renderer_device_method_call( + dleyna_connector_id_t conn, + const gchar *sender, + const gchar *object, + const gchar *interface, + const gchar *method, + GVariant *parameters, + dleyna_connector_msg_id_t invocation); + +static const dleyna_connector_dispatch_cb_t g_root_vtables[1] = { + prv_dlr_method_call +}; + +static const dleyna_connector_dispatch_cb_t + g_server_vtables[DLR_INTERFACE_INFO_MAX] = { + /* MUST be in the exact same order as g_msu_server_introspection */ + prv_props_method_call, + prv_dlr_device_method_call, + prv_dlr_player_method_call, + prv_dlr_push_host_method_call, + prv_renderer_device_method_call +}; + +const dleyna_connector_t *dlr_renderer_get_connector(void) +{ + return g_context.connector; +} + +dleyna_task_processor_t *dlr_renderer_service_get_task_processor(void) +{ + return g_context.processor; +} + +dlr_upnp_t *dlr_renderer_service_get_upnp(void) +{ + return g_context.upnp; +} + +static void prv_process_sync_task(dlr_task_t *task) +{ + GError *error; + + switch (task->type) { + case DLR_TASK_GET_VERSION: + dlr_task_complete(task); + dleyna_task_queue_task_completed(task->atom.queue_id); + break; + case DLR_TASK_GET_SERVERS: + task->result = dlr_upnp_get_server_ids(g_context.upnp); + dlr_task_complete(task); + dleyna_task_queue_task_completed(task->atom.queue_id); + break; + case DLR_TASK_RAISE: + case DLR_TASK_QUIT: + error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_NOT_SUPPORTED, + "Command not supported."); + dlr_task_fail(task, error); + dleyna_task_queue_task_completed(task->atom.queue_id); + g_error_free(error); + break; + default: + break; + } +} + +static void prv_async_task_complete(dlr_task_t *task, GError *error) +{ + DLEYNA_LOG_DEBUG("Enter"); + + if (error) { + dlr_task_fail(task, error); + g_error_free(error); + } else { + dlr_task_complete(task); + } + + dleyna_task_queue_task_completed(task->atom.queue_id); + + DLEYNA_LOG_DEBUG("Exit"); +} + +static void prv_process_async_task(dlr_task_t *task) +{ + dlr_async_task_t *async_task = (dlr_async_task_t *)task; + + DLEYNA_LOG_DEBUG("Enter"); + + async_task->cancellable = g_cancellable_new(); + + switch (task->type) { + case DLR_TASK_GET_PROP: + dlr_upnp_get_prop(g_context.upnp, task, + prv_async_task_complete); + break; + case DLR_TASK_GET_ALL_PROPS: + dlr_upnp_get_all_props(g_context.upnp, task, + prv_async_task_complete); + break; + case DLR_TASK_SET_PROP: + dlr_upnp_set_prop(g_context.upnp, task, + prv_async_task_complete); + break; + case DLR_TASK_PLAY: + dlr_upnp_play(g_context.upnp, task, + prv_async_task_complete); + break; + case DLR_TASK_PAUSE: + dlr_upnp_pause(g_context.upnp, task, + prv_async_task_complete); + break; + case DLR_TASK_PLAY_PAUSE: + dlr_upnp_play_pause(g_context.upnp, task, + prv_async_task_complete); + break; + case DLR_TASK_STOP: + dlr_upnp_stop(g_context.upnp, task, + prv_async_task_complete); + break; + case DLR_TASK_NEXT: + dlr_upnp_next(g_context.upnp, task, + prv_async_task_complete); + break; + case DLR_TASK_PREVIOUS: + dlr_upnp_previous(g_context.upnp, task, + prv_async_task_complete); + break; + case DLR_TASK_OPEN_URI: + dlr_upnp_open_uri(g_context.upnp, task, + prv_async_task_complete); + break; + case DLR_TASK_SEEK: + dlr_upnp_seek(g_context.upnp, task, + prv_async_task_complete); + break; + case DLR_TASK_SET_POSITION: + dlr_upnp_set_position(g_context.upnp, task, + prv_async_task_complete); + break; + case DLR_TASK_GOTO_TRACK: + dlr_upnp_goto_track(g_context.upnp, task, + prv_async_task_complete); + break; + case DLR_TASK_HOST_URI: + dlr_upnp_host_uri(g_context.upnp, task, + prv_async_task_complete); + break; + case DLR_TASK_REMOVE_URI: + dlr_upnp_remove_uri(g_context.upnp, task, + prv_async_task_complete); + break; + default: + break; + } + + DLEYNA_LOG_DEBUG("Exit"); +} + +static void prv_process_task(dleyna_task_atom_t *task, gpointer user_data) +{ + dlr_task_t *client_task = (dlr_task_t *)task; + + if (client_task->synchronous) + prv_process_sync_task(client_task); + else + prv_process_async_task(client_task); +} + +static void prv_cancel_task(dleyna_task_atom_t *task, gpointer user_data) +{ + dlr_task_cancel((dlr_task_t *)task); +} + +static void prv_delete_task(dleyna_task_atom_t *task, gpointer user_data) +{ + dlr_task_delete((dlr_task_t *)task); +} + +static void prv_remove_client(const gchar *name) +{ + dleyna_task_processor_remove_queues_for_source(g_context.processor, + name); + + dlr_upnp_lost_client(g_context.upnp, name); + + g_context.watchers--; + if (g_context.watchers == 0) + if (!dleyna_settings_is_never_quit(g_context.settings)) + dleyna_task_processor_set_quitting(g_context.processor); +} + +static void prv_lost_client(const gchar *name) +{ + DLEYNA_LOG_INFO("Client %s lost", name); + prv_remove_client(name); +} + +static void prv_control_point_initialize(const dleyna_connector_t *connector, + dleyna_task_processor_t *processor, + dleyna_settings_t *settings) +{ + memset(&g_context, 0, sizeof(g_context)); + + g_context.processor = processor; + g_context.settings = settings; + g_context.connector = connector; + g_context.connector->set_client_lost_cb(prv_lost_client); + + g_set_prgname(DLR_PRG_NAME); +} + +static void prv_control_point_stop_service(void) +{ + dlr_upnp_unsubscribe(g_context.upnp); + + if (g_context.upnp) + dlr_upnp_delete(g_context.upnp); + + if (g_context.connection) { + if (g_context.dlr_id) + g_context.connector->unpublish_object( + g_context.connection, + g_context.dlr_id); + } +} + +static void prv_control_point_free(void) +{ +} + +static void prv_add_task(dlr_task_t *task, const gchar *source, + const gchar *sink) +{ + const dleyna_task_queue_key_t *queue_id; + + if (g_context.connector->watch_client(source)) + g_context.watchers++; + + queue_id = dleyna_task_processor_lookup_queue(g_context.processor, + source, sink); + if (!queue_id) + queue_id = dleyna_task_processor_add_queue( + g_context.processor, + source, + sink, + DLEYNA_TASK_QUEUE_FLAG_AUTO_START, + prv_process_task, + prv_cancel_task, + prv_delete_task); + + dleyna_task_queue_add_task(queue_id, &task->atom); +} + +static void prv_dlr_method_call(dleyna_connector_id_t conn, + const gchar *sender, const gchar *object, + const gchar *interface, + const gchar *method, GVariant *parameters, + dleyna_connector_msg_id_t invocation) +{ + dlr_task_t *task; + + DLEYNA_LOG_INFO("Calling %s method", method); + + if (!strcmp(method, DLR_INTERFACE_RELEASE)) { + g_context.connector->unwatch_client(sender); + prv_remove_client(sender); + g_context.connector->return_response(invocation, NULL); + } else { + if (!strcmp(method, DLR_INTERFACE_GET_VERSION)) + task = dlr_task_get_version_new(invocation); + else if (!strcmp(method, DLR_INTERFACE_GET_SERVERS)) + task = dlr_task_get_servers_new(invocation); + else + goto finished; + + prv_add_task(task, sender, DLR_RENDERER_SINK); + } + +finished: + + return; +} + +static const gchar *prv_get_device_id(const gchar *object, GError **error) +{ + dlr_device_t *device; + + device = dlr_device_from_path(object, + dlr_upnp_get_server_udn_map(g_context.upnp)); + + + if (!device) { + DLEYNA_LOG_WARNING("Cannot locate device for %s", object); + + *error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OBJECT_NOT_FOUND, + "Cannot locate device corresponding to the specified path"); + goto on_error; + } + + return device->path; + +on_error: + + return NULL; +} + +static void prv_props_method_call(dleyna_connector_id_t conn, + const gchar *sender, + const gchar *object, + const gchar *interface, + const gchar *method, + GVariant *parameters, + dleyna_connector_msg_id_t invocation) +{ + dlr_task_t *task; + const gchar *device_id; + GError *error = NULL; + + device_id = prv_get_device_id(object, &error); + if (!device_id) { + g_context.connector->return_error(invocation, error); + g_error_free(error); + + goto finished; + } + + if (!strcmp(method, DLR_INTERFACE_GET_ALL)) + task = dlr_task_get_props_new(invocation, object, parameters); + else if (!strcmp(method, DLR_INTERFACE_GET)) + task = dlr_task_get_prop_new(invocation, object, parameters); + else if (!strcmp(method, DLR_INTERFACE_SET)) + task = dlr_task_set_prop_new(invocation, object, parameters); + else + goto finished; + + prv_add_task(task, sender, device_id); + +finished: + + return; +} + +static void prv_dlr_device_method_call(dleyna_connector_id_t conn, + const gchar *sender, + const gchar *object, + const gchar *interface, + const gchar *method, + GVariant *parameters, + dleyna_connector_msg_id_t invocation) +{ + dlr_task_t *task; + const gchar *device_id; + GError *error = NULL; + + device_id = prv_get_device_id(object, &error); + if (!device_id) { + g_context.connector->return_error(invocation, error); + g_error_free(error); + + goto finished; + } + + if (!strcmp(method, DLR_INTERFACE_RAISE)) + task = dlr_task_raise_new(invocation); + else if (!strcmp(method, DLR_INTERFACE_QUIT)) + task = dlr_task_quit_new(invocation); + else + goto finished; + + prv_add_task(task, sender, device_id); + +finished: + + return; +} + +static void prv_dlr_player_method_call(dleyna_connector_id_t conn, + const gchar *sender, + const gchar *object, + const gchar *interface, + const gchar *method, + GVariant *parameters, + dleyna_connector_msg_id_t invocation) +{ + dlr_task_t *task; + const gchar *device_id; + GError *error = NULL; + + device_id = prv_get_device_id(object, &error); + if (!device_id) { + g_context.connector->return_error(invocation, error); + g_error_free(error); + + goto finished; + } + + if (!strcmp(method, DLR_INTERFACE_PLAY)) + task = dlr_task_play_new(invocation, object); + else if (!strcmp(method, DLR_INTERFACE_PAUSE)) + task = dlr_task_pause_new(invocation, object); + else if (!strcmp(method, DLR_INTERFACE_PLAY_PAUSE)) + task = dlr_task_play_pause_new(invocation, object); + else if (!strcmp(method, DLR_INTERFACE_STOP)) + task = dlr_task_stop_new(invocation, object); + else if (!strcmp(method, DLR_INTERFACE_NEXT)) + task = dlr_task_next_new(invocation, object); + else if (!strcmp(method, DLR_INTERFACE_PREVIOUS)) + task = dlr_task_previous_new(invocation, object); + else if (!strcmp(method, DLR_INTERFACE_OPEN_URI)) + task = dlr_task_open_uri_new(invocation, object, parameters); + else if (!strcmp(method, DLR_INTERFACE_SEEK)) + task = dlr_task_seek_new(invocation, object, parameters); + else if (!strcmp(method, DLR_INTERFACE_SET_POSITION)) + task = dlr_task_set_position_new(invocation, object, + parameters); + else if (!strcmp(method, DLR_INTERFACE_GOTO_TRACK)) + task = dlr_task_goto_track_new(invocation, object, parameters); + else + goto finished; + + prv_add_task(task, sender, device_id); + +finished: + + return; +} + +static void prv_dlr_push_host_method_call(dleyna_connector_id_t conn, + const gchar *sender, + const gchar *object, + const gchar *interface, + const gchar *method, + GVariant *parameters, + dleyna_connector_msg_id_t invocation) +{ + dlr_task_t *task; + const gchar *device_id; + GError *error = NULL; + + device_id = prv_get_device_id(object, &error); + if (!device_id) { + g_context.connector->return_error(invocation, error); + g_error_free(error); + + goto on_error; + } + + if (!strcmp(method, DLR_INTERFACE_HOST_FILE)) + task = dlr_task_host_uri_new(invocation, object, sender, + parameters); + else if (!strcmp(method, DLR_INTERFACE_REMOVE_FILE)) + task = dlr_task_remove_uri_new(invocation, object, sender, + parameters); + else + goto on_error; + + prv_add_task(task, sender, device_id); + +on_error: + + return; +} + +static void prv_renderer_device_method_call( + dleyna_connector_id_t conn, + const gchar *sender, + const gchar *object, + const gchar *interface, + const gchar *method, + GVariant *parameters, + dleyna_connector_msg_id_t invocation) +{ + const gchar *device_id = NULL; + GError *error = NULL; + const dleyna_task_queue_key_t *queue_id; + + device_id = prv_get_device_id(object, &error); + if (!device_id) { + g_context.connector->return_error(invocation, error); + g_error_free(error); + + goto finished; + } + + if (!strcmp(method, DLR_INTERFACE_CANCEL)) { + queue_id = dleyna_task_processor_lookup_queue( + g_context.processor, + sender, device_id); + if (queue_id) + dleyna_task_processor_cancel_queue(queue_id); + + g_context.connector->return_response(invocation, NULL); + } + +finished: + + return; +} + +static void prv_found_media_server(const gchar *path) +{ + DLEYNA_LOG_INFO("New media server %s", path); + + (void) g_context.connector->notify(g_context.connection, + DLEYNA_SERVER_OBJECT, + DLEYNA_SERVER_INTERFACE_MANAGER, + DLR_INTERFACE_FOUND_SERVER, + g_variant_new("(s)", path), + NULL); +} + +static void prv_lost_media_server(const gchar *path) +{ + DLEYNA_LOG_INFO("Lost %s", path); + + (void) g_context.connector->notify(g_context.connection, + DLEYNA_SERVER_OBJECT, + DLEYNA_SERVER_INTERFACE_MANAGER, + DLR_INTERFACE_LOST_SERVER, + g_variant_new("(s)", path), + NULL); + + dleyna_task_processor_remove_queues_for_sink(g_context.processor, path); +} + +static gboolean prv_control_point_start_service( + dleyna_connector_id_t connection) +{ + gboolean retval = TRUE; + + g_context.connection = connection; + + g_context.dlr_id = g_context.connector->publish_object( + connection, + DLEYNA_SERVER_OBJECT, + TRUE, + 0, + g_root_vtables); + + if (!g_context.dlr_id) { + retval = FALSE; + goto out; + } else { + g_context.upnp = dlr_upnp_new(connection, + g_server_vtables, + prv_found_media_server, + prv_lost_media_server); + } + +out: + + return retval; +} + +static const gchar *prv_control_point_server_name(void) +{ + return DLEYNA_SERVER_NAME; +} + +static const gchar *prv_control_point_server_introspection(void) +{ + return g_server_introspection; +} + +static const gchar *prv_control_point_root_introspection(void) +{ + return g_root_introspection; +} + +static const dleyna_control_point_t g_control_point = { + prv_control_point_initialize, + prv_control_point_free, + prv_control_point_server_name, + prv_control_point_server_introspection, + prv_control_point_root_introspection, + prv_control_point_start_service, + prv_control_point_stop_service +}; + +const dleyna_control_point_t *dleyna_control_point_get_renderer(void) +{ + return &g_control_point; +} + diff --git a/libdleyna/renderer/server.h b/libdleyna/renderer/server.h new file mode 100644 index 0000000..ad770e9 --- /dev/null +++ b/libdleyna/renderer/server.h @@ -0,0 +1,40 @@ +/* + * dLeyna + * + * Copyright (C) 2012-2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Regis Merlino <regis.merlino@intel.com> + * + */ + +#ifndef DLR_SERVER_H__ +#define DLR_SERVER_H__ + +#include <libdleyna/core/connector.h> +#include <libdleyna/core/task-processor.h> + +#define DLR_RENDERER_SINK "dleyna-renderer" + +typedef struct dlr_device_t_ dlr_device_t; +typedef struct dlr_upnp_t_ dlr_upnp_t; + +dlr_upnp_t *dlr_renderer_service_get_upnp(void); + +dleyna_task_processor_t *dlr_renderer_service_get_task_processor(void); + +const dleyna_connector_t *dlr_renderer_get_connector(void); + +#endif /* DLR_SERVER_H__ */ diff --git a/libdleyna/renderer/task.c b/libdleyna/renderer/task.c new file mode 100644 index 0000000..f461824 --- /dev/null +++ b/libdleyna/renderer/task.c @@ -0,0 +1,378 @@ +/* + * dLeyna + * + * Copyright (C) 2012-2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Mark Ryan <mark.d.ryan@intel.com> + * + */ + +#include <libdleyna/core/error.h> +#include <libdleyna/core/task-processor.h> + +#include "async.h" +#include "server.h" + +dlr_task_t *dlr_task_get_version_new(dleyna_connector_msg_id_t invocation) +{ + dlr_task_t *task = g_new0(dlr_task_t, 1); + + task->type = DLR_TASK_GET_VERSION; + task->invocation = invocation; + task->result_format = "(@s)"; + task->result = g_variant_ref_sink(g_variant_new_string(VERSION)); + task->synchronous = TRUE; + + return task; +} + +dlr_task_t *dlr_task_get_servers_new(dleyna_connector_msg_id_t invocation) +{ + dlr_task_t *task = g_new0(dlr_task_t, 1); + + task->type = DLR_TASK_GET_SERVERS; + task->invocation = invocation; + task->result_format = "(@as)"; + task->synchronous = TRUE; + + return task; +} + +dlr_task_t *dlr_task_raise_new(dleyna_connector_msg_id_t invocation) +{ + dlr_task_t *task = g_new0(dlr_task_t, 1); + + task->type = DLR_TASK_RAISE; + task->invocation = invocation; + task->synchronous = TRUE; + + return task; +} + +dlr_task_t *dlr_task_quit_new(dleyna_connector_msg_id_t invocation) +{ + dlr_task_t *task = g_new0(dlr_task_t, 1); + + task->type = DLR_TASK_QUIT; + task->invocation = invocation; + task->synchronous = TRUE; + + return task; +} + +static void prv_dlr_task_delete(dlr_task_t *task) +{ + if (!task->synchronous) + dlr_async_task_delete((dlr_async_task_t *)task); + + switch (task->type) { + case DLR_TASK_GET_ALL_PROPS: + g_free(task->ut.get_props.interface_name); + break; + case DLR_TASK_GET_PROP: + g_free(task->ut.get_prop.interface_name); + g_free(task->ut.get_prop.prop_name); + break; + case DLR_TASK_SET_PROP: + g_free(task->ut.set_prop.interface_name); + g_free(task->ut.set_prop.prop_name); + g_variant_unref(task->ut.set_prop.params); + break; + case DLR_TASK_OPEN_URI: + g_free(task->ut.open_uri.uri); + break; + case DLR_TASK_HOST_URI: + case DLR_TASK_REMOVE_URI: + g_free(task->ut.host_uri.uri); + g_free(task->ut.host_uri.client); + break; + default: + break; + } + + g_free(task->path); + if (task->result) + g_variant_unref(task->result); + + g_free(task); +} + +static dlr_task_t *prv_device_task_new(dlr_task_type_t type, + dleyna_connector_msg_id_t invocation, + const gchar *path, + const gchar *result_format) +{ + dlr_task_t *task = (dlr_task_t *)g_new0(dlr_async_task_t, 1); + + task->type = type; + task->invocation = invocation; + task->result_format = result_format; + + task->path = g_strdup(path); + g_strstrip(task->path); + + return task; +} + +dlr_task_t *dlr_task_get_prop_new(dleyna_connector_msg_id_t invocation, + const gchar *path, GVariant *parameters) +{ + dlr_task_t *task; + + task = prv_device_task_new(DLR_TASK_GET_PROP, invocation, path, "(v)"); + + g_variant_get(parameters, "(ss)", &task->ut.get_prop.interface_name, + &task->ut.get_prop.prop_name); + + g_strstrip(task->ut.get_prop.interface_name); + g_strstrip(task->ut.get_prop.prop_name); + + return task; +} + +dlr_task_t *dlr_task_get_props_new(dleyna_connector_msg_id_t invocation, + const gchar *path, GVariant *parameters) +{ + dlr_task_t *task; + + task = prv_device_task_new(DLR_TASK_GET_ALL_PROPS, invocation, path, + "(@a{sv})"); + + g_variant_get(parameters, "(s)", &task->ut.get_props.interface_name); + g_strstrip(task->ut.get_props.interface_name); + + return task; +} + +dlr_task_t *dlr_task_set_prop_new(dleyna_connector_msg_id_t invocation, + const gchar *path, GVariant *parameters) +{ + dlr_task_t *task; + + task = prv_device_task_new(DLR_TASK_SET_PROP, invocation, path, NULL); + + g_variant_get(parameters, "(ssv)", &task->ut.set_prop.interface_name, + &task->ut.set_prop.prop_name, &task->ut.set_prop.params); + + g_strstrip(task->ut.set_prop.interface_name); + g_strstrip(task->ut.set_prop.prop_name); + + return task; +} + +dlr_task_t *dlr_task_play_new(dleyna_connector_msg_id_t invocation, + const gchar *path) +{ + return prv_device_task_new(DLR_TASK_PLAY, invocation, path, NULL); +} + +dlr_task_t *dlr_task_pause_new(dleyna_connector_msg_id_t invocation, + const gchar *path) +{ + return prv_device_task_new(DLR_TASK_PAUSE, invocation, path, NULL); +} + +dlr_task_t *dlr_task_play_pause_new(dleyna_connector_msg_id_t invocation, + const gchar *path) +{ + return prv_device_task_new(DLR_TASK_PLAY_PAUSE, invocation, path, NULL); +} + +dlr_task_t *dlr_task_stop_new(dleyna_connector_msg_id_t invocation, + const gchar *path) +{ + return prv_device_task_new(DLR_TASK_STOP, invocation, path, NULL); +} + +dlr_task_t *dlr_task_next_new(dleyna_connector_msg_id_t invocation, + const gchar *path) +{ + return prv_device_task_new(DLR_TASK_NEXT, invocation, path, NULL); +} + +dlr_task_t *dlr_task_previous_new(dleyna_connector_msg_id_t invocation, + const gchar *path) +{ + return prv_device_task_new(DLR_TASK_PREVIOUS, invocation, path, NULL); +} + +dlr_task_t *dlr_task_seek_new(dleyna_connector_msg_id_t invocation, + const gchar *path, GVariant *parameters) +{ + dlr_task_t *task = prv_device_task_new(DLR_TASK_SEEK, invocation, + path, NULL); + + g_variant_get(parameters, "(x)", &task->ut.seek.position); + + return task; +} + +dlr_task_t *dlr_task_set_position_new(dleyna_connector_msg_id_t invocation, + const gchar *path, GVariant *parameters) +{ + gchar *track_id; + + dlr_task_t *task = prv_device_task_new(DLR_TASK_SET_POSITION, + invocation, path, NULL); + + g_variant_get(parameters, "(&ox)", &track_id, &task->ut.seek.position); + + return task; +} + +dlr_task_t *dlr_task_goto_track_new(dleyna_connector_msg_id_t invocation, + const gchar *path, GVariant *parameters) +{ + dlr_task_t *task = prv_device_task_new(DLR_TASK_GOTO_TRACK, + invocation, path, NULL); + + g_variant_get(parameters, "(u)", &task->ut.seek.track_number); + + return task; +} + +dlr_task_t *dlr_task_open_uri_new(dleyna_connector_msg_id_t invocation, + const gchar *path, GVariant *parameters) +{ + dlr_task_t *task; + + task = prv_device_task_new(DLR_TASK_OPEN_URI, invocation, path, + NULL); + + g_variant_get(parameters, "(s)", &task->ut.open_uri.uri); + g_strstrip(task->ut.open_uri.uri); + + return task; +} + +dlr_task_t *dlr_task_host_uri_new(dleyna_connector_msg_id_t invocation, + const gchar *path, + const gchar *sender, + GVariant *parameters) +{ + dlr_task_t *task; + + task = prv_device_task_new(DLR_TASK_HOST_URI, invocation, path, + "(@s)"); + + g_variant_get(parameters, "(s)", &task->ut.host_uri.uri); + g_strstrip(task->ut.host_uri.uri); + task->ut.host_uri.client = g_strdup(sender); + + return task; +} + +dlr_task_t *dlr_task_remove_uri_new(dleyna_connector_msg_id_t invocation, + const gchar *path, + const gchar *sender, + GVariant *parameters) +{ + dlr_task_t *task; + + task = prv_device_task_new(DLR_TASK_REMOVE_URI, invocation, path, NULL); + + g_variant_get(parameters, "(s)", &task->ut.host_uri.uri); + g_strstrip(task->ut.host_uri.uri); + task->ut.host_uri.client = g_strdup(sender); + + return task; +} + +void dlr_task_complete(dlr_task_t *task) +{ + if (!task) + goto finished; + + if (task->invocation) { + if (task->result_format && task->result) + dlr_renderer_get_connector()->return_response( + task->invocation, + g_variant_new(task->result_format, + task->result)); + else + dlr_renderer_get_connector()->return_response( + task->invocation, + NULL); + + task->invocation = NULL; + } + +finished: + + return; +} + +void dlr_task_fail(dlr_task_t *task, GError *error) +{ + if (!task) + goto finished; + + if (task->invocation) { + dlr_renderer_get_connector()->return_error(task->invocation, + error); + task->invocation = NULL; + } + +finished: + + return; +} + +void dlr_task_cancel(dlr_task_t *task) +{ + GError *error; + + if (!task) + goto finished; + + if (task->invocation) { + error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_CANCELLED, + "Operation cancelled."); + dlr_renderer_get_connector()->return_error(task->invocation, + error); + task->invocation = NULL; + g_error_free(error); + } + + if (!task->synchronous) + dlr_async_task_cancel((dlr_async_task_t *)task); + +finished: + + return; +} + +void dlr_task_delete(dlr_task_t *task) +{ + GError *error; + + if (!task) + goto finished; + + if (task->invocation) { + error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_DIED, + "Unable to complete command."); + dlr_renderer_get_connector()->return_error(task->invocation, + error); + g_error_free(error); + } + + prv_dlr_task_delete(task); + +finished: + + return; +} diff --git a/libdleyna/renderer/task.h b/libdleyna/renderer/task.h new file mode 100644 index 0000000..4fd11b9 --- /dev/null +++ b/libdleyna/renderer/task.h @@ -0,0 +1,174 @@ +/* + * dLeyna + * + * Copyright (C) 2012-2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Mark Ryan <mark.d.ryan@intel.com> + * + */ + +#ifndef DLR_TASK_H__ +#define DLR_TASK_H__ + +#include <gio/gio.h> +#include <glib.h> + +#include <libdleyna/core/connector.h> +#include <libdleyna/core/task-atom.h> + +enum dlr_task_type_t_ { + DLR_TASK_GET_VERSION, + DLR_TASK_GET_SERVERS, + DLR_TASK_RAISE, + DLR_TASK_QUIT, + DLR_TASK_SET_PROP, + DLR_TASK_GET_ALL_PROPS, + DLR_TASK_GET_PROP, + DLR_TASK_PAUSE, + DLR_TASK_PLAY, + DLR_TASK_PLAY_PAUSE, + DLR_TASK_STOP, + DLR_TASK_NEXT, + DLR_TASK_PREVIOUS, + DLR_TASK_OPEN_URI, + DLR_TASK_SEEK, + DLR_TASK_SET_POSITION, + DLR_TASK_GOTO_TRACK, + DLR_TASK_HOST_URI, + DLR_TASK_REMOVE_URI +}; +typedef enum dlr_task_type_t_ dlr_task_type_t; + +typedef void (*dlr_cancel_task_t)(void *handle); + +typedef struct dlr_task_get_props_t_ dlr_task_get_props_t; +struct dlr_task_get_props_t_ { + gchar *interface_name; +}; + +typedef struct dlr_task_get_prop_t_ dlr_task_get_prop_t; +struct dlr_task_get_prop_t_ { + gchar *prop_name; + gchar *interface_name; +}; + +typedef struct dlr_task_set_prop_t_ dlr_task_set_prop_t; +struct dlr_task_set_prop_t_ { + gchar *prop_name; + gchar *interface_name; + GVariant *params; +}; + +typedef struct dlr_task_open_uri_t_ dlr_task_open_uri_t; +struct dlr_task_open_uri_t_ { + gchar *uri; +}; + +typedef struct dlr_task_seek_t_ dlr_task_seek_t; +struct dlr_task_seek_t_ { + gint64 position; + guint32 track_number; +}; + +typedef struct dlr_task_host_uri_t_ dlr_task_host_uri_t; +struct dlr_task_host_uri_t_ { + gchar *uri; + gchar *client; +}; + +typedef struct dlr_task_t_ dlr_task_t; +struct dlr_task_t_ { + dleyna_task_atom_t atom; /* pseudo inheritance - MUST be first field */ + dlr_task_type_t type; + gchar *path; + const gchar *result_format; + GVariant *result; + dleyna_connector_msg_id_t invocation; + gboolean synchronous; + union { + dlr_task_get_props_t get_props; + dlr_task_get_prop_t get_prop; + dlr_task_set_prop_t set_prop; + dlr_task_open_uri_t open_uri; + dlr_task_host_uri_t host_uri; + dlr_task_seek_t seek; + } ut; +}; + +dlr_task_t *dlr_task_get_version_new(dleyna_connector_msg_id_t invocation); + +dlr_task_t *dlr_task_get_servers_new(dleyna_connector_msg_id_t invocation); + +dlr_task_t *dlr_task_raise_new(dleyna_connector_msg_id_t invocation); + +dlr_task_t *dlr_task_quit_new(dleyna_connector_msg_id_t invocation); + +dlr_task_t *dlr_task_set_prop_new(dleyna_connector_msg_id_t invocation, + const gchar *path, GVariant *parameters); + +dlr_task_t *dlr_task_get_prop_new(dleyna_connector_msg_id_t invocation, + const gchar *path, GVariant *parameters); + +dlr_task_t *dlr_task_get_props_new(dleyna_connector_msg_id_t invocation, + const gchar *path, GVariant *parameters); + +dlr_task_t *dlr_task_play_new(dleyna_connector_msg_id_t invocation, + const gchar *path); + +dlr_task_t *dlr_task_pause_new(dleyna_connector_msg_id_t invocation, + const gchar *path); + +dlr_task_t *dlr_task_play_pause_new(dleyna_connector_msg_id_t invocation, + const gchar *path); + +dlr_task_t *dlr_task_stop_new(dleyna_connector_msg_id_t invocation, + const gchar *path); + +dlr_task_t *dlr_task_next_new(dleyna_connector_msg_id_t invocation, + const gchar *path); + +dlr_task_t *dlr_task_previous_new(dleyna_connector_msg_id_t invocation, + const gchar *path); + +dlr_task_t *dlr_task_seek_new(dleyna_connector_msg_id_t invocation, + const gchar *path, GVariant *parameters); + +dlr_task_t *dlr_task_set_position_new(dleyna_connector_msg_id_t invocation, + const gchar *path, GVariant *parameters); + +dlr_task_t *dlr_task_goto_track_new(dleyna_connector_msg_id_t invocation, + const gchar *path, GVariant *parameters); + +dlr_task_t *dlr_task_open_uri_new(dleyna_connector_msg_id_t invocation, + const gchar *path, GVariant *parameters); + +dlr_task_t *dlr_task_host_uri_new(dleyna_connector_msg_id_t invocation, + const gchar *path, const gchar *sender, + GVariant *parameters); + +dlr_task_t *dlr_task_remove_uri_new(dleyna_connector_msg_id_t invocation, + const gchar *path, const gchar *sender, + GVariant *parameters); + +void dlr_task_complete(dlr_task_t *task); + +void dlr_task_fail(dlr_task_t *task, GError *error); + +void dlr_task_delete(dlr_task_t *task); + +void dlr_task_cancel(dlr_task_t *task); + +#endif diff --git a/libdleyna/renderer/upnp.c b/libdleyna/renderer/upnp.c new file mode 100644 index 0000000..e6d905f --- /dev/null +++ b/libdleyna/renderer/upnp.c @@ -0,0 +1,752 @@ +/* + * dLeyna + * + * Copyright (C) 2012-2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Mark Ryan <mark.d.ryan@intel.com> + * + */ + +#include <string.h> + +#include <libgssdp/gssdp-resource-browser.h> +#include <libgupnp/gupnp-context-manager.h> +#include <libgupnp/gupnp-error.h> + +#include <libdleyna/core/error.h> +#include <libdleyna/core/log.h> +#include <libdleyna/core/service-task.h> + +#include "async.h" +#include "device.h" +#include "host-service.h" +#include "prop-defs.h" +#include "upnp.h" + +struct dlr_upnp_t_ { + dleyna_connector_id_t connection; + const dleyna_connector_dispatch_cb_t *interface_info; + dlr_upnp_callback_t found_server; + dlr_upnp_callback_t lost_server; + GUPnPContextManager *context_manager; + void *user_data; + GHashTable *server_udn_map; + GHashTable *server_uc_map; + guint counter; + dlr_host_service_t *host_service; +}; + +/* Private structure used in service task */ +typedef struct prv_device_new_ct_t_ prv_device_new_ct_t; +struct prv_device_new_ct_t_ { + dlr_upnp_t *upnp; + char *udn; + dlr_device_t *device; + const dleyna_task_queue_key_t *queue_id; +}; + +static void prv_device_new_free(prv_device_new_ct_t *priv_t) +{ + if (priv_t) { + g_free(priv_t->udn); + g_free(priv_t); + } +} + +static void prv_device_chain_end(gboolean cancelled, gpointer data) +{ + dlr_device_t *device; + prv_device_new_ct_t *priv_t = (prv_device_new_ct_t *)data; + + DLEYNA_LOG_DEBUG("Enter"); + + device = priv_t->device; + + if (cancelled) + goto on_clear; + + DLEYNA_LOG_DEBUG("Notify new server available: %s", device->path); + g_hash_table_insert(priv_t->upnp->server_udn_map, g_strdup(priv_t->udn), + device); + priv_t->upnp->found_server(device->path); + +on_clear: + + g_hash_table_remove(priv_t->upnp->server_uc_map, priv_t->udn); + prv_device_new_free(priv_t); + + if (cancelled) + dlr_device_delete(device); + + DLEYNA_LOG_DEBUG("Exit"); + DLEYNA_LOG_DEBUG_NL(); +} + +static void prv_server_available_cb(GUPnPControlPoint *cp, + GUPnPDeviceProxy *proxy, + gpointer user_data) +{ + dlr_upnp_t *upnp = user_data; + const char *udn; + dlr_device_t *device; + const gchar *ip_address; + dlr_device_context_t *context; + const dleyna_task_queue_key_t *queue_id; + unsigned int i; + prv_device_new_ct_t *priv_t; + + DLEYNA_LOG_DEBUG("Enter"); + + udn = gupnp_device_info_get_udn((GUPnPDeviceInfo *)proxy); + + if (!udn) + goto on_error; + + ip_address = gupnp_context_get_host_ip( + gupnp_control_point_get_context(cp)); + + DLEYNA_LOG_DEBUG("UDN %s", udn); + DLEYNA_LOG_DEBUG("IP Address %s", ip_address); + + device = g_hash_table_lookup(upnp->server_udn_map, udn); + + if (!device) { + priv_t = g_hash_table_lookup(upnp->server_uc_map, udn); + + if (priv_t) + device = priv_t->device; + } + + if (!device) { + DLEYNA_LOG_DEBUG("Device not found. Adding"); + + priv_t = g_new0(prv_device_new_ct_t, 1); + + queue_id = dleyna_task_processor_add_queue( + dlr_renderer_service_get_task_processor(), + dleyna_service_task_create_source(), + DLR_RENDERER_SINK, + DLEYNA_TASK_QUEUE_FLAG_AUTO_REMOVE, + dleyna_service_task_process_cb, + dleyna_service_task_cancel_cb, + dleyna_service_task_delete_cb); + dleyna_task_queue_set_finally(queue_id, prv_device_chain_end); + dleyna_task_queue_set_user_data(queue_id, priv_t); + + device = dlr_device_new(upnp->connection, proxy, ip_address, + upnp->counter, + upnp->interface_info, + queue_id); + + upnp->counter++; + + priv_t->upnp = upnp; + priv_t->udn = g_strdup(udn); + priv_t->queue_id = queue_id; + priv_t->device = device; + + g_hash_table_insert(upnp->server_uc_map, g_strdup(udn), priv_t); + } else { + DLEYNA_LOG_DEBUG("Device Found"); + + for (i = 0; i < device->contexts->len; ++i) { + context = g_ptr_array_index(device->contexts, i); + if (!strcmp(context->ip_address, ip_address)) + break; + } + + if (i == device->contexts->len) { + DLEYNA_LOG_DEBUG("Adding Context"); + dlr_device_append_new_context(device, ip_address, + proxy); + } + } + +on_error: + + DLEYNA_LOG_DEBUG("Exit"); + DLEYNA_LOG_DEBUG_NL(); + + return; +} + +static gboolean prv_subscribe_to_service_changes(gpointer user_data) +{ + dlr_device_t *device = user_data; + + device->timeout_id = 0; + dlr_device_subscribe_to_service_changes(device); + + return FALSE; +} + +static void prv_server_unavailable_cb(GUPnPControlPoint *cp, + GUPnPDeviceProxy *proxy, + gpointer user_data) +{ + dlr_upnp_t *upnp = user_data; + const char *udn; + dlr_device_t *device; + const gchar *ip_address; + unsigned int i; + dlr_device_context_t *context; + gboolean subscribed; + gboolean under_construction = FALSE; + prv_device_new_ct_t *priv_t; + + DLEYNA_LOG_DEBUG("Enter"); + + udn = gupnp_device_info_get_udn((GUPnPDeviceInfo *)proxy); + + if (!udn) + goto on_error; + + ip_address = gupnp_context_get_host_ip( + gupnp_control_point_get_context(cp)); + + DLEYNA_LOG_DEBUG("UDN %s", udn); + DLEYNA_LOG_DEBUG("IP Address %s", ip_address); + + device = g_hash_table_lookup(upnp->server_udn_map, udn); + + if (!device) { + priv_t = g_hash_table_lookup(upnp->server_uc_map, udn); + + if (priv_t) { + device = priv_t->device; + under_construction = TRUE; + } + } + + if (!device) { + DLEYNA_LOG_WARNING("Device not found. Ignoring"); + goto on_error; + } + + for (i = 0; i < device->contexts->len; ++i) { + context = g_ptr_array_index(device->contexts, i); + if (!strcmp(context->ip_address, ip_address)) + break; + } + + if (i < device->contexts->len) { + subscribed = (context->subscribed_av || context->subscribed_cm); + + (void) g_ptr_array_remove_index(device->contexts, i); + + if (device->contexts->len == 0) { + if (!under_construction) { + DLEYNA_LOG_DEBUG( + "Last Context lost. Delete device"); + + upnp->lost_server(device->path); + g_hash_table_remove(upnp->server_udn_map, udn); + } else { + DLEYNA_LOG_WARNING( + "Device under construction. Cancelling"); + + dleyna_task_processor_cancel_queue( + priv_t->queue_id); + } + } else if (subscribed && !device->timeout_id) { + DLEYNA_LOG_DEBUG("Subscribe on new context"); + + device->timeout_id = g_timeout_add_seconds(1, + prv_subscribe_to_service_changes, + device); + } + } + +on_error: + + return; +} + +static void prv_on_context_available(GUPnPContextManager *context_manager, + GUPnPContext *context, + gpointer user_data) +{ + dlr_upnp_t *upnp = user_data; + GUPnPControlPoint *cp; + + cp = gupnp_control_point_new( + context, + "urn:schemas-upnp-org:device:MediaRenderer:1"); + + g_signal_connect(cp, "device-proxy-available", + G_CALLBACK(prv_server_available_cb), upnp); + + g_signal_connect(cp, "device-proxy-unavailable", + G_CALLBACK(prv_server_unavailable_cb), upnp); + + gssdp_resource_browser_set_active(GSSDP_RESOURCE_BROWSER(cp), TRUE); + gupnp_context_manager_manage_control_point(upnp->context_manager, cp); + g_object_unref(cp); +} + +dlr_upnp_t *dlr_upnp_new(dleyna_connector_id_t connection, + const dleyna_connector_dispatch_cb_t *dispatch_table, + dlr_upnp_callback_t found_server, + dlr_upnp_callback_t lost_server) +{ + dlr_upnp_t *upnp = g_new0(dlr_upnp_t, 1); + + upnp->connection = connection; + upnp->interface_info = dispatch_table; + upnp->found_server = found_server; + upnp->lost_server = lost_server; + + upnp->server_udn_map = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, + dlr_device_delete); + + upnp->server_uc_map = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, NULL); + + upnp->context_manager = gupnp_context_manager_create(0); + + g_signal_connect(upnp->context_manager, "context-available", + G_CALLBACK(prv_on_context_available), + upnp); + + dlr_host_service_new(&upnp->host_service); + + return upnp; +} + +void dlr_upnp_delete(dlr_upnp_t *upnp) +{ + if (upnp) { + dlr_host_service_delete(upnp->host_service); + g_object_unref(upnp->context_manager); + g_hash_table_unref(upnp->server_udn_map); + g_hash_table_unref(upnp->server_uc_map); + + g_free(upnp); + } +} + +GVariant *dlr_upnp_get_server_ids(dlr_upnp_t *upnp) +{ + GVariantBuilder vb; + GHashTableIter iter; + gpointer value; + dlr_device_t *device; + + DLEYNA_LOG_DEBUG("Enter"); + + g_variant_builder_init(&vb, G_VARIANT_TYPE("as")); + g_hash_table_iter_init(&iter, upnp->server_udn_map); + + while (g_hash_table_iter_next(&iter, NULL, &value)) { + device = value; + g_variant_builder_add(&vb, "s", device->path); + } + + DLEYNA_LOG_DEBUG("Exit"); + + return g_variant_ref_sink(g_variant_builder_end(&vb)); +} + +GHashTable *dlr_upnp_get_server_udn_map(dlr_upnp_t *upnp) +{ + return upnp->server_udn_map; +} + + +void dlr_upnp_set_prop(dlr_upnp_t *upnp, dlr_task_t *task, + dlr_upnp_task_complete_t cb) +{ + dlr_device_t *device; + dlr_async_task_t *cb_data = (dlr_async_task_t *)task; + + device = dlr_device_from_path(task->path, upnp->server_udn_map); + + if (!device) { + cb_data->cb = cb; + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OBJECT_NOT_FOUND, + "Cannot locate a device for the specified object"); + + (void) g_idle_add(dlr_async_task_complete, cb_data); + } else { + dlr_device_set_prop(device, task, cb); + } +} + +void dlr_upnp_get_prop(dlr_upnp_t *upnp, dlr_task_t *task, + dlr_upnp_task_complete_t cb) +{ + dlr_device_t *device; + dlr_async_task_t *cb_data = (dlr_async_task_t *)task; + + DLEYNA_LOG_DEBUG("Enter"); + + DLEYNA_LOG_DEBUG("Path: %s", task->path); + DLEYNA_LOG_DEBUG("Interface %s", task->ut.get_prop.interface_name); + DLEYNA_LOG_DEBUG("Prop.%s", task->ut.get_prop.prop_name); + + device = dlr_device_from_path(task->path, upnp->server_udn_map); + + if (!device) { + DLEYNA_LOG_WARNING("Cannot locate device"); + + cb_data->cb = cb; + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OBJECT_NOT_FOUND, + "Cannot locate a device for the specified object"); + + (void) g_idle_add(dlr_async_task_complete, cb_data); + } else { + dlr_device_get_prop(device, task, cb); + } + + DLEYNA_LOG_DEBUG("Exit"); +} + +void dlr_upnp_get_all_props(dlr_upnp_t *upnp, dlr_task_t *task, + dlr_upnp_task_complete_t cb) +{ + dlr_device_t *device; + dlr_async_task_t *cb_data = (dlr_async_task_t *)task; + + DLEYNA_LOG_DEBUG("Enter"); + + DLEYNA_LOG_DEBUG("Path: %s", task->path); + DLEYNA_LOG_DEBUG("Interface %s", task->ut.get_prop.interface_name); + + device = dlr_device_from_path(task->path, upnp->server_udn_map); + + if (!device) { + cb_data->cb = cb; + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OBJECT_NOT_FOUND, + "Cannot locate a device for the specified object"); + + (void) g_idle_add(dlr_async_task_complete, cb_data); + } else { + dlr_device_get_all_props(device, task, cb); + } + + DLEYNA_LOG_DEBUG("Exit"); +} + +void dlr_upnp_play(dlr_upnp_t *upnp, dlr_task_t *task, + dlr_upnp_task_complete_t cb) +{ + dlr_device_t *device; + dlr_async_task_t *cb_data = (dlr_async_task_t *)task; + + DLEYNA_LOG_DEBUG("Enter"); + + device = dlr_device_from_path(task->path, upnp->server_udn_map); + + if (!device) { + cb_data->cb = cb; + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OBJECT_NOT_FOUND, + "Cannot locate a device for the specified object"); + + (void) g_idle_add(dlr_async_task_complete, cb_data); + } else { + dlr_device_play(device, task, cb); + } + + DLEYNA_LOG_DEBUG("Exit"); +} + +void dlr_upnp_pause(dlr_upnp_t *upnp, dlr_task_t *task, + dlr_upnp_task_complete_t cb) +{ + dlr_device_t *device; + dlr_async_task_t *cb_data = (dlr_async_task_t *)task; + + DLEYNA_LOG_DEBUG("Enter"); + + device = dlr_device_from_path(task->path, upnp->server_udn_map); + + if (!device) { + cb_data->cb = cb; + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OBJECT_NOT_FOUND, + "Cannot locate a device for the specified object"); + + (void) g_idle_add(dlr_async_task_complete, cb_data); + } else { + dlr_device_pause(device, task, cb); + } + + DLEYNA_LOG_DEBUG("Exit"); +} + +void dlr_upnp_play_pause(dlr_upnp_t *upnp, dlr_task_t *task, + dlr_upnp_task_complete_t cb) +{ + dlr_device_t *device; + dlr_async_task_t *cb_data = (dlr_async_task_t *)task; + + DLEYNA_LOG_DEBUG("Enter"); + + device = dlr_device_from_path(task->path, upnp->server_udn_map); + + if (!device) { + cb_data->cb = cb; + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OBJECT_NOT_FOUND, + "Cannot locate a device for the specified object"); + + (void) g_idle_add(dlr_async_task_complete, cb_data); + } else { + dlr_device_play_pause(device, task, cb); + } + + DLEYNA_LOG_DEBUG("Exit"); +} + +void dlr_upnp_stop(dlr_upnp_t *upnp, dlr_task_t *task, + dlr_upnp_task_complete_t cb) +{ + dlr_device_t *device; + dlr_async_task_t *cb_data = (dlr_async_task_t *)task; + + DLEYNA_LOG_DEBUG("Enter"); + + device = dlr_device_from_path(task->path, upnp->server_udn_map); + + if (!device) { + cb_data->cb = cb; + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OBJECT_NOT_FOUND, + "Cannot locate a device for the specified object"); + + (void) g_idle_add(dlr_async_task_complete, cb_data); + } else { + dlr_device_stop(device, task, cb); + } + + DLEYNA_LOG_DEBUG("Exit"); +} + +void dlr_upnp_next(dlr_upnp_t *upnp, dlr_task_t *task, + dlr_upnp_task_complete_t cb) +{ + dlr_device_t *device; + dlr_async_task_t *cb_data = (dlr_async_task_t *)task; + + DLEYNA_LOG_DEBUG("Enter"); + + device = dlr_device_from_path(task->path, upnp->server_udn_map); + + if (!device) { + cb_data->cb = cb; + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OBJECT_NOT_FOUND, + "Cannot locate a device for the specified object"); + + (void) g_idle_add(dlr_async_task_complete, cb_data); + } else { + dlr_device_next(device, task, cb); + } + + DLEYNA_LOG_DEBUG("Exit"); +} + +void dlr_upnp_previous(dlr_upnp_t *upnp, dlr_task_t *task, + dlr_upnp_task_complete_t cb) +{ + dlr_device_t *device; + dlr_async_task_t *cb_data = (dlr_async_task_t *)task; + + DLEYNA_LOG_DEBUG("Enter"); + + device = dlr_device_from_path(task->path, upnp->server_udn_map); + + if (!device) { + cb_data->cb = cb; + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OBJECT_NOT_FOUND, + "Cannot locate a device for the specified object"); + + (void) g_idle_add(dlr_async_task_complete, cb_data); + } else { + dlr_device_previous(device, task, cb); + } + + DLEYNA_LOG_DEBUG("Exit"); +} + +void dlr_upnp_open_uri(dlr_upnp_t *upnp, dlr_task_t *task, + dlr_upnp_task_complete_t cb) +{ + dlr_device_t *device; + dlr_async_task_t *cb_data = (dlr_async_task_t *)task; + + DLEYNA_LOG_DEBUG("Enter"); + + device = dlr_device_from_path(task->path, upnp->server_udn_map); + + if (!device) { + cb_data->cb = cb; + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OBJECT_NOT_FOUND, + "Cannot locate a device for the specified object"); + + (void) g_idle_add(dlr_async_task_complete, cb_data); + } else { + dlr_device_open_uri(device, task, cb); + } + + DLEYNA_LOG_DEBUG("Exit"); +} + +void dlr_upnp_seek(dlr_upnp_t *upnp, dlr_task_t *task, + dlr_upnp_task_complete_t cb) +{ + dlr_device_t *device; + dlr_async_task_t *cb_data = (dlr_async_task_t *)task; + + DLEYNA_LOG_DEBUG("Enter"); + + device = dlr_device_from_path(task->path, upnp->server_udn_map); + + if (!device) { + cb_data->cb = cb; + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OBJECT_NOT_FOUND, + "Cannot locate a device for the specified object"); + + (void) g_idle_add(dlr_async_task_complete, cb_data); + } else { + dlr_device_seek(device, task, cb); + } + + DLEYNA_LOG_DEBUG("Exit"); +} + +void dlr_upnp_set_position(dlr_upnp_t *upnp, dlr_task_t *task, + dlr_upnp_task_complete_t cb) +{ + dlr_device_t *device; + dlr_async_task_t *cb_data = (dlr_async_task_t *)task; + + DLEYNA_LOG_DEBUG("Enter"); + + device = dlr_device_from_path(task->path, upnp->server_udn_map); + + if (!device) { + cb_data->cb = cb; + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OBJECT_NOT_FOUND, + "Cannot locate a device for the specified object"); + + (void) g_idle_add(dlr_async_task_complete, cb_data); + } else { + dlr_device_set_position(device, task, cb); + } + + DLEYNA_LOG_DEBUG("Exit"); +} + +void dlr_upnp_goto_track(dlr_upnp_t *upnp, dlr_task_t *task, + dlr_upnp_task_complete_t cb) +{ + dlr_device_t *device; + dlr_async_task_t *cb_data = (dlr_async_task_t *)task; + + DLEYNA_LOG_DEBUG("Enter"); + + device = dlr_device_from_path(task->path, upnp->server_udn_map); + + if (!device) { + cb_data->cb = cb; + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OBJECT_NOT_FOUND, + "Cannot locate a device for the specified object"); + + (void) g_idle_add(dlr_async_task_complete, cb_data); + } else { + dlr_device_goto_track(device, task, cb); + } + + DLEYNA_LOG_DEBUG("Exit"); +} + +void dlr_upnp_host_uri(dlr_upnp_t *upnp, dlr_task_t *task, + dlr_upnp_task_complete_t cb) +{ + dlr_device_t *device; + dlr_async_task_t *cb_data = (dlr_async_task_t *)task; + + DLEYNA_LOG_DEBUG("Enter"); + + device = dlr_device_from_path(task->path, upnp->server_udn_map); + + if (!device) { + cb_data->cb = cb; + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OBJECT_NOT_FOUND, + "Cannot locate a device for the specified object"); + + (void) g_idle_add(dlr_async_task_complete, cb_data); + } else { + dlr_device_host_uri(device, task, upnp->host_service, cb); + } + + DLEYNA_LOG_DEBUG("Exit"); +} + +void dlr_upnp_remove_uri(dlr_upnp_t *upnp, dlr_task_t *task, + dlr_upnp_task_complete_t cb) +{ + dlr_device_t *device; + dlr_async_task_t *cb_data = (dlr_async_task_t *)task; + + DLEYNA_LOG_DEBUG("Enter"); + + device = dlr_device_from_path(task->path, upnp->server_udn_map); + + if (!device) { + cb_data->cb = cb; + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OBJECT_NOT_FOUND, + "Cannot locate a device for the specified object"); + + (void) g_idle_add(dlr_async_task_complete, cb_data); + } else { + dlr_device_remove_uri(device, task, upnp->host_service, cb); + } + + DLEYNA_LOG_DEBUG("Exit"); +} + +void dlr_upnp_lost_client(dlr_upnp_t *upnp, const gchar *client_name) +{ + dlr_host_service_lost_client(upnp->host_service, client_name); +} + +void dlr_upnp_unsubscribe(dlr_upnp_t *upnp) +{ + GHashTableIter iter; + dlr_device_t *device; + + DLEYNA_LOG_DEBUG("Enter"); + + g_hash_table_iter_init(&iter, upnp->server_udn_map); + while (g_hash_table_iter_next(&iter, NULL, (gpointer *)&device)) + dlr_device_unsubscribe(device); + + DLEYNA_LOG_DEBUG("Exit"); +} diff --git a/libdleyna/renderer/upnp.h b/libdleyna/renderer/upnp.h new file mode 100644 index 0000000..9e19163 --- /dev/null +++ b/libdleyna/renderer/upnp.h @@ -0,0 +1,103 @@ +/* + * dLeyna + * + * Copyright (C) 2012-2013 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU Lesser General Public License, + * version 2.1, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Mark Ryan <mark.d.ryan@intel.com> + * + */ + +#ifndef DLR_UPNP_H__ +#define DLR_UPNP_H__ + +#include <libdleyna/core/connector.h> + +#include "server.h" +#include "task.h" + +enum dlr_interface_type_ { + DLR_INTERFACE_INFO_PROPERTIES, + DLR_INTERFACE_INFO_ROOT, + DLR_INTERFACE_INFO_PLAYER, + DLR_INTERFACE_INFO_PUSH_HOST, + DLR_INTERFACE_INFO_DEVICE, + DLR_INTERFACE_INFO_MAX +}; + +typedef void (*dlr_upnp_callback_t)(const gchar *path); +typedef void (*dlr_upnp_task_complete_t)(dlr_task_t *task, GError *error); + +dlr_upnp_t *dlr_upnp_new(dleyna_connector_id_t connection, + const dleyna_connector_dispatch_cb_t *dispatch_table, + dlr_upnp_callback_t found_server, + dlr_upnp_callback_t lost_server); + +void dlr_upnp_delete(dlr_upnp_t *upnp); + +GVariant *dlr_upnp_get_server_ids(dlr_upnp_t *upnp); + +GHashTable *dlr_upnp_get_server_udn_map(dlr_upnp_t *upnp); + +void dlr_upnp_set_prop(dlr_upnp_t *upnp, dlr_task_t *task, + dlr_upnp_task_complete_t cb); + +void dlr_upnp_get_prop(dlr_upnp_t *upnp, dlr_task_t *task, + dlr_upnp_task_complete_t cb); + +void dlr_upnp_get_all_props(dlr_upnp_t *upnp, dlr_task_t *task, + dlr_upnp_task_complete_t cb); + +void dlr_upnp_play(dlr_upnp_t *upnp, dlr_task_t *task, + dlr_upnp_task_complete_t cb); + +void dlr_upnp_pause(dlr_upnp_t *upnp, dlr_task_t *task, + dlr_upnp_task_complete_t cb); + +void dlr_upnp_play_pause(dlr_upnp_t *upnp, dlr_task_t *task, + dlr_upnp_task_complete_t cb); + +void dlr_upnp_stop(dlr_upnp_t *upnp, dlr_task_t *task, + dlr_upnp_task_complete_t cb); + +void dlr_upnp_next(dlr_upnp_t *upnp, dlr_task_t *task, + dlr_upnp_task_complete_t cb); + +void dlr_upnp_previous(dlr_upnp_t *upnp, dlr_task_t *task, + dlr_upnp_task_complete_t cb); + +void dlr_upnp_open_uri(dlr_upnp_t *upnp, dlr_task_t *task, + dlr_upnp_task_complete_t cb); + +void dlr_upnp_seek(dlr_upnp_t *upnp, dlr_task_t *task, + dlr_upnp_task_complete_t cb); + +void dlr_upnp_set_position(dlr_upnp_t *upnp, dlr_task_t *task, + dlr_upnp_task_complete_t cb); + +void dlr_upnp_goto_track(dlr_upnp_t *upnp, dlr_task_t *task, + dlr_upnp_task_complete_t cb); + +void dlr_upnp_host_uri(dlr_upnp_t *upnp, dlr_task_t *task, + dlr_upnp_task_complete_t cb); + +void dlr_upnp_remove_uri(dlr_upnp_t *upnp, dlr_task_t *task, + dlr_upnp_task_complete_t cb); + +void dlr_upnp_lost_client(dlr_upnp_t *upnp, const gchar *client_name); + +void dlr_upnp_unsubscribe(dlr_upnp_t *upnp); + +#endif /* DLR_UPNP_H__ */ |