diff options
author | Regis Merlino <regis.merlino@intel.com> | 2013-03-11 10:52:35 -0700 |
---|---|---|
committer | Regis Merlino <regis.merlino@intel.com> | 2013-03-11 10:52:35 -0700 |
commit | d929e2f2b70447e3589952d8c69688880b7b514d (patch) | |
tree | 935e700c96d309003009e236b58860461efc3918 /libdleyna/server | |
parent | 6b9249f2b95ee7e01546bce4ea6366c4e7aa25f7 (diff) | |
download | dleyna-server-d929e2f2b70447e3589952d8c69688880b7b514d.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/server')
-rw-r--r-- | libdleyna/server/Makefile.am | 64 | ||||
-rw-r--r-- | libdleyna/server/async.c | 99 | ||||
-rw-r--r-- | libdleyna/server/async.h | 113 | ||||
-rw-r--r-- | libdleyna/server/client.h | 34 | ||||
-rw-r--r-- | libdleyna/server/control-point-server.h | 30 | ||||
-rw-r--r-- | libdleyna/server/device.c | 4348 | ||||
-rw-r--r-- | libdleyna/server/device.h | 129 | ||||
-rw-r--r-- | libdleyna/server/dleyna-server-1.0.pc.in | 11 | ||||
-rw-r--r-- | libdleyna/server/dleyna-server-service.conf.in | 37 | ||||
-rw-r--r-- | libdleyna/server/interface.h | 191 | ||||
-rw-r--r-- | libdleyna/server/path.c | 154 | ||||
-rw-r--r-- | libdleyna/server/path.h | 36 | ||||
-rw-r--r-- | libdleyna/server/props.c | 1792 | ||||
-rw-r--r-- | libdleyna/server/props.h | 134 | ||||
-rw-r--r-- | libdleyna/server/search.c | 148 | ||||
-rw-r--r-- | libdleyna/server/search.h | 31 | ||||
-rw-r--r-- | libdleyna/server/server.c | 1199 | ||||
-rw-r--r-- | libdleyna/server/server.h | 47 | ||||
-rw-r--r-- | libdleyna/server/sort.c | 98 | ||||
-rw-r--r-- | libdleyna/server/sort.h | 31 | ||||
-rw-r--r-- | libdleyna/server/task.c | 618 | ||||
-rw-r--r-- | libdleyna/server/task.h | 267 | ||||
-rw-r--r-- | libdleyna/server/upnp.c | 1110 | ||||
-rw-r--r-- | libdleyna/server/upnp.h | 109 |
24 files changed, 10830 insertions, 0 deletions
diff --git a/libdleyna/server/Makefile.am b/libdleyna/server/Makefile.am new file mode 100644 index 0000000..fcdba64 --- /dev/null +++ b/libdleyna/server/Makefile.am @@ -0,0 +1,64 @@ +libdleyna_serverincdir = $(includedir)/dleyna-1.0/libdleyna/server + +DLEYNA_SERVER_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-server-1.0.la + +libdleyna_serverinc_HEADERS = control-point-server.h + +libdleyna_server_1_0_la_LDFLAGS = -version-info $(DLEYNA_SERVER_VERSION) \ + -no-undefined + +libdleyna_server_1_0_la_SOURCES = $(libdleyna_serverinc_HEADERS) \ + server.c \ + async.c \ + device.c \ + path.c \ + props.c \ + search.c \ + sort.c \ + task.c \ + upnp.c + +libdleyna_server_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-server-service.conf + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = dleyna-server-1.0.pc + +EXTRA_DIST = $(sysconf_DATA) +CLEANFILES = $(pkgconfig_DATA) dleyna-server-service.conf +DISTCLEANFILES = $(pkgconfig_DATA) dleyna-server-service.conf + +maintainer-clean-local: + rm -rf build-aux diff --git a/libdleyna/server/async.c b/libdleyna/server/async.c new file mode 100644 index 0000000..fcdbe9a --- /dev/null +++ b/libdleyna/server/async.c @@ -0,0 +1,99 @@ +/* + * 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" +#include "server.h" + +void dls_async_task_delete(dls_async_task_t *cb_data) +{ + switch (cb_data->task.type) { + case DLS_TASK_GET_CHILDREN: + case DLS_TASK_SEARCH: + if (cb_data->ut.bas.vbs) + g_ptr_array_unref(cb_data->ut.bas.vbs); + break; + case DLS_TASK_GET_ALL_PROPS: + case DLS_TASK_GET_RESOURCE: + if (cb_data->ut.get_all.vb) + g_variant_builder_unref(cb_data->ut.get_all.vb); + break; + case DLS_TASK_UPLOAD_TO_ANY: + case DLS_TASK_UPLOAD: + g_free(cb_data->ut.upload.mime_type); + break; + case DLS_TASK_UPDATE_OBJECT: + g_free(cb_data->ut.update.current_tag_value); + g_free(cb_data->ut.update.new_tag_value); + break; + case DLS_TASK_CREATE_PLAYLIST: + case DLS_TASK_CREATE_PLAYLIST_IN_ANY: + g_free(cb_data->ut.playlist.didl); + if (cb_data->ut.playlist.collection) + g_object_unref(cb_data->ut.playlist.collection); + break; + default: + break; + } + + if (cb_data->cancellable) + g_object_unref(cb_data->cancellable); +} + +gboolean dls_async_task_complete(gpointer user_data) +{ + dls_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 dls_async_task_cancelled_cb(GCancellable *cancellable, gpointer user_data) +{ + dls_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(dls_async_task_complete, cb_data); +} + +void dls_async_task_cancel(dls_async_task_t *cb_data) +{ + if (cb_data->cancellable) + g_cancellable_cancel(cb_data->cancellable); +} diff --git a/libdleyna/server/async.h b/libdleyna/server/async.h new file mode 100644 index 0000000..8efb8c5 --- /dev/null +++ b/libdleyna/server/async.h @@ -0,0 +1,113 @@ +/* + * 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 DLS_ASYNC_H__ +#define DLS_ASYNC_H__ + +#include <libgupnp/gupnp-control-point.h> +#include <libgupnp-av/gupnp-media-collection.h> + +#include <libdleyna/core/task-atom.h> + +#include "server.h" +#include "task.h" +#include "upnp.h" + +typedef struct dls_async_task_t_ dls_async_task_t; +typedef guint64 dls_upnp_prop_mask; + +typedef void (*dls_async_cb_t)(dls_async_task_t *cb_data); + +typedef struct dls_async_bas_t_ dls_async_bas_t; +struct dls_async_bas_t_ { + dls_upnp_prop_mask filter_mask; + GPtrArray *vbs; + const gchar *protocol_info; + gboolean need_child_count; + guint retrieved; + guint max_count; + dls_async_cb_t get_children_cb; +}; + +typedef struct dls_async_get_prop_t_ dls_async_get_prop_t; +struct dls_async_get_prop_t_ { + GCallback prop_func; + const gchar *protocol_info; +}; + +typedef struct dls_async_get_all_t_ dls_async_get_all_t; +struct dls_async_get_all_t_ { + GCallback prop_func; + GVariantBuilder *vb; + dls_upnp_prop_mask filter_mask; + const gchar *protocol_info; + gboolean need_child_count; + gboolean device_object; +}; + +typedef struct dls_async_upload_t_ dls_async_upload_t; +struct dls_async_upload_t_ { + const gchar *object_class; + gchar *mime_type; +}; + +typedef struct dls_async_update_t_ dls_async_update_t; +struct dls_async_update_t_ { + gchar *current_tag_value; + gchar *new_tag_value; + GHashTable *map; +}; + +typedef struct dls_async_playlist_t_ dls_async_playlist_t; +struct dls_async_playlist_t_ { + const dleyna_task_queue_key_t *queue_id; + GUPnPMediaCollection *collection; + gchar *didl; +}; + +struct dls_async_task_t_ { + dls_task_t task; /* pseudo inheritance - MUST be first field */ + dls_upnp_task_complete_t cb; + GError *error; + GUPnPServiceProxyAction *action; + GUPnPServiceProxy *proxy; + GCancellable *cancellable; + gulong cancel_id; + union { + dls_async_bas_t bas; + dls_async_get_prop_t get_prop; + dls_async_get_all_t get_all; + dls_async_upload_t upload; + dls_async_update_t update; + dls_async_playlist_t playlist; + } ut; +}; + +void dls_async_task_delete(dls_async_task_t *cb_data); + +gboolean dls_async_task_complete(gpointer user_data); + +void dls_async_task_cancelled_cb(GCancellable *cancellable, gpointer user_data); + +void dls_async_task_cancel(dls_async_task_t *cb_data); + +#endif /* DLS_ASYNC_H__ */ diff --git a/libdleyna/server/client.h b/libdleyna/server/client.h new file mode 100644 index 0000000..6d499dd --- /dev/null +++ b/libdleyna/server/client.h @@ -0,0 +1,34 @@ +/* + * 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 DLS_CLIENT_H__ +#define DLS_CLIENT_H__ + +#include <glib.h> + +typedef struct dls_client_t_ dls_client_t; +struct dls_client_t_ { + gchar *protocol_info; + gboolean prefer_local_addresses; +}; + +#endif /* DLS_CLIENT_H__ */ diff --git a/libdleyna/server/control-point-server.h b/libdleyna/server/control-point-server.h new file mode 100644 index 0000000..1f9876b --- /dev/null +++ b/libdleyna/server/control-point-server.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_SERVER_H__ +#define DLEYNA_CONTROL_POINT_SERVER_H__ + +#include <libdleyna/core/control-point.h> + +const dleyna_control_point_t *dleyna_control_point_get_server(void); + +#endif /* DLEYNA_CONTROL_POINT_SERVER_H__ */ diff --git a/libdleyna/server/device.c b/libdleyna/server/device.c new file mode 100644 index 0000000..c6b7dc4 --- /dev/null +++ b/libdleyna/server/device.c @@ -0,0 +1,4348 @@ +/* + * 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 <libgupnp/gupnp-error.h> +#include <libgupnp-dlna/gupnp-dlna-profile.h> +#include <libgupnp-dlna/gupnp-dlna-profile-guesser.h> +#include <libgupnp-av/gupnp-media-collection.h> +#include <libgupnp-av/gupnp-cds-last-change-parser.h> +#include <libsoup/soup.h> + +#include <libdleyna/core/error.h> +#include <libdleyna/core/log.h> +#include <libdleyna/core/service-task.h> + +#include "device.h" +#include "interface.h" +#include "path.h" +#include "server.h" + +#define DLS_SYSTEM_UPDATE_VAR "SystemUpdateID" +#define DLS_CONTAINER_UPDATE_VAR "ContainerUpdateIDs" +#define DLS_LAST_CHANGE_VAR "LastChange" +#define DLS_DMS_DEVICE_TYPE "urn:schemas-upnp-org:device:MediaServer:" + +#define DLS_UPLOAD_STATUS_IN_PROGRESS "IN_PROGRESS" +#define DLS_UPLOAD_STATUS_CANCELLED "CANCELLED" +#define DLS_UPLOAD_STATUS_ERROR "ERROR" +#define DLS_UPLOAD_STATUS_COMPLETED "COMPLETED" + +typedef gboolean(*dls_device_count_cb_t)(dls_async_task_t *cb_data, + gint count); + +typedef struct dls_device_count_data_t_ dls_device_count_data_t; +struct dls_device_count_data_t_ { + dls_device_count_cb_t cb; + dls_async_task_t *cb_data; +}; + +typedef struct dls_device_object_builder_t_ dls_device_object_builder_t; +struct dls_device_object_builder_t_ { + GVariantBuilder *vb; + gchar *id; + gboolean needs_child_count; +}; + +typedef struct dls_device_upload_t_ dls_device_upload_t; +struct dls_device_upload_t_ { + SoupSession *soup_session; + SoupMessage *msg; + GMappedFile *mapped_file; + gchar *body; + gsize body_length; + const gchar *status; + guint64 bytes_uploaded; + guint64 bytes_to_upload; +}; + +typedef struct dls_device_upload_job_t_ dls_device_upload_job_t; +struct dls_device_upload_job_t_ { + gint upload_id; + dls_device_t *device; + guint remove_idle; +}; + +/* Private structure used in chain task */ +typedef struct prv_new_device_ct_t_ prv_new_device_ct_t; +struct prv_new_device_ct_t_ { + dls_device_t *dev; + dleyna_connector_id_t connection; + const dleyna_connector_dispatch_cb_t *vtable; + GHashTable *property_map; +}; + +typedef struct prv_new_playlist_ct_t_ prv_new_playlist_ct_t; +struct prv_new_playlist_ct_t_ { + dls_async_task_t *cb_data; + gchar *id; + gchar *parent_id; +}; + +static void prv_get_child_count(dls_async_task_t *cb_data, + dls_device_count_cb_t cb, const gchar *id); +static void prv_retrieve_child_count_for_list(dls_async_task_t *cb_data); +static void prv_container_update_cb(GUPnPServiceProxy *proxy, + const char *variable, + GValue *value, + gpointer user_data); +static void prv_system_update_cb(GUPnPServiceProxy *proxy, + const char *variable, + GValue *value, + gpointer user_data); +static void prv_last_change_cb(GUPnPServiceProxy *proxy, + const char *variable, + GValue *value, + gpointer user_data); +static void prv_upload_delete(gpointer up); +static void prv_upload_job_delete(gpointer up); +static void prv_get_sr_token_for_props(GUPnPServiceProxy *proxy, + const dls_device_t *device, + dls_async_task_t *cb_data); + +static void prv_object_builder_delete(void *dob) +{ + dls_device_object_builder_t *builder = dob; + + if (builder) { + if (builder->vb) + g_variant_builder_unref(builder->vb); + + g_free(builder->id); + g_free(builder); + } +} + +static void prv_count_data_new(dls_async_task_t *cb_data, + dls_device_count_cb_t cb, + dls_device_count_data_t **count_data) +{ + dls_device_count_data_t *cd; + + cd = g_new(dls_device_count_data_t, 1); + cd->cb = cb; + cd->cb_data = cb_data; + + *count_data = cd; +} + +static void prv_context_unsubscribe(dls_device_context_t *ctx) +{ + if (ctx->timeout_id) { + (void) g_source_remove(ctx->timeout_id); + ctx->timeout_id = 0; + } + + if (ctx->subscribed) { + gupnp_service_proxy_remove_notify( + ctx->service_proxy, + DLS_SYSTEM_UPDATE_VAR, + prv_system_update_cb, + ctx->device); + gupnp_service_proxy_remove_notify( + ctx->service_proxy, + DLS_CONTAINER_UPDATE_VAR, + prv_container_update_cb, + ctx->device); + gupnp_service_proxy_remove_notify( + ctx->service_proxy, + DLS_LAST_CHANGE_VAR, + prv_last_change_cb, + ctx->device); + + gupnp_service_proxy_set_subscribed(ctx->service_proxy, + FALSE); + + ctx->subscribed = FALSE; + } +} + +static void prv_context_delete(gpointer context) +{ + dls_device_context_t *ctx = context; + + if (ctx) { + prv_context_unsubscribe(ctx); + + if (ctx->device_proxy) + g_object_unref(ctx->device_proxy); + + if (ctx->service_proxy) + g_object_unref(ctx->service_proxy); + + g_free(ctx->ip_address); + g_free(ctx); + } +} + +static void prv_context_new(const gchar *ip_address, + GUPnPDeviceProxy *proxy, + dls_device_t *device, + dls_device_context_t **context) +{ + const gchar *service_type = + "urn:schemas-upnp-org:service:ContentDirectory"; + dls_device_context_t *ctx = g_new(dls_device_context_t, 1); + + ctx->ip_address = g_strdup(ip_address); + ctx->device_proxy = proxy; + ctx->device = device; + g_object_ref(proxy); + ctx->service_proxy = (GUPnPServiceProxy *) + gupnp_device_info_get_service((GUPnPDeviceInfo *)proxy, + service_type); + ctx->subscribed = FALSE; + ctx->timeout_id = 0; + + *context = ctx; +} + +void dls_device_delete(void *device) +{ + dls_device_t *dev = device; + + if (dev) { + dev->shutting_down = TRUE; + g_hash_table_unref(dev->upload_jobs); + g_hash_table_unref(dev->uploads); + + if (dev->timeout_id) + (void) g_source_remove(dev->timeout_id); + + if (dev->id) + (void) dls_server_get_connector()->unpublish_subtree( + dev->connection, dev->id); + + g_ptr_array_unref(dev->contexts); + g_free(dev->path); + g_variant_unref(dev->search_caps); + g_variant_unref(dev->sort_caps); + g_variant_unref(dev->sort_ext_caps); + g_variant_unref(dev->feature_list); + g_free(dev); + } +} + +void dls_device_unsubscribe(void *device) +{ + unsigned int i; + dls_device_t *dev = device; + dls_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 void prv_last_change_decode(GUPnPCDSLastChangeEntry *entry, + GVariantBuilder *array, + const char *root_path) +{ + GUPnPCDSLastChangeEvent event; + GVariant *state; + const char *object_id; + const char *parent_id; + const char *mclass; + const char *media_class; + char *key[] = {"ADD", "DEL", "MOD", "DONE"}; + char *parent_path; + char *path = NULL; + gboolean sub_update; + guint32 update_id; + + object_id = gupnp_cds_last_change_entry_get_object_id(entry); + if (!object_id) + goto on_error; + + sub_update = gupnp_cds_last_change_entry_is_subtree_update(entry); + update_id = gupnp_cds_last_change_entry_get_update_id(entry); + path = dls_path_from_id(root_path, object_id); + event = gupnp_cds_last_change_entry_get_event(entry); + + switch (event) { + case GUPNP_CDS_LAST_CHANGE_EVENT_OBJECT_ADDED: + parent_id = gupnp_cds_last_change_entry_get_parent_id(entry); + if (!parent_id) + goto on_error; + + mclass = gupnp_cds_last_change_entry_get_class(entry); + if (!mclass) + goto on_error; + + media_class = dls_props_upnp_class_to_media_spec(mclass); + if (!media_class) + goto on_error; + + parent_path = dls_path_from_id(root_path, parent_id); + state = g_variant_new("(oubos)", path, update_id, sub_update, + parent_path, media_class); + g_free(parent_path); + break; + case GUPNP_CDS_LAST_CHANGE_EVENT_OBJECT_REMOVED: + case GUPNP_CDS_LAST_CHANGE_EVENT_OBJECT_MODIFIED: + state = g_variant_new("(oub)", path, update_id, sub_update); + break; + case GUPNP_CDS_LAST_CHANGE_EVENT_ST_DONE: + state = g_variant_new("(ou)", path, update_id); + break; + case GUPNP_CDS_LAST_CHANGE_EVENT_INVALID: + default: + goto on_error; + break; + } + + g_variant_builder_add(array, "(sv)", key[event - 1], state); + +on_error: + + g_free(path); +} + +static void prv_last_change_cb(GUPnPServiceProxy *proxy, + const char *variable, + GValue *value, + gpointer user_data) +{ + const gchar *last_change; + GVariantBuilder array; + GVariant *val; + dls_device_t *device = user_data; + GUPnPCDSLastChangeParser *parser; + GList *list; + GList *next; + GError *error = NULL; + + last_change = g_value_get_string(value); + + DLEYNA_LOG_DEBUG_NL(); + DLEYNA_LOG_DEBUG("LastChange XML: %s", last_change); + DLEYNA_LOG_DEBUG_NL(); + + parser = gupnp_cds_last_change_parser_new(); + list = gupnp_cds_last_change_parser_parse(parser, last_change, &error); + + if (error != NULL) { + DLEYNA_LOG_WARNING( + "gupnp_cds_last_change_parser_parse parsing failed: %s", + error->message); + goto on_error; + } + + g_variant_builder_init(&array, G_VARIANT_TYPE("a(sv)")); + next = list; + while (next) { + prv_last_change_decode(next->data, &array, device->path); + gupnp_cds_last_change_entry_unref(next->data); + next = g_list_next(next); + } + + val = g_variant_new("(@a(sv))", g_variant_builder_end(&array)); + + (void) dls_server_get_connector()->notify(device->connection, + device->path, + DLEYNA_SERVER_INTERFACE_MEDIA_DEVICE, + DLS_INTERFACE_ESV_LAST_CHANGE, + val, + NULL); + +on_error: + + g_list_free(list); + g_object_unref(parser); + + if (error != NULL) + g_error_free(error); +} + +static void prv_build_container_update_array(const gchar *root_path, + const gchar *value, + GVariantBuilder *builder) +{ + gchar **str_array; + int pos = 0; + gchar *path; + guint id; + + str_array = g_strsplit(value, ",", 0); + + DLEYNA_LOG_DEBUG_NL(); + + while (str_array[pos] && str_array[pos + 1]) { + path = dls_path_from_id(root_path, str_array[pos++]); + id = atoi(str_array[pos++]); + g_variant_builder_add(builder, "(ou)", path, id); + DLEYNA_LOG_DEBUG("@Id [%s] - Path [%s] - id[%d]", + str_array[pos-2], path, id); + g_free(path); + } + + DLEYNA_LOG_DEBUG_NL(); + + g_strfreev(str_array); +} + +static void prv_container_update_cb(GUPnPServiceProxy *proxy, + const char *variable, + GValue *value, + gpointer user_data) +{ + dls_device_t *device = user_data; + GVariantBuilder array; + + g_variant_builder_init(&array, G_VARIANT_TYPE("a(ou)")); + + prv_build_container_update_array(device->path, + g_value_get_string(value), + &array); + + (void) dls_server_get_connector()->notify( + device->connection, + device->path, + DLEYNA_SERVER_INTERFACE_MEDIA_DEVICE, + DLS_INTERFACE_ESV_CONTAINER_UPDATE_IDS, + g_variant_new("(@a(ou))", + g_variant_builder_end(&array)), + NULL); +} + +static void prv_system_update_cb(GUPnPServiceProxy *proxy, + const char *variable, + GValue *value, + gpointer user_data) +{ + GVariantBuilder *array; + GVariant *val; + dls_device_t *device = user_data; + guint suid = g_value_get_uint(value); + + DLEYNA_LOG_DEBUG("System Update %u", suid); + + device->system_update_id = suid; + + array = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); + g_variant_builder_add(array, "{sv}", + DLS_INTERFACE_PROP_ESV_SYSTEM_UPDATE_ID, + g_variant_new_uint32(suid)); + val = g_variant_new("(s@a{sv}as)", DLEYNA_SERVER_INTERFACE_MEDIA_DEVICE, + g_variant_builder_end(array), + NULL); + + (void) dls_server_get_connector()->notify(device->connection, + device->path, + DLS_INTERFACE_PROPERTIES, + DLS_INTERFACE_PROPERTIES_CHANGED, + val, + NULL); + + g_variant_builder_unref(array); +} + +static gboolean prv_re_enable_subscription(gpointer user_data) +{ + dls_device_context_t *context = user_data; + + context->timeout_id = 0; + + return FALSE; +} + +static void prv_subscription_lost_cb(GUPnPServiceProxy *proxy, + const GError *reason, + gpointer user_data) +{ + dls_device_context_t *context = user_data; + + if (!context->timeout_id) { + gupnp_service_proxy_set_subscribed(context->service_proxy, + TRUE); + context->timeout_id = g_timeout_add_seconds( + 10, + prv_re_enable_subscription, + context); + } else { + g_source_remove(context->timeout_id); + gupnp_service_proxy_remove_notify(context->service_proxy, + DLS_SYSTEM_UPDATE_VAR, + prv_system_update_cb, + context->device); + gupnp_service_proxy_remove_notify(context->service_proxy, + DLS_CONTAINER_UPDATE_VAR, + prv_container_update_cb, + context->device); + gupnp_service_proxy_remove_notify(context->service_proxy, + DLS_LAST_CHANGE_VAR, + prv_last_change_cb, + context->device); + + context->timeout_id = 0; + context->subscribed = FALSE; + } +} + +void dls_device_subscribe_to_contents_change(dls_device_t *device) +{ + dls_device_context_t *context; + + context = dls_device_get_context(device, NULL); + + DLEYNA_LOG_DEBUG("Subscribe for events on context: %s", + context->ip_address); + + gupnp_service_proxy_add_notify(context->service_proxy, + DLS_SYSTEM_UPDATE_VAR, + G_TYPE_UINT, + prv_system_update_cb, + device); + + gupnp_service_proxy_add_notify(context->service_proxy, + DLS_CONTAINER_UPDATE_VAR, + G_TYPE_STRING, + prv_container_update_cb, + device); + + gupnp_service_proxy_add_notify(context->service_proxy, + DLS_LAST_CHANGE_VAR, + G_TYPE_STRING, + prv_last_change_cb, + device); + + context->subscribed = TRUE; + gupnp_service_proxy_set_subscribed(context->service_proxy, TRUE); + + g_signal_connect(context->service_proxy, + "subscription-lost", + G_CALLBACK(prv_subscription_lost_cb), + context); +} + +static void prv_feature_list_add_feature(gchar *root_path, + GUPnPFeature *feature, + GVariantBuilder *vb) +{ + GVariantBuilder vbo; + GVariant *var_obj; + const char *name; + const char *version; + const char *obj_id; + gchar **obj; + gchar **saved; + gchar *path; + + name = gupnp_feature_get_name(feature); + version = gupnp_feature_get_version(feature); + obj_id = gupnp_feature_get_object_ids(feature); + + g_variant_builder_init(&vbo, G_VARIANT_TYPE("ao")); + + if (obj_id != NULL && *obj_id) { + obj = g_strsplit(obj_id, ",", 0); + saved = obj; + + while (obj && *obj) { + path = dls_path_from_id(root_path, *obj); + g_variant_builder_add(&vbo, "o", path); + g_free(path); + obj++; + } + + g_strfreev(saved); + } + + var_obj = g_variant_builder_end(&vbo); + g_variant_builder_add(vb, "(ss@ao)", name, version, var_obj); +} + +static void prv_get_feature_list_analyze(dls_device_t *device, gchar *result) +{ + GUPnPFeatureListParser *parser; + GUPnPFeature *feature; + GList *list; + GList *item; + GError *error = NULL; + GVariantBuilder vb; +#if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_DEBUG + char *str; +#endif + parser = gupnp_feature_list_parser_new(); + list = gupnp_feature_list_parser_parse_text(parser, result, &error); + + if (error != NULL) { + DLEYNA_LOG_WARNING("GetFeatureList parsing failed: %s", + error->message); + goto on_exit; + } + + g_variant_builder_init(&vb, G_VARIANT_TYPE("a(ssao)")); + item = list; + + while (item != NULL) { + feature = (GUPnPFeature *)item->data; + prv_feature_list_add_feature(device->path, feature, &vb); + g_object_unref(feature); + item = g_list_next(item); + } + + device->feature_list = g_variant_ref_sink(g_variant_builder_end(&vb)); + +#if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_DEBUG + str = g_variant_print(device->feature_list, FALSE); + DLEYNA_LOG_DEBUG("%s = %s", DLS_INTERFACE_PROP_SV_FEATURE_LIST, str); + g_free(str); +#endif + +on_exit: + g_list_free(list); + g_object_unref(parser); + + if (error != NULL) + g_error_free(error); +} + +static void prv_get_feature_list_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; + + if (!gupnp_service_proxy_end_action(proxy, action, &error, + "FeatureList", G_TYPE_STRING, + &result, NULL)) { + DLEYNA_LOG_WARNING("GetFeatureList operation failed: %s", + error->message); + goto on_error; + } + + DLEYNA_LOG_DEBUG("GetFeatureList result: %s", result); + + prv_get_feature_list_analyze(priv_t->dev, result); + +on_error: + if (error != NULL) + g_error_free(error); + + g_free(result); +} + +static GUPnPServiceProxyAction *prv_get_feature_list( + dleyna_service_task_t *task, + GUPnPServiceProxy *proxy, + gboolean *failed) +{ + *failed = FALSE; + + return gupnp_service_proxy_begin_action( + proxy, "GetFeatureList", + dleyna_service_task_begin_action_cb, + task, NULL); +} + +static void prv_get_sort_ext_capabilities_analyze(dls_device_t *device, + gchar *result) +{ + gchar **caps; + gchar **saved; +#if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_DEBUG + gchar *props; +#endif + GVariantBuilder sort_ext_caps_vb; + + g_variant_builder_init(&sort_ext_caps_vb, G_VARIANT_TYPE("as")); + + caps = g_strsplit(result, ",", 0); + saved = caps; + + while (caps && *caps) { + g_variant_builder_add(&sort_ext_caps_vb, "s", *caps); + caps++; + } + + g_strfreev(saved); + + device->sort_ext_caps = g_variant_ref_sink(g_variant_builder_end( + &sort_ext_caps_vb)); + +#if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_DEBUG + props = g_variant_print(device->sort_ext_caps, FALSE); + DLEYNA_LOG_DEBUG("%s = %s", + DLS_INTERFACE_PROP_SV_SORT_EXT_CAPABILITIES, props); + g_free(props); +#endif +} + +static void prv_get_sort_ext_capabilities_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; + + if (!gupnp_service_proxy_end_action(proxy, action, &error, + "SortExtensionCaps", + G_TYPE_STRING, &result, NULL)) { + DLEYNA_LOG_WARNING( + "GetSortExtensionCapabilities operation failed: %s", + error->message); + goto on_error; + } + + DLEYNA_LOG_DEBUG("GetSortExtensionCapabilities result: %s", result); + + prv_get_sort_ext_capabilities_analyze(priv_t->dev, result); + +on_error: + + if (error) + g_error_free(error); + + g_free(result); +} + +static GUPnPServiceProxyAction *prv_get_sort_ext_capabilities( + dleyna_service_task_t *task, + GUPnPServiceProxy *proxy, + gboolean *failed) +{ + *failed = FALSE; + + return gupnp_service_proxy_begin_action( + proxy, + "GetSortExtensionCapabilities", + dleyna_service_task_begin_action_cb, + task, NULL); +} + +static void prv_get_capabilities_analyze(GHashTable *property_map, + gchar *result, + GVariant **variant) +{ + gchar **caps; + gchar **saved; + gchar *prop_name; + GVariantBuilder caps_vb; + + g_variant_builder_init(&caps_vb, G_VARIANT_TYPE("as")); + + caps = g_strsplit(result, ",", 0); + saved = caps; + + while (caps && *caps) { + prop_name = g_hash_table_lookup(property_map, *caps); + + if (prop_name) + g_variant_builder_add(&caps_vb, "s", prop_name); + + caps++; + } + + g_strfreev(saved); + + *variant = g_variant_ref_sink(g_variant_builder_end(&caps_vb)); + +#if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_DEBUG + prop_name = g_variant_print(*variant, FALSE); + DLEYNA_LOG_DEBUG("%s = %s", " Variant", prop_name); + g_free(prop_name); +#endif +} + +static void prv_get_sort_capabilities_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; + + if (!gupnp_service_proxy_end_action(proxy, action, &error, "SortCaps", + G_TYPE_STRING, &result, NULL)) { + DLEYNA_LOG_WARNING("GetSortCapabilities operation failed: %s", + error->message); + goto on_error; + } + + DLEYNA_LOG_DEBUG("GetSortCapabilities result: %s", result); + + prv_get_capabilities_analyze(priv_t->property_map, result, + &priv_t->dev->sort_caps); + +on_error: + + if (error) + g_error_free(error); + + g_free(result); +} + +static GUPnPServiceProxyAction *prv_get_sort_capabilities( + dleyna_service_task_t *task, + GUPnPServiceProxy *proxy, + gboolean *failed) +{ + *failed = FALSE; + + return gupnp_service_proxy_begin_action( + proxy, + "GetSortCapabilities", + dleyna_service_task_begin_action_cb, + task, NULL); +} + +static void prv_get_search_capabilities_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; + + if (!gupnp_service_proxy_end_action(proxy, action, &error, "SearchCaps", + G_TYPE_STRING, &result, NULL)) { + DLEYNA_LOG_WARNING("GetSearchCapabilities operation failed: %s", + error->message); + goto on_error; + } + + DLEYNA_LOG_DEBUG("GetSearchCapabilities result: %s", result); + + prv_get_capabilities_analyze(priv_t->property_map, result, + &priv_t->dev->search_caps); + +on_error: + + if (error) + g_error_free(error); + + g_free(result); +} + +static GUPnPServiceProxyAction *prv_get_search_capabilities( + dleyna_service_task_t *task, + GUPnPServiceProxy *proxy, + gboolean *failed) +{ + *failed = FALSE; + + return gupnp_service_proxy_begin_action( + proxy, "GetSearchCapabilities", + dleyna_service_task_begin_action_cb, + task, NULL); +} + +static GUPnPServiceProxyAction *prv_subscribe(dleyna_service_task_t *task, + GUPnPServiceProxy *proxy, + gboolean *failed) +{ + dls_device_t *device; + + device = (dls_device_t *)dleyna_service_task_get_user_data(task); + dls_device_subscribe_to_contents_change(device); + + *failed = FALSE; + + return NULL; +} + +static gboolean prv_subtree_interface_filter(const gchar *object_path, + const gchar *node, + const gchar *interface) +{ + gboolean root_object = FALSE; + const gchar *slash; + gboolean retval = TRUE; + + /* All objects in the hierarchy support the same interface. Strictly + speaking this is not correct as it will allow ListChildren to be + executed on a mediaitem object. However, returning the correct + interface here would be too inefficient. We would need to either + cache the type of all objects encountered so far or issue a UPnP + request here to determine the objects type. Best to let the client + call ListChildren on a item. This will lead to an error when we + execute the UPnP command and we can return an error then. + + We do know however that the root objects are containers. Therefore + we can remove the MediaItem2 interface from the root containers. We + also know that only the root objects suport the MediaDevice + interface. + */ + + if (dls_path_get_non_root_id(object_path, &slash)) + root_object = !slash; + + if (root_object && !strcmp(interface, DLS_INTERFACE_MEDIA_ITEM)) + retval = FALSE; + else if (!root_object && !strcmp(interface, + DLEYNA_SERVER_INTERFACE_MEDIA_DEVICE)) + retval = FALSE; + + return retval; +} + +static GUPnPServiceProxyAction *prv_declare(dleyna_service_task_t *task, + GUPnPServiceProxy *proxy, + gboolean *failed) +{ + guint id; + dls_device_t *device; + prv_new_device_ct_t *priv_t; + + priv_t = (prv_new_device_ct_t *)dleyna_service_task_get_user_data(task); + device = priv_t->dev; + + id = dls_server_get_connector()->publish_subtree(priv_t->connection, + device->path, + priv_t->vtable, + DLS_INTERFACE_INFO_MAX, + prv_subtree_interface_filter); + if (id) { + device->id = id; + + device->uploads = g_hash_table_new_full( + g_int_hash, + g_int_equal, + g_free, + prv_upload_delete); + device->upload_jobs = g_hash_table_new_full( + g_int_hash, + g_int_equal, + g_free, + prv_upload_job_delete); + + } else { + DLEYNA_LOG_WARNING("dleyna_connector_publish_subtree FAILED"); + } + + *failed = (!id); + + return NULL; +} + +dls_device_t *dls_device_new( + dleyna_connector_id_t connection, + GUPnPDeviceProxy *proxy, + const gchar *ip_address, + const dleyna_connector_dispatch_cb_t *dispatch_table, + GHashTable *property_map, + guint counter, + const dleyna_task_queue_key_t *queue_id) +{ + dls_device_t *dev; + prv_new_device_ct_t *priv_t; + gchar *new_path; + dls_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(dls_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_context_delete); + dev->path = new_path; + + priv_t->dev = dev; + priv_t->connection = connection; + priv_t->vtable = dispatch_table; + priv_t->property_map = property_map; + + context = dls_device_append_new_context(dev, ip_address, proxy); + s_proxy = context->service_proxy; + + dleyna_service_task_add(queue_id, prv_get_search_capabilities, + s_proxy, + prv_get_search_capabilities_cb, NULL, priv_t); + + dleyna_service_task_add(queue_id, prv_get_sort_capabilities, + s_proxy, + prv_get_sort_capabilities_cb, NULL, priv_t); + + dleyna_service_task_add(queue_id, prv_get_sort_ext_capabilities, + s_proxy, + prv_get_sort_ext_capabilities_cb, NULL, priv_t); + + dleyna_service_task_add(queue_id, prv_get_feature_list, s_proxy, + prv_get_feature_list_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); + + return dev; +} + +dls_device_context_t *dls_device_append_new_context(dls_device_t *device, + const gchar *ip_address, + GUPnPDeviceProxy *proxy) +{ + dls_device_context_t *context; + + prv_context_new(ip_address, proxy, device, &context); + g_ptr_array_add(device->contexts, context); + + return context; +} + +dls_device_t *dls_device_from_path(const gchar *path, GHashTable *device_list) +{ + GHashTableIter iter; + gpointer value; + dls_device_t *device; + dls_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; +} + +dls_device_context_t *dls_device_get_context(const dls_device_t *device, + dls_client_t *client) +{ + dls_device_context_t *context; + unsigned int i; + const char ip4_local_prefix[] = "127.0.0."; + gboolean prefer_local; + gboolean is_local; + + prefer_local = (client && client->prefer_local_addresses); + + for (i = 0; i < device->contexts->len; ++i) { + context = g_ptr_array_index(device->contexts, i); + + is_local = (!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")); + + if (prefer_local == is_local) + break; + } + + if (i == device->contexts->len) + context = g_ptr_array_index(device->contexts, 0); + + return context; +} + +static void prv_found_child(GUPnPDIDLLiteParser *parser, + GUPnPDIDLLiteObject *object, + gpointer user_data) +{ + dls_async_task_t *cb_data = user_data; + dls_task_t *task = &cb_data->task; + dls_task_get_children_t *task_data = &task->ut.get_children; + dls_async_bas_t *cb_task_data = &cb_data->ut.bas; + dls_device_object_builder_t *builder; + gboolean have_child_count; + + DLEYNA_LOG_DEBUG("Enter"); + + builder = g_new0(dls_device_object_builder_t, 1); + + if (GUPNP_IS_DIDL_LITE_CONTAINER(object)) { + if (!task_data->containers) + goto on_error; + } else { + if (!task_data->items) + goto on_error; + } + + builder->vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); + + if (!dls_props_add_object(builder->vb, object, task->target.root_path, + task->target.path, cb_task_data->filter_mask)) + goto on_error; + + if (GUPNP_IS_DIDL_LITE_CONTAINER(object)) { + dls_props_add_container(builder->vb, + (GUPnPDIDLLiteContainer *)object, + cb_task_data->filter_mask, + &have_child_count); + + if (!have_child_count && (cb_task_data->filter_mask & + DLS_UPNP_MASK_PROP_CHILD_COUNT)) { + builder->needs_child_count = TRUE; + builder->id = g_strdup( + gupnp_didl_lite_object_get_id(object)); + cb_task_data->need_child_count = TRUE; + } + } else { + dls_props_add_item(builder->vb, object, + task->target.root_path, + cb_task_data->filter_mask, + cb_task_data->protocol_info); + } + + g_ptr_array_add(cb_task_data->vbs, builder); + + DLEYNA_LOG_DEBUG("Exit with SUCCESS"); + + return; + +on_error: + + prv_object_builder_delete(builder); + + DLEYNA_LOG_DEBUG("Exit with FAIL"); +} + +static GVariant *prv_children_result_to_variant(dls_async_task_t *cb_data) +{ + guint i; + dls_device_object_builder_t *builder; + dls_async_bas_t *cb_task_data = &cb_data->ut.bas; + GVariantBuilder vb; + + g_variant_builder_init(&vb, G_VARIANT_TYPE("aa{sv}")); + + for (i = 0; i < cb_task_data->vbs->len; ++i) { + builder = g_ptr_array_index(cb_task_data->vbs, i); + g_variant_builder_add(&vb, "@a{sv}", + g_variant_builder_end(builder->vb)); + } + + return g_variant_builder_end(&vb); +} + +static void prv_get_search_ex_result(dls_async_task_t *cb_data) +{ + GVariant *out_params[2]; + dls_async_bas_t *cb_task_data = &cb_data->ut.bas; + + out_params[0] = prv_children_result_to_variant(cb_data); + out_params[1] = g_variant_new_uint32(cb_task_data->max_count); + + cb_data->task.result = g_variant_ref_sink( + g_variant_new_tuple(out_params, 2)); +} + +static void prv_get_children_result(dls_async_task_t *cb_data) +{ + GVariant *retval = prv_children_result_to_variant(cb_data); + cb_data->task.result = g_variant_ref_sink(retval); +} + +static gboolean prv_child_count_for_list_cb(dls_async_task_t *cb_data, + gint count) +{ + dls_async_bas_t *cb_task_data = &cb_data->ut.bas; + dls_device_object_builder_t *builder; + + builder = g_ptr_array_index(cb_task_data->vbs, cb_task_data->retrieved); + dls_props_add_child_count(builder->vb, count); + cb_task_data->retrieved++; + prv_retrieve_child_count_for_list(cb_data); + + return cb_task_data->retrieved >= cb_task_data->vbs->len; +} + +static void prv_retrieve_child_count_for_list(dls_async_task_t *cb_data) +{ + dls_async_bas_t *cb_task_data = &cb_data->ut.bas; + dls_device_object_builder_t *builder; + guint i; + + for (i = cb_task_data->retrieved; i < cb_task_data->vbs->len; ++i) { + builder = g_ptr_array_index(cb_task_data->vbs, i); + + if (builder->needs_child_count) + break; + } + + cb_task_data->retrieved = i; + + if (i < cb_task_data->vbs->len) + prv_get_child_count(cb_data, prv_child_count_for_list_cb, + builder->id); + else + cb_task_data->get_children_cb(cb_data); +} + +static void prv_get_children_cb(GUPnPServiceProxy *proxy, + GUPnPServiceProxyAction *action, + gpointer user_data) +{ + gchar *result = NULL; + GUPnPDIDLLiteParser *parser = NULL; + GError *upnp_error = NULL; + dls_async_task_t *cb_data = user_data; + dls_async_bas_t *cb_task_data = &cb_data->ut.bas; + + DLEYNA_LOG_DEBUG("Enter"); + + if (!gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action, + &upnp_error, + "Result", G_TYPE_STRING, + &result, NULL)) { + DLEYNA_LOG_WARNING("Browse operation failed: %s", + upnp_error->message); + + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OPERATION_FAILED, + "Browse operation failed: %s", + upnp_error->message); + goto on_error; + } + + DLEYNA_LOG_DEBUG("GetChildren result: %s", result); + + parser = gupnp_didl_lite_parser_new(); + + g_signal_connect(parser, "object-available" , + G_CALLBACK(prv_found_child), cb_data); + + cb_task_data->vbs = g_ptr_array_new_with_free_func( + prv_object_builder_delete); + + if (!gupnp_didl_lite_parser_parse_didl(parser, result, &upnp_error) && + upnp_error->code != GUPNP_XML_ERROR_EMPTY_NODE) { + DLEYNA_LOG_WARNING("Unable to parse results of browse: %s", + upnp_error->message); + + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OPERATION_FAILED, + "Unable to parse results of browse: %s", + upnp_error->message); + goto on_error; + } + + if (cb_task_data->need_child_count) { + DLEYNA_LOG_DEBUG("Need to retrieve ChildCounts"); + + cb_task_data->get_children_cb = prv_get_children_result; + prv_retrieve_child_count_for_list(cb_data); + goto no_complete; + } else { + prv_get_children_result(cb_data); + } + +on_error: + + (void) g_idle_add(dls_async_task_complete, cb_data); + g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); + +no_complete: + + if (upnp_error) + g_error_free(upnp_error); + + if (parser) + g_object_unref(parser); + + g_free(result); + + DLEYNA_LOG_DEBUG("Exit"); +} + +void dls_device_get_children(dls_client_t *client, + dls_task_t *task, + const gchar *upnp_filter, const gchar *sort_by) +{ + dls_async_task_t *cb_data = (dls_async_task_t *)task; + dls_device_context_t *context; + + DLEYNA_LOG_DEBUG("Enter"); + + context = dls_device_get_context(task->target.device, client); + + cb_data->action = + gupnp_service_proxy_begin_action(context->service_proxy, + "Browse", + prv_get_children_cb, + cb_data, + "ObjectID", G_TYPE_STRING, + task->target.id, + + "BrowseFlag", G_TYPE_STRING, + "BrowseDirectChildren", + + "Filter", G_TYPE_STRING, + upnp_filter, + + "StartingIndex", G_TYPE_INT, + task->ut.get_children.start, + "RequestedCount", G_TYPE_INT, + task->ut.get_children.count, + "SortCriteria", G_TYPE_STRING, + sort_by, + NULL); + + cb_data->proxy = context->service_proxy; + + g_object_add_weak_pointer((G_OBJECT(context->service_proxy)), + (gpointer *)&cb_data->proxy); + + cb_data->cancel_id = g_cancellable_connect( + cb_data->cancellable, + G_CALLBACK(dls_async_task_cancelled_cb), + cb_data, NULL); + + DLEYNA_LOG_DEBUG("Exit"); +} + +static void prv_get_item(GUPnPDIDLLiteParser *parser, + GUPnPDIDLLiteObject *object, + gpointer user_data) +{ + dls_async_task_t *cb_data = user_data; + dls_async_get_all_t *cb_task_data = &cb_data->ut.get_all; + + if (!GUPNP_IS_DIDL_LITE_CONTAINER(object)) + dls_props_add_item(cb_task_data->vb, object, + cb_data->task.target.root_path, + DLS_UPNP_MASK_ALL_PROPS, + cb_task_data->protocol_info); + else + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_UNKNOWN_INTERFACE, + "Interface not supported on container."); +} + +static void prv_get_container(GUPnPDIDLLiteParser *parser, + GUPnPDIDLLiteObject *object, + gpointer user_data) +{ + dls_async_task_t *cb_data = user_data; + dls_async_get_all_t *cb_task_data = &cb_data->ut.get_all; + gboolean have_child_count; + + if (GUPNP_IS_DIDL_LITE_CONTAINER(object)) { + dls_props_add_container(cb_task_data->vb, + (GUPnPDIDLLiteContainer *)object, + DLS_UPNP_MASK_ALL_PROPS, + &have_child_count); + if (!have_child_count) + cb_task_data->need_child_count = TRUE; + } else { + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_UNKNOWN_INTERFACE, + "Interface not supported on item."); + } +} + +static void prv_get_object(GUPnPDIDLLiteParser *parser, + GUPnPDIDLLiteObject *object, + gpointer user_data) +{ + dls_async_task_t *cb_data = user_data; + dls_async_get_all_t *cb_task_data = &cb_data->ut.get_all; + const char *id; + const char *parent_path; + gchar *path = NULL; + + id = gupnp_didl_lite_object_get_parent_id(object); + + if (!id || !strcmp(id, "-1") || !strcmp(id, "")) { + parent_path = cb_data->task.target.root_path; + } else { + path = dls_path_from_id(cb_data->task.target.root_path, id); + parent_path = path; + } + + if (!dls_props_add_object(cb_task_data->vb, object, + cb_data->task.target.root_path, + parent_path, DLS_UPNP_MASK_ALL_PROPS)) + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_BAD_RESULT, + "Unable to retrieve mandatory object properties"); + g_free(path); +} + +static void prv_get_all(GUPnPDIDLLiteParser *parser, + GUPnPDIDLLiteObject *object, + gpointer user_data) +{ + dls_async_task_t *cb_data = user_data; + dls_async_get_all_t *cb_task_data = &cb_data->ut.get_all; + gboolean have_child_count; + + prv_get_object(parser, object, user_data); + + if (!cb_data->error) { + if (GUPNP_IS_DIDL_LITE_CONTAINER(object)) { + dls_props_add_container( + cb_task_data->vb, + (GUPnPDIDLLiteContainer *) + object, DLS_UPNP_MASK_ALL_PROPS, + &have_child_count); + if (!have_child_count) + cb_task_data->need_child_count = TRUE; + } else { + dls_props_add_item(cb_task_data->vb, + object, + cb_data->task.target.root_path, + DLS_UPNP_MASK_ALL_PROPS, + cb_task_data->protocol_info); + } + } +} + +static gboolean prv_subscribed(const dls_device_t *device) +{ + dls_device_context_t *context; + unsigned int i; + gboolean subscribed = FALSE; + + for (i = 0; i < device->contexts->len; ++i) { + context = g_ptr_array_index(device->contexts, i); + if (context->subscribed) { + subscribed = TRUE; + break; + } + } + + return subscribed; +} + +static void prv_system_update_id_for_prop_cb(GUPnPServiceProxy *proxy, + GUPnPServiceProxyAction *action, + gpointer user_data) +{ + GError *upnp_error = NULL; + guint id; + dls_async_task_t *cb_data = user_data; + + DLEYNA_LOG_DEBUG("Enter"); + + if (!gupnp_service_proxy_end_action(proxy, action, &upnp_error, + "Id", G_TYPE_UINT, + &id, + NULL)) { + DLEYNA_LOG_WARNING("Unable to retrieve ServiceUpdateID: %s %s", + g_quark_to_string(upnp_error->domain), + upnp_error->message); + + cb_data->error = g_error_new( + DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OPERATION_FAILED, + "Unable to retrieve ServiceUpdateID: %s", + upnp_error->message); + + goto on_complete; + } + + cb_data->task.result = g_variant_ref_sink(g_variant_new_uint32(id)); + +on_complete: + + (void) g_idle_add(dls_async_task_complete, cb_data); + g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); + + if (upnp_error) + g_error_free(upnp_error); + + DLEYNA_LOG_DEBUG("Exit"); +} + +static void prv_get_system_update_id_for_prop(GUPnPServiceProxy *proxy, + const dls_device_t *device, + dls_async_task_t *cb_data) +{ + guint suid; + + DLEYNA_LOG_DEBUG("Enter"); + + if (prv_subscribed(device)) { + suid = device->system_update_id; + + cb_data->task.result = g_variant_ref_sink( + g_variant_new_uint32(suid)); + + (void) g_idle_add(dls_async_task_complete, cb_data); + + goto on_complete; + } + + gupnp_service_proxy_begin_action(proxy, "GetSystemUpdateID", + prv_system_update_id_for_prop_cb, + cb_data, + NULL); + + cb_data->proxy = proxy; + + g_object_add_weak_pointer((G_OBJECT(proxy)), + (gpointer *)&cb_data->proxy); + + cb_data->cancel_id = g_cancellable_connect( + cb_data->cancellable, + G_CALLBACK(dls_async_task_cancelled_cb), + cb_data, NULL); + +on_complete: + + DLEYNA_LOG_DEBUG("Exit"); +} + +static void prv_system_update_id_for_props_cb(GUPnPServiceProxy *proxy, + GUPnPServiceProxyAction *action, + gpointer user_data) +{ + GError *upnp_error = NULL; + guint id; + dls_async_task_t *cb_data = user_data; + dls_async_get_all_t *cb_task_data = &cb_data->ut.get_all; + + DLEYNA_LOG_DEBUG("Enter"); + + if (!gupnp_service_proxy_end_action(proxy, action, &upnp_error, + "Id", G_TYPE_UINT, + &id, + NULL)) { + DLEYNA_LOG_WARNING("Unable to retrieve ServiceUpdateID: %s %s", + g_quark_to_string(upnp_error->domain), + upnp_error->message); + + cb_data->error = g_error_new( + DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OPERATION_FAILED, + "Unable to retrieve ServiceUpdateID: %s", + upnp_error->message); + + goto on_complete; + } + + g_variant_builder_add(cb_task_data->vb, "{sv}", + DLS_SYSTEM_UPDATE_VAR, + g_variant_new_uint32(id)); + + cb_data->task.result = g_variant_ref_sink(g_variant_builder_end( + cb_task_data->vb)); + +on_complete: + + if (!cb_data->error) + prv_get_sr_token_for_props(proxy, cb_data->task.target.device, + cb_data); + else { + (void) g_idle_add(dls_async_task_complete, cb_data); + g_cancellable_disconnect(cb_data->cancellable, + cb_data->cancel_id); + } + + if (upnp_error) + g_error_free(upnp_error); + + DLEYNA_LOG_DEBUG("Exit"); +} + +static void prv_get_system_update_id_for_props(GUPnPServiceProxy *proxy, + const dls_device_t *device, + dls_async_task_t *cb_data) +{ + dls_async_get_all_t *cb_task_data; + guint suid; + + DLEYNA_LOG_DEBUG("Enter"); + + if (prv_subscribed(device)) { + suid = device->system_update_id; + + cb_task_data = &cb_data->ut.get_all; + + g_variant_builder_add(cb_task_data->vb, "{sv}", + DLS_SYSTEM_UPDATE_VAR, + g_variant_new_uint32(suid)); + + prv_get_sr_token_for_props(proxy, device, cb_data); + + goto on_complete; + } + + gupnp_service_proxy_begin_action(proxy, "GetSystemUpdateID", + prv_system_update_id_for_props_cb, + cb_data, + NULL); + + cb_data->proxy = proxy; + + g_object_add_weak_pointer((G_OBJECT(proxy)), + (gpointer *)&cb_data->proxy); + + cb_data->cancel_id = g_cancellable_connect( + cb_data->cancellable, + G_CALLBACK(dls_async_task_cancelled_cb), + cb_data, NULL); + +on_complete: + + DLEYNA_LOG_DEBUG("Exit"); +} + +static int prv_get_media_server_version(const dls_device_t *device) +{ + dls_device_context_t *context; + const char *device_type; + const char *version; + + context = dls_device_get_context(device, NULL); + device_type = gupnp_device_info_get_device_type((GUPnPDeviceInfo *) + context->device_proxy); + + if (!g_str_has_prefix(device_type, DLS_DMS_DEVICE_TYPE)) + goto on_error; + + version = device_type + sizeof(DLS_DMS_DEVICE_TYPE) - 1; + + return atoi(version); + +on_error: + + return -1; +} + +static void prv_service_reset_for_prop_cb(GUPnPServiceProxy *proxy, + GUPnPServiceProxyAction *action, + gpointer user_data) +{ + GError *upnp_error = NULL; + gchar *token = NULL; + dls_async_task_t *cb_data = user_data; + + DLEYNA_LOG_DEBUG("Enter"); + + if (!gupnp_service_proxy_end_action(proxy, action, &upnp_error, + "ResetToken", G_TYPE_STRING, + &token, + NULL)) { + DLEYNA_LOG_WARNING( + "Unable to retrieve ServiceResetToken: %s %s", + g_quark_to_string(upnp_error->domain), + upnp_error->message); + + + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OPERATION_FAILED, + "GetServiceResetToken failed: %s", + upnp_error->message); + + goto on_complete; + } + + cb_data->task.result = g_variant_ref_sink(g_variant_new_string(token)); + + DLEYNA_LOG_DEBUG("Service Reset %s", token); + + g_free(token); + +on_complete: + + (void) g_idle_add(dls_async_task_complete, cb_data); + g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); + + if (upnp_error) + g_error_free(upnp_error); + + DLEYNA_LOG_DEBUG("Exit"); +} + +static void prv_get_sr_token_for_prop(GUPnPServiceProxy *proxy, + const dls_device_t *device, + dls_async_task_t *cb_data) +{ + DLEYNA_LOG_DEBUG("Enter"); + + if (prv_get_media_server_version(device) < 3) { + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_UNKNOWN_PROPERTY, + "Unknown property"); + + (void) g_idle_add(dls_async_task_complete, cb_data); + + goto on_error; + } + + gupnp_service_proxy_begin_action(proxy, "GetServiceResetToken", + prv_service_reset_for_prop_cb, + cb_data, + NULL); + + cb_data->proxy = proxy; + + g_object_add_weak_pointer((G_OBJECT(proxy)), + (gpointer *)&cb_data->proxy); + + cb_data->cancel_id = g_cancellable_connect( + cb_data->cancellable, + G_CALLBACK(dls_async_task_cancelled_cb), + cb_data, NULL); + +on_error: + + DLEYNA_LOG_DEBUG("Exit"); +} + +static void prv_service_reset_for_props_cb(GUPnPServiceProxy *proxy, + GUPnPServiceProxyAction *action, + gpointer user_data) +{ + GError *upnp_error = NULL; + gchar *token = NULL; + dls_async_task_t *cb_data = user_data; + dls_async_get_all_t *cb_task_data; + + DLEYNA_LOG_DEBUG("Enter"); + + if (!gupnp_service_proxy_end_action(proxy, action, &upnp_error, + "ResetToken", G_TYPE_STRING, + &token, + NULL)) { + DLEYNA_LOG_WARNING( + "Unable to retrieve ServiceResetToken: %s %s", + g_quark_to_string(upnp_error->domain), + upnp_error->message); + + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OPERATION_FAILED, + "GetServiceResetToken failed: %s", + upnp_error->message); + + goto on_complete; + } + + cb_task_data = &cb_data->ut.get_all; + g_variant_builder_add(cb_task_data->vb, "{sv}", + DLS_INTERFACE_PROP_SV_SERVICE_RESET_TOKEN, + g_variant_new_string(token)); + + cb_data->task.result = g_variant_ref_sink(g_variant_builder_end( + cb_task_data->vb)); + + DLEYNA_LOG_DEBUG("Service Reset %s", token); + + g_free(token); + +on_complete: + + (void) g_idle_add(dls_async_task_complete, cb_data); + g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); + + if (upnp_error) + g_error_free(upnp_error); + + DLEYNA_LOG_DEBUG("Exit"); +} + +static void prv_get_sr_token_for_props(GUPnPServiceProxy *proxy, + const dls_device_t *device, + dls_async_task_t *cb_data) +{ + dls_async_get_all_t *cb_task_data; + + DLEYNA_LOG_DEBUG("Enter"); + + if (prv_get_media_server_version(device) < 3) { + cb_task_data = &cb_data->ut.get_all; + + cb_data->task.result = g_variant_ref_sink(g_variant_builder_end( + cb_task_data->vb)); + + goto on_complete; /* No error here, just skip the property */ + } + + gupnp_service_proxy_begin_action(proxy, "GetServiceResetToken", + prv_service_reset_for_props_cb, + cb_data, + NULL); + + cb_data->proxy = proxy; + + g_object_add_weak_pointer((G_OBJECT(proxy)), + (gpointer *)&cb_data->proxy); + + cb_data->cancel_id = g_cancellable_connect( + cb_data->cancellable, + G_CALLBACK(dls_async_task_cancelled_cb), + cb_data, NULL); + + DLEYNA_LOG_DEBUG("Exit"); + + return; + +on_complete: + + (void) g_idle_add(dls_async_task_complete, cb_data); + + DLEYNA_LOG_DEBUG("Exit"); +} + +static gboolean prv_get_all_child_count_cb(dls_async_task_t *cb_data, + gint count) +{ + dls_async_get_all_t *cb_task_data = &cb_data->ut.get_all; + + dls_props_add_child_count(cb_task_data->vb, count); + if (cb_task_data->device_object) + prv_get_system_update_id_for_props(cb_data->proxy, + cb_data->task.target.device, + cb_data); + else + cb_data->task.result = g_variant_ref_sink(g_variant_builder_end( + cb_task_data->vb)); + + return !cb_task_data->device_object; +} + +static void prv_get_all_ms2spec_props_cb(GUPnPServiceProxy *proxy, + GUPnPServiceProxyAction *action, + gpointer user_data) +{ + GError *upnp_error = NULL; + gchar *result = NULL; + GUPnPDIDLLiteParser *parser = NULL; + dls_async_task_t *cb_data = user_data; + dls_async_get_all_t *cb_task_data = &cb_data->ut.get_all; + + DLEYNA_LOG_DEBUG("Enter"); + + if (!gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action, + &upnp_error, + "Result", G_TYPE_STRING, + &result, NULL)) { + DLEYNA_LOG_WARNING("Browse operation failed: %s", + upnp_error->message); + + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OPERATION_FAILED, + "Browse operation failed: %s", + upnp_error->message); + goto on_error; + } + + DLEYNA_LOG_DEBUG("GetMS2SpecProps result: %s", result); + + parser = gupnp_didl_lite_parser_new(); + + g_signal_connect(parser, "object-available" , cb_task_data->prop_func, + cb_data); + + if (!gupnp_didl_lite_parser_parse_didl(parser, result, &upnp_error)) { + if (upnp_error->code == GUPNP_XML_ERROR_EMPTY_NODE) { + DLEYNA_LOG_WARNING("Property not defined for object"); + + cb_data->error = + g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_UNKNOWN_PROPERTY, + "Property not defined for object"); + } else { + DLEYNA_LOG_WARNING( + "Unable to parse results of browse: %s", + upnp_error->message); + + cb_data->error = + g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OPERATION_FAILED, + "Unable to parse results of browse: %s", + upnp_error->message); + } + goto on_error; + } + + if (cb_data->error) + goto on_error; + + if (cb_task_data->need_child_count) { + DLEYNA_LOG_DEBUG("Need Child Count"); + + prv_get_child_count(cb_data, prv_get_all_child_count_cb, + cb_data->task.target.id); + + goto no_complete; + } else if (cb_data->task.type == DLS_TASK_GET_ALL_PROPS && + cb_task_data->device_object) { + prv_get_system_update_id_for_props(proxy, + cb_data->task.target.device, + cb_data); + + goto no_complete; + } else { + cb_data->task.result = g_variant_ref_sink(g_variant_builder_end( + cb_task_data->vb)); + } + +on_error: + + (void) g_idle_add(dls_async_task_complete, cb_data); + g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); + +no_complete: + + if (upnp_error) + g_error_free(upnp_error); + + if (parser) + g_object_unref(parser); + + g_free(result); + + DLEYNA_LOG_DEBUG("Exit"); +} + +static void prv_get_all_ms2spec_props(dls_device_context_t *context, + dls_async_task_t *cb_data) +{ + dls_async_get_all_t *cb_task_data = &cb_data->ut.get_all; + dls_task_t *task = &cb_data->task; + dls_task_get_props_t *task_data = &task->ut.get_props; + + DLEYNA_LOG_DEBUG("Enter called"); + + if (!strcmp(DLS_INTERFACE_MEDIA_CONTAINER, task_data->interface_name)) { + cb_task_data->prop_func = G_CALLBACK(prv_get_container); + } else if (!strcmp(DLS_INTERFACE_MEDIA_ITEM, + task_data->interface_name)) { + cb_task_data->prop_func = G_CALLBACK(prv_get_item); + } else if (!strcmp(DLS_INTERFACE_MEDIA_OBJECT, + task_data->interface_name)) { + cb_task_data->prop_func = G_CALLBACK(prv_get_object); + } else if (!strcmp("", task_data->interface_name)) { + cb_task_data->prop_func = G_CALLBACK(prv_get_all); + } else { + DLEYNA_LOG_WARNING("Interface is unknown."); + + cb_data->error = + g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_UNKNOWN_INTERFACE, + "Interface is unknown."); + goto on_error; + } + + cb_data->action = gupnp_service_proxy_begin_action( + context->service_proxy, "Browse", + prv_get_all_ms2spec_props_cb, cb_data, + "ObjectID", G_TYPE_STRING, task->target.id, + "BrowseFlag", G_TYPE_STRING, "BrowseMetadata", + "Filter", G_TYPE_STRING, "*", + "StartingIndex", G_TYPE_INT, 0, + "RequestedCount", G_TYPE_INT, 0, + "SortCriteria", G_TYPE_STRING, + "", NULL); + + cb_data->proxy = context->service_proxy; + + g_object_add_weak_pointer((G_OBJECT(context->service_proxy)), + (gpointer *)&cb_data->proxy); + + cb_data->cancel_id = g_cancellable_connect( + cb_data->cancellable, + G_CALLBACK(dls_async_task_cancelled_cb), + cb_data, NULL); + + DLEYNA_LOG_DEBUG("Exit with SUCCESS"); + + return; + +on_error: + + (void) g_idle_add(dls_async_task_complete, cb_data); + + DLEYNA_LOG_DEBUG("Exit with FAIL"); + + return; +} + +void dls_device_get_all_props(dls_client_t *client, + dls_task_t *task, + gboolean root_object) +{ + dls_async_task_t *cb_data = (dls_async_task_t *)task; + dls_async_get_all_t *cb_task_data; + dls_task_get_props_t *task_data = &task->ut.get_props; + dls_device_context_t *context; + + DLEYNA_LOG_DEBUG("Enter"); + + context = dls_device_get_context(task->target.device, client); + cb_task_data = &cb_data->ut.get_all; + + cb_task_data->vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); + cb_task_data->device_object = root_object; + + if (!strcmp(task_data->interface_name, + DLEYNA_SERVER_INTERFACE_MEDIA_DEVICE)) { + if (root_object) { + dls_props_add_device( + (GUPnPDeviceInfo *)context->device_proxy, + task->target.device, + cb_task_data->vb); + + prv_get_system_update_id_for_props( + context->service_proxy, + task->target.device, + cb_data); + } else { + cb_data->error = + g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_UNKNOWN_INTERFACE, + "Interface is only valid on root objects."); + + (void) g_idle_add(dls_async_task_complete, cb_data); + } + + } else if (strcmp(task_data->interface_name, "")) { + cb_task_data->device_object = FALSE; + prv_get_all_ms2spec_props(context, cb_data); + } else { + if (root_object) + dls_props_add_device( + (GUPnPDeviceInfo *)context->device_proxy, + task->target.device, + cb_task_data->vb); + + prv_get_all_ms2spec_props(context, cb_data); + } + + DLEYNA_LOG_DEBUG("Exit"); +} + +static void prv_get_object_property(GUPnPDIDLLiteParser *parser, + GUPnPDIDLLiteObject *object, + gpointer user_data) +{ + dls_async_task_t *cb_data = user_data; + dls_task_t *task = &cb_data->task; + dls_task_get_prop_t *task_data = &task->ut.get_prop; + + if (cb_data->task.result) + goto on_error; + + cb_data->task.result = dls_props_get_object_prop(task_data->prop_name, + task->target.root_path, + object); + +on_error: + + return; +} + +static void prv_get_item_property(GUPnPDIDLLiteParser *parser, + GUPnPDIDLLiteObject *object, + gpointer user_data) +{ + dls_async_task_t *cb_data = user_data; + dls_task_t *task = &cb_data->task; + dls_task_get_prop_t *task_data = &task->ut.get_prop; + dls_async_get_prop_t *cb_task_data = &cb_data->ut.get_prop; + + if (cb_data->task.result) + goto on_error; + + cb_data->task.result = dls_props_get_item_prop( + task_data->prop_name, + task->target.root_path, + object, + cb_task_data->protocol_info); + +on_error: + + return; +} + +static void prv_get_container_property(GUPnPDIDLLiteParser *parser, + GUPnPDIDLLiteObject *object, + gpointer user_data) +{ + dls_async_task_t *cb_data = user_data; + dls_task_t *task = &cb_data->task; + dls_task_get_prop_t *task_data = &task->ut.get_prop; + + if (cb_data->task.result) + goto on_error; + + cb_data->task.result = dls_props_get_container_prop( + task_data->prop_name, + object); + +on_error: + + return; +} + +static void prv_get_all_property(GUPnPDIDLLiteParser *parser, + GUPnPDIDLLiteObject *object, + gpointer user_data) +{ + dls_async_task_t *cb_data = user_data; + + prv_get_object_property(parser, object, user_data); + + if (cb_data->task.result) + goto on_error; + + if (GUPNP_IS_DIDL_LITE_CONTAINER(object)) + prv_get_container_property(parser, object, user_data); + else + prv_get_item_property(parser, object, user_data); + +on_error: + + return; +} + +static gboolean prv_get_child_count_cb(dls_async_task_t *cb_data, + gint count) +{ + DLEYNA_LOG_DEBUG("Enter"); + + DLEYNA_LOG_DEBUG("Count %d", count); + + cb_data->task.result = g_variant_ref_sink( + g_variant_new_uint32((guint) count)); + + DLEYNA_LOG_DEBUG("Exit"); + + return TRUE; +} + +static void prv_count_children_cb(GUPnPServiceProxy *proxy, + GUPnPServiceProxyAction *action, + gpointer user_data) +{ + dls_device_count_data_t *count_data = user_data; + dls_async_task_t *cb_data = count_data->cb_data; + GError *upnp_error = NULL; + gint count; + gboolean complete = FALSE; + + DLEYNA_LOG_DEBUG("Enter"); + + if (!gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action, + &upnp_error, + "TotalMatches", G_TYPE_INT, + &count, + NULL)) { + DLEYNA_LOG_WARNING("Browse operation failed: %s", + upnp_error->message); + + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OPERATION_FAILED, + "Browse operation failed: %s", + upnp_error->message); + goto on_error; + } + + complete = count_data->cb(cb_data, count); + +on_error: + + g_free(user_data); + + if (cb_data->error || complete) { + (void) g_idle_add(dls_async_task_complete, cb_data); + g_cancellable_disconnect(cb_data->cancellable, + cb_data->cancel_id); + } + + if (upnp_error) + g_error_free(upnp_error); + + DLEYNA_LOG_DEBUG("Exit"); +} + +static void prv_get_child_count(dls_async_task_t *cb_data, + dls_device_count_cb_t cb, const gchar *id) +{ + dls_device_count_data_t *count_data; + + DLEYNA_LOG_DEBUG("Enter"); + + prv_count_data_new(cb_data, cb, &count_data); + cb_data->action = + gupnp_service_proxy_begin_action(cb_data->proxy, + "Browse", + prv_count_children_cb, + count_data, + "ObjectID", G_TYPE_STRING, id, + + "BrowseFlag", G_TYPE_STRING, + "BrowseDirectChildren", + + "Filter", G_TYPE_STRING, "", + + "StartingIndex", G_TYPE_INT, + 0, + + "RequestedCount", G_TYPE_INT, + 1, + + "SortCriteria", G_TYPE_STRING, + "", + + NULL); + + DLEYNA_LOG_DEBUG("Exit with SUCCESS"); +} + +static void prv_get_ms2spec_prop_cb(GUPnPServiceProxy *proxy, + GUPnPServiceProxyAction *action, + gpointer user_data) +{ + GError *upnp_error = NULL; + gchar *result = NULL; + GUPnPDIDLLiteParser *parser = NULL; + dls_async_task_t *cb_data = user_data; + dls_async_get_prop_t *cb_task_data = &cb_data->ut.get_prop; + dls_task_get_prop_t *task_data = &cb_data->task.ut.get_prop; + + DLEYNA_LOG_DEBUG("Enter"); + + if (!gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action, + &upnp_error, + "Result", G_TYPE_STRING, + &result, NULL)) { + DLEYNA_LOG_WARNING("Browse operation failed: %s", + upnp_error->message); + + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OPERATION_FAILED, + "Browse operation failed: %s", + upnp_error->message); + goto on_error; + } + + DLEYNA_LOG_DEBUG("GetMS2SpecProp result: %s", result); + + parser = gupnp_didl_lite_parser_new(); + + g_signal_connect(parser, "object-available" , cb_task_data->prop_func, + cb_data); + + if (!gupnp_didl_lite_parser_parse_didl(parser, result, &upnp_error)) { + if (upnp_error->code == GUPNP_XML_ERROR_EMPTY_NODE) { + DLEYNA_LOG_WARNING("Property not defined for object"); + + cb_data->error = + g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_UNKNOWN_PROPERTY, + "Property not defined for object"); + } else { + DLEYNA_LOG_WARNING( + "Unable to parse results of browse: %s", + upnp_error->message); + + cb_data->error = + g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OPERATION_FAILED, + "Unable to parse results of browse: %s", + upnp_error->message); + } + goto on_error; + } + + if (!cb_data->task.result) { + DLEYNA_LOG_WARNING("Property not defined for object"); + + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_UNKNOWN_PROPERTY, + "Property not defined for object"); + } + +on_error: + + if (cb_data->error && !strcmp(task_data->prop_name, + DLS_INTERFACE_PROP_CHILD_COUNT)) { + DLEYNA_LOG_DEBUG("ChildCount not supported by server"); + + g_error_free(cb_data->error); + cb_data->error = NULL; + prv_get_child_count(cb_data, prv_get_child_count_cb, + cb_data->task.target.id); + } else { + (void) g_idle_add(dls_async_task_complete, cb_data); + g_cancellable_disconnect(cb_data->cancellable, + cb_data->cancel_id); + } + + if (upnp_error) + g_error_free(upnp_error); + + if (parser) + g_object_unref(parser); + + g_free(result); + + DLEYNA_LOG_DEBUG("Exit"); +} + +static void prv_get_ms2spec_prop(dls_device_context_t *context, + dls_prop_map_t *prop_map, + dls_task_get_prop_t *task_data, + dls_async_task_t *cb_data) +{ + dls_async_get_prop_t *cb_task_data; + const gchar *filter; + + DLEYNA_LOG_DEBUG("Enter"); + + cb_task_data = &cb_data->ut.get_prop; + + if (!prop_map) { + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_UNKNOWN_PROPERTY, + "Unknown property"); + goto on_error; + } + + filter = prop_map->filter ? prop_map->upnp_prop_name : ""; + + if (!strcmp(DLS_INTERFACE_MEDIA_CONTAINER, task_data->interface_name)) { + cb_task_data->prop_func = + G_CALLBACK(prv_get_container_property); + } else if (!strcmp(DLS_INTERFACE_MEDIA_ITEM, + task_data->interface_name)) { + cb_task_data->prop_func = G_CALLBACK(prv_get_item_property); + } else if (!strcmp(DLS_INTERFACE_MEDIA_OBJECT, + task_data->interface_name)) { + cb_task_data->prop_func = G_CALLBACK(prv_get_object_property); + } else if (!strcmp("", task_data->interface_name)) { + cb_task_data->prop_func = G_CALLBACK(prv_get_all_property); + } else { + DLEYNA_LOG_WARNING("Interface is unknown.%s", + task_data->interface_name); + + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_UNKNOWN_INTERFACE, + "Interface is unknown."); + goto on_error; + } + + cb_data->action = gupnp_service_proxy_begin_action( + context->service_proxy, "Browse", + prv_get_ms2spec_prop_cb, + cb_data, + "ObjectID", G_TYPE_STRING, cb_data->task.target.id, + "BrowseFlag", G_TYPE_STRING, + "BrowseMetadata", + "Filter", G_TYPE_STRING, filter, + "StartingIndex", G_TYPE_INT, 0, + "RequestedCount", G_TYPE_INT, 0, + "SortCriteria", G_TYPE_STRING, + "", + NULL); + + cb_data->proxy = context->service_proxy; + + g_object_add_weak_pointer((G_OBJECT(context->service_proxy)), + (gpointer *)&cb_data->proxy); + + cb_data->cancel_id = g_cancellable_connect( + cb_data->cancellable, + G_CALLBACK(dls_async_task_cancelled_cb), + cb_data, NULL); + + DLEYNA_LOG_DEBUG("Exit with SUCCESS"); + + return; + +on_error: + + (void) g_idle_add(dls_async_task_complete, cb_data); + + DLEYNA_LOG_DEBUG("Exit with FAIL"); + + return; +} + +void dls_device_get_prop(dls_client_t *client, + dls_task_t *task, + dls_prop_map_t *prop_map, gboolean root_object) +{ + dls_async_task_t *cb_data = (dls_async_task_t *)task; + dls_task_get_prop_t *task_data = &task->ut.get_prop; + dls_device_context_t *context; + gboolean complete = FALSE; + + DLEYNA_LOG_DEBUG("Enter"); + + context = dls_device_get_context(task->target.device, client); + + if (!strcmp(task_data->interface_name, + DLEYNA_SERVER_INTERFACE_MEDIA_DEVICE)) { + if (root_object) { + if (!strcmp( + task_data->prop_name, + DLS_INTERFACE_PROP_ESV_SYSTEM_UPDATE_ID)) { + prv_get_system_update_id_for_prop( + context->service_proxy, + task->target.device, + cb_data); + } else if (!strcmp( + task_data->prop_name, + DLS_INTERFACE_PROP_SV_SERVICE_RESET_TOKEN)) { + prv_get_sr_token_for_prop( + context->service_proxy, + task->target.device, + cb_data); + } else { + cb_data->task.result = + dls_props_get_device_prop( + (GUPnPDeviceInfo *) + context->device_proxy, + task->target.device, + task_data->prop_name); + + if (!cb_data->task.result) + cb_data->error = g_error_new( + DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_UNKNOWN_PROPERTY, + "Unknown property"); + + (void) g_idle_add(dls_async_task_complete, + cb_data); + } + + } else { + cb_data->error = + g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_UNKNOWN_INTERFACE, + "Interface is unknown."); + + (void) g_idle_add(dls_async_task_complete, cb_data); + } + + } else if (strcmp(task_data->interface_name, "")) { + prv_get_ms2spec_prop(context, prop_map, &task->ut.get_prop, + cb_data); + } else { + if (root_object) { + if (!strcmp( + task_data->prop_name, + DLS_INTERFACE_PROP_ESV_SYSTEM_UPDATE_ID)) { + prv_get_system_update_id_for_prop( + context->service_proxy, + task->target.device, + cb_data); + complete = TRUE; + } else if (!strcmp( + task_data->prop_name, + DLS_INTERFACE_PROP_SV_SERVICE_RESET_TOKEN)) { + prv_get_sr_token_for_prop( + context->service_proxy, + task->target.device, + cb_data); + complete = TRUE; + } else { + cb_data->task.result = + dls_props_get_device_prop( + (GUPnPDeviceInfo *) + context->device_proxy, + task->target.device, + task_data->prop_name); + if (cb_data->task.result) { + (void) g_idle_add( + dls_async_task_complete, + cb_data); + complete = TRUE; + } + } + } + + if (!complete) + prv_get_ms2spec_prop(context, prop_map, + &task->ut.get_prop, + cb_data); + } + + DLEYNA_LOG_DEBUG("Exit"); +} + +static void prv_found_target(GUPnPDIDLLiteParser *parser, + GUPnPDIDLLiteObject *object, + gpointer user_data) +{ + dls_async_task_t *cb_data = user_data; + dls_async_bas_t *cb_task_data = &cb_data->ut.bas; + const char *id; + const char *parent_path; + gchar *path = NULL; + gboolean have_child_count; + dls_device_object_builder_t *builder; + + DLEYNA_LOG_DEBUG("Enter"); + + builder = g_new0(dls_device_object_builder_t, 1); + + id = gupnp_didl_lite_object_get_parent_id(object); + + if (!id || !strcmp(id, "-1") || !strcmp(id, "")) { + parent_path = cb_data->task.target.root_path; + } else { + path = dls_path_from_id(cb_data->task.target.root_path, id); + parent_path = path; + } + + builder->vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); + + if (!dls_props_add_object(builder->vb, object, + cb_data->task.target.root_path, + parent_path, cb_task_data->filter_mask)) + goto on_error; + + if (GUPNP_IS_DIDL_LITE_CONTAINER(object)) { + dls_props_add_container(builder->vb, + (GUPnPDIDLLiteContainer *)object, + cb_task_data->filter_mask, + &have_child_count); + + if (!have_child_count && (cb_task_data->filter_mask & + DLS_UPNP_MASK_PROP_CHILD_COUNT)) { + builder->needs_child_count = TRUE; + builder->id = g_strdup( + gupnp_didl_lite_object_get_id(object)); + cb_task_data->need_child_count = TRUE; + } + } else { + dls_props_add_item(builder->vb, + object, + cb_data->task.target.root_path, + cb_task_data->filter_mask, + cb_task_data->protocol_info); + } + + g_ptr_array_add(cb_task_data->vbs, builder); + g_free(path); + + DLEYNA_LOG_DEBUG("Exit with SUCCESS"); + + return; + +on_error: + + g_free(path); + prv_object_builder_delete(builder); + + DLEYNA_LOG_DEBUG("Exit with FAIL"); +} + +static void prv_search_cb(GUPnPServiceProxy *proxy, + GUPnPServiceProxyAction *action, + gpointer user_data) +{ + gchar *result = NULL; + GUPnPDIDLLiteParser *parser = NULL; + GError *upnp_error = NULL; + dls_async_task_t *cb_data = user_data; + dls_async_bas_t *cb_task_data = &cb_data->ut.bas; + + DLEYNA_LOG_DEBUG("Enter"); + + if (!gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action, + &upnp_error, + "Result", G_TYPE_STRING, + &result, + "TotalMatches", G_TYPE_INT, + &cb_task_data->max_count, + NULL)) { + + DLEYNA_LOG_WARNING("Search operation failed %s", + upnp_error->message); + + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OPERATION_FAILED, + "Search operation failed: %s", + upnp_error->message); + goto on_error; + } + + parser = gupnp_didl_lite_parser_new(); + + cb_task_data->vbs = g_ptr_array_new_with_free_func( + prv_object_builder_delete); + + g_signal_connect(parser, "object-available" , + G_CALLBACK(prv_found_target), cb_data); + + DLEYNA_LOG_DEBUG("Server Search result: %s", result); + + if (!gupnp_didl_lite_parser_parse_didl(parser, result, &upnp_error) && + upnp_error->code != GUPNP_XML_ERROR_EMPTY_NODE) { + DLEYNA_LOG_WARNING("Unable to parse results of search: %s", + upnp_error->message); + + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OPERATION_FAILED, + "Unable to parse results of search: %s", + upnp_error->message); + goto on_error; + } + + if (cb_task_data->need_child_count) { + DLEYNA_LOG_DEBUG("Need to retrieve child count"); + + if (cb_data->task.multiple_retvals) + cb_task_data->get_children_cb = + prv_get_search_ex_result; + else + cb_task_data->get_children_cb = prv_get_children_result; + prv_retrieve_child_count_for_list(cb_data); + goto no_complete; + } else { + if (cb_data->task.multiple_retvals) + prv_get_search_ex_result(cb_data); + else + prv_get_children_result(cb_data); + } + +on_error: + + (void) g_idle_add(dls_async_task_complete, cb_data); + g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); + +no_complete: + + if (parser) + g_object_unref(parser); + + g_free(result); + + if (upnp_error) + g_error_free(upnp_error); + + DLEYNA_LOG_DEBUG("Exit"); +} + +void dls_device_search(dls_client_t *client, + dls_task_t *task, + const gchar *upnp_filter, const gchar *upnp_query, + const gchar *sort_by) +{ + dls_async_task_t *cb_data = (dls_async_task_t *)task; + dls_device_context_t *context; + + DLEYNA_LOG_DEBUG("Enter"); + + context = dls_device_get_context(task->target.device, client); + + cb_data->action = gupnp_service_proxy_begin_action( + context->service_proxy, "Search", + prv_search_cb, + cb_data, + "ContainerID", G_TYPE_STRING, task->target.id, + "SearchCriteria", G_TYPE_STRING, upnp_query, + "Filter", G_TYPE_STRING, upnp_filter, + "StartingIndex", G_TYPE_INT, task->ut.search.start, + "RequestedCount", G_TYPE_INT, task->ut.search.count, + "SortCriteria", G_TYPE_STRING, sort_by, + NULL); + + cb_data->proxy = context->service_proxy; + + g_object_add_weak_pointer((G_OBJECT(context->service_proxy)), + (gpointer *)&cb_data->proxy); + + cb_data->cancel_id = g_cancellable_connect( + cb_data->cancellable, + G_CALLBACK(dls_async_task_cancelled_cb), + cb_data, NULL); + + DLEYNA_LOG_DEBUG("Exit"); +} + +static void prv_get_resource(GUPnPDIDLLiteParser *parser, + GUPnPDIDLLiteObject *object, + gpointer user_data) +{ + dls_async_task_t *cb_data = user_data; + dls_task_t *task = &cb_data->task; + dls_task_get_resource_t *task_data = &task->ut.resource; + dls_async_get_all_t *cb_task_data = &cb_data->ut.get_all; + + DLEYNA_LOG_DEBUG("Enter"); + + dls_props_add_resource(cb_task_data->vb, object, + cb_task_data->filter_mask, + task_data->protocol_info); +} + +void dls_device_get_resource(dls_client_t *client, + dls_task_t *task, + const gchar *upnp_filter) +{ + dls_async_task_t *cb_data = (dls_async_task_t *)task; + dls_async_get_all_t *cb_task_data; + dls_device_context_t *context; + + context = dls_device_get_context(task->target.device, client); + cb_task_data = &cb_data->ut.get_all; + + cb_task_data->vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); + cb_task_data->prop_func = G_CALLBACK(prv_get_resource); + cb_task_data->device_object = FALSE; + + cb_data->action = gupnp_service_proxy_begin_action( + context->service_proxy, "Browse", + prv_get_all_ms2spec_props_cb, cb_data, + "ObjectID", G_TYPE_STRING, task->target.id, + "BrowseFlag", G_TYPE_STRING, "BrowseMetadata", + "Filter", G_TYPE_STRING, upnp_filter, + "StartingIndex", G_TYPE_INT, 0, + "RequestedCount", G_TYPE_INT, 0, + "SortCriteria", G_TYPE_STRING, + "", NULL); + + cb_data->proxy = context->service_proxy; + + g_object_add_weak_pointer((G_OBJECT(context->service_proxy)), + (gpointer *)&cb_data->proxy); + + cb_data->cancel_id = g_cancellable_connect( + cb_data->cancellable, + G_CALLBACK(dls_async_task_cancelled_cb), + cb_data, NULL); + + DLEYNA_LOG_DEBUG("Exit"); +} + +static gchar *prv_create_new_container_didl(const gchar *parent_id, + dls_task_t *task) +{ + GUPnPDIDLLiteWriter *writer; + GUPnPDIDLLiteObject *item; + GUPnPDIDLLiteContainer *container; + gchar *retval; + GVariantIter iter; + GVariant *child_type; + const gchar *actual_type; + + writer = gupnp_didl_lite_writer_new(NULL); + item = GUPNP_DIDL_LITE_OBJECT( + gupnp_didl_lite_writer_add_container(writer)); + container = GUPNP_DIDL_LITE_CONTAINER(item); + + gupnp_didl_lite_object_set_id(item, ""); + gupnp_didl_lite_object_set_title( + item, + task->ut.create_container.display_name); + gupnp_didl_lite_object_set_parent_id(item, parent_id); + actual_type = dls_props_media_spec_to_upnp_class( + task->ut.create_container.type); + gupnp_didl_lite_object_set_upnp_class(item, actual_type); + gupnp_didl_lite_object_set_restricted(item, FALSE); + gupnp_didl_lite_object_set_dlna_managed(item, GUPNP_OCM_FLAGS_UPLOAD); + + g_variant_iter_init(&iter, task->ut.create_container.child_types); + while ((child_type = g_variant_iter_next_value(&iter))) { + actual_type = dls_props_media_spec_to_upnp_class( + g_variant_get_string(child_type, NULL)); + if (actual_type != NULL) + gupnp_didl_lite_container_add_create_class(container, + actual_type); + g_variant_unref(child_type); + } + + retval = gupnp_didl_lite_writer_get_string(writer); + + g_object_unref(item); + g_object_unref(writer); + + return retval; +} + +static const gchar *prv_get_dlna_profile_name(const gchar *filename) +{ + gchar *uri; + GError *error = NULL; + const gchar *profile_name = NULL; + GUPnPDLNAProfile *profile; + GUPnPDLNAProfileGuesser *guesser; + gboolean relaxed_mode = TRUE; + gboolean extended_mode = TRUE; + + 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); + +on_error: + g_object_unref(guesser); + + g_free(uri); + + return profile_name; +} + +static gchar *prv_create_upload_didl(const gchar *parent_id, dls_task_t *task, + const gchar *object_class, + const gchar *mime_type) +{ + GUPnPDIDLLiteWriter *writer; + GUPnPDIDLLiteObject *item; + gchar *retval; + GUPnPProtocolInfo *protocol_info; + GUPnPDIDLLiteResource *res; + const gchar *profile; + + writer = gupnp_didl_lite_writer_new(NULL); + item = GUPNP_DIDL_LITE_OBJECT(gupnp_didl_lite_writer_add_item(writer)); + + gupnp_didl_lite_object_set_id(item, ""); + gupnp_didl_lite_object_set_title(item, task->ut.upload.display_name); + gupnp_didl_lite_object_set_parent_id(item, parent_id); + gupnp_didl_lite_object_set_upnp_class(item, object_class); + gupnp_didl_lite_object_set_restricted(item, FALSE); + + protocol_info = gupnp_protocol_info_new(); + gupnp_protocol_info_set_mime_type(protocol_info, mime_type); + gupnp_protocol_info_set_protocol(protocol_info, "*"); + gupnp_protocol_info_set_network(protocol_info, "*"); + + profile = prv_get_dlna_profile_name(task->ut.upload.file_path); + if (profile != NULL) + gupnp_protocol_info_set_dlna_profile(protocol_info, profile); + + res = gupnp_didl_lite_object_add_resource(item); + gupnp_didl_lite_resource_set_protocol_info(res, protocol_info); + + retval = gupnp_didl_lite_writer_get_string(writer); + + g_object_unref(res); + g_object_unref(protocol_info); + g_object_unref(item); + g_object_unref(writer); + + return retval; +} + +static void prv_extract_import_uri(GUPnPDIDLLiteParser *parser, + GUPnPDIDLLiteObject *object, + gpointer user_data) +{ + gchar **import_uri = user_data; + GList *resources; + GList *ptr; + GUPnPDIDLLiteResource *res; + const gchar *uri; + + if (!*import_uri) { + resources = gupnp_didl_lite_object_get_resources(object); + ptr = resources; + while (ptr) { + res = ptr->data; + if (!*import_uri) { + uri = gupnp_didl_lite_resource_get_import_uri( + res); + if (uri) + *import_uri = g_strdup(uri); + } + g_object_unref(res); + ptr = ptr->next; + } + + g_list_free(resources); + } +} + +static void prv_upload_delete_cb(GUPnPServiceProxy *proxy, + GUPnPServiceProxyAction *action, + gpointer user_data) +{ + dls_async_task_t *cb_data = user_data; + + DLEYNA_LOG_DEBUG("Enter"); + + (void) gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action, + NULL, NULL); + (void) g_idle_add(dls_async_task_complete, cb_data); + g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); + + DLEYNA_LOG_DEBUG("Exit"); +} + +static void prv_upload_job_delete(gpointer up_job) +{ + dls_device_upload_job_t *upload_job = up_job; + + if (up_job) { + if (upload_job->remove_idle) + (void) g_source_remove(upload_job->remove_idle); + + g_free(upload_job); + } +} + +static gboolean prv_remove_update_job(gpointer user_data) +{ + dls_device_upload_job_t *upload_job = user_data; + dls_device_upload_t *upload; + + upload = g_hash_table_lookup(upload_job->device->uploads, + &upload_job->upload_id); + if (upload) { + g_hash_table_remove(upload_job->device->uploads, + &upload_job->upload_id); + + DLEYNA_LOG_DEBUG("Removing Upload Object: %d", + upload_job->upload_id); + } + + upload_job->remove_idle = 0; + g_hash_table_remove(upload_job->device->upload_jobs, + &upload_job->upload_id); + + return FALSE; +} + +static void prv_generate_upload_update(dls_device_upload_job_t *upload_job, + dls_device_upload_t *upload) +{ + GVariant *args; + + args = g_variant_new("(ustt)", upload_job->upload_id, upload->status, + upload->bytes_uploaded, upload->bytes_to_upload); + + DLEYNA_LOG_DEBUG( + "Emitting: %s (%u %s %"G_GUINT64_FORMAT" %"G_GUINT64_FORMAT")" + " on %s", + DLS_INTERFACE_UPLOAD_UPDATE, upload_job->upload_id, + upload->status, upload->bytes_uploaded, + upload->bytes_to_upload, upload_job->device->path); + + (void) dls_server_get_connector()->notify( + upload_job->device->connection, + upload_job->device->path, + DLEYNA_SERVER_INTERFACE_MEDIA_DEVICE, + DLS_INTERFACE_UPLOAD_UPDATE, + args, + NULL); +} + +static void prv_post_finished(SoupSession *session, SoupMessage *msg, + gpointer user_data) +{ + dls_device_upload_job_t *upload_job = user_data; + dls_device_upload_t *upload; + gint *upload_id; + + DLEYNA_LOG_DEBUG("Enter"); + + DLEYNA_LOG_DEBUG("Upload %u finished. Code %u Message %s", + upload_job->upload_id, msg->status_code, + msg->reason_phrase); + + /* This is clumsy but we need to distinguish between two cases: + 1. We cancel because the process is exitting. + 2. We cancel because a client has called CancelUpload. + + We could use custom SOUP error messages to distinguish the cases + but device->shutting_down seemed less hacky. + + We need this check as if we are shutting down it is + dangerous to manipulate uploads as we are being called from its + destructor. + */ + + if (upload_job->device->shutting_down) { + DLEYNA_LOG_DEBUG("Device shutting down. Cancelling Upload"); + goto on_error; + } + + upload = g_hash_table_lookup(upload_job->device->uploads, + &upload_job->upload_id); + if (upload) { + upload_job->remove_idle = + g_timeout_add(30000, prv_remove_update_job, user_data); + + if (SOUP_STATUS_IS_SUCCESSFUL(msg->status_code)) { + upload->status = DLS_UPLOAD_STATUS_COMPLETED; + upload->bytes_uploaded = upload->bytes_to_upload; + } else if (msg->status_code == SOUP_STATUS_CANCELLED) { + upload->status = DLS_UPLOAD_STATUS_CANCELLED; + } else { + upload->status = DLS_UPLOAD_STATUS_ERROR; + } + + DLEYNA_LOG_DEBUG("Upload Status: %s", upload->status); + + prv_generate_upload_update(upload_job, upload); + + g_object_unref(upload->msg); + upload->msg = NULL; + + g_object_unref(upload->soup_session); + upload->soup_session = NULL; + + g_mapped_file_unref(upload->mapped_file); + upload->mapped_file = NULL; + + g_free(upload->body); + upload->body = NULL; + + upload_id = g_new(gint, 1); + *upload_id = upload_job->upload_id; + + g_hash_table_insert(upload_job->device->upload_jobs, upload_id, + upload_job); + + upload_job = NULL; + } + +on_error: + + prv_upload_job_delete(upload_job); + + DLEYNA_LOG_DEBUG("Exit"); +} + +static void prv_upload_delete(gpointer up) +{ + dls_device_upload_t *upload = up; + + DLEYNA_LOG_DEBUG("Enter"); + + if (upload) { + if (upload->msg) { + soup_session_cancel_message(upload->soup_session, + upload->msg, + SOUP_STATUS_CANCELLED); + g_object_unref(upload->msg); + } + + if (upload->soup_session) + g_object_unref(upload->soup_session); + + if (upload->mapped_file) + g_mapped_file_unref(upload->mapped_file); + else if (upload->body) + g_free(upload->body); + + g_free(upload); + } + + DLEYNA_LOG_DEBUG("Exit"); +} + +static void prv_post_bytes_written(SoupMessage *msg, SoupBuffer *chunk, + gpointer user_data) +{ + dls_device_upload_t *upload = user_data; + + upload->bytes_uploaded += chunk->length; + if (upload->bytes_uploaded > upload->bytes_to_upload) + upload->bytes_uploaded = upload->bytes_to_upload; +} + +static dls_device_upload_t *prv_upload_data_new(const gchar *file_path, + gchar *body, + gsize body_length, + const gchar *import_uri, + const gchar *mime_type, + GError **error) +{ + dls_device_upload_t *upload = NULL; + GMappedFile *mapped_file = NULL; + gchar *up_body = body; + gsize up_body_length = body_length; + + DLEYNA_LOG_DEBUG("Enter"); + + if (file_path) { + mapped_file = g_mapped_file_new(file_path, FALSE, NULL); + if (!mapped_file) { + DLEYNA_LOG_WARNING("Unable to map %s into memory", + file_path); + + *error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_IO, + "Unable to map %s into memory", + file_path); + goto on_error; + } + + up_body = g_mapped_file_get_contents(mapped_file); + up_body_length = g_mapped_file_get_length(mapped_file); + } + + upload = g_new0(dls_device_upload_t, 1); + + upload->soup_session = soup_session_async_new(); + upload->msg = soup_message_new("POST", import_uri); + upload->mapped_file = mapped_file; + upload->body = body; + upload->body_length = body_length; + + if (!upload->msg) { + DLEYNA_LOG_WARNING("Invalid URI %s", import_uri); + + *error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_BAD_RESULT, + "Invalid URI %s", import_uri); + goto on_error; + } + + upload->status = DLS_UPLOAD_STATUS_IN_PROGRESS; + upload->bytes_to_upload = up_body_length; + + soup_message_headers_set_expectations(upload->msg->request_headers, + SOUP_EXPECTATION_CONTINUE); + + soup_message_set_request(upload->msg, mime_type, SOUP_MEMORY_STATIC, + up_body, up_body_length); + g_signal_connect(upload->msg, "wrote-body-data", + G_CALLBACK(prv_post_bytes_written), upload); + + DLEYNA_LOG_DEBUG("Exit with Success"); + + return upload; + +on_error: + + prv_upload_delete(upload); + + DLEYNA_LOG_WARNING("Exit with Fail"); + + return NULL; +} + +static void prv_create_container_cb(GUPnPServiceProxy *proxy, + GUPnPServiceProxyAction *action, + gpointer user_data) +{ + dls_async_task_t *cb_data = user_data; + GError *upnp_error = NULL; + gchar *result = NULL; + gchar *object_id = NULL; + gchar *object_path; + + DLEYNA_LOG_DEBUG("Enter"); + + if (!gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action, + &upnp_error, + "ObjectID", G_TYPE_STRING, + &object_id, + "Result", G_TYPE_STRING, + &result, + NULL)) { + DLEYNA_LOG_WARNING("Create Object operation failed: %s", + upnp_error->message); + + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OPERATION_FAILED, + "Create Object operation failed: %s", + upnp_error->message); + goto on_error; + } + + object_path = dls_path_from_id(cb_data->task.target.root_path, + object_id); + cb_data->task.result = g_variant_ref_sink(g_variant_new_object_path( + object_path)); + g_free(object_path); + +on_error: + + (void) g_idle_add(dls_async_task_complete, cb_data); + g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); + + if (object_id) + g_free(object_id); + + if (result) + g_free(result); + + if (upnp_error) + g_error_free(upnp_error); + + DLEYNA_LOG_DEBUG("Exit"); +} + +static void prv_generic_upload_cb(dls_async_task_t *cb_data, + char *file_path, + gchar *body, + gsize body_length, + const gchar *mime_type) +{ + gchar *object_id = NULL; + gchar *result = NULL; + gchar *import_uri = NULL; + gchar *object_path; + GError *error = NULL; + gboolean delete_needed = FALSE; + gint *upload_id; + GUPnPDIDLLiteParser *parser = NULL; + GVariant *out_p[2]; + dls_device_upload_t *upload; + dls_device_upload_job_t *upload_job; + + DLEYNA_LOG_DEBUG("Enter"); + + if (!gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action, + &error, + "ObjectID", G_TYPE_STRING, + &object_id, + "Result", G_TYPE_STRING, + &result, + NULL)) { + DLEYNA_LOG_WARNING("Create Object operation failed: %s", + error->message); + + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OPERATION_FAILED, + "Create Object operation " + " failed: %s", + error->message); + goto on_error; + } + + DLEYNA_LOG_DEBUG_NL(); + DLEYNA_LOG_DEBUG("Create Object Result: %s", result); + DLEYNA_LOG_DEBUG_NL(); + + delete_needed = TRUE; + + parser = gupnp_didl_lite_parser_new(); + + g_signal_connect(parser, "object-available" , + G_CALLBACK(prv_extract_import_uri), &import_uri); + + if (!gupnp_didl_lite_parser_parse_didl(parser, result, &error) && + error->code != GUPNP_XML_ERROR_EMPTY_NODE) { + + DLEYNA_LOG_WARNING( + "Unable to parse results of CreateObject: %s", + error->message); + + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OPERATION_FAILED, + "Unable to parse results of CreateObject: %s", + error->message); + goto on_error; + } + + if (!import_uri) { + DLEYNA_LOG_WARNING("Missing Import URI"); + + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OPERATION_FAILED, + "Missing Import URI"); + goto on_error; + } + + DLEYNA_LOG_DEBUG("Import URI %s", import_uri); + + upload = prv_upload_data_new(file_path, body, body_length, + import_uri, mime_type, &cb_data->error); + + if (!upload) + goto on_error; + + upload_job = g_new0(dls_device_upload_job_t, 1); + upload_job->device = cb_data->task.target.device; + upload_job->upload_id = (gint) cb_data->task.target.device->upload_id; + + soup_session_queue_message(upload->soup_session, upload->msg, + prv_post_finished, upload_job); + g_object_ref(upload->msg); + + upload_id = g_new(gint, 1); + *upload_id = upload_job->upload_id; + g_hash_table_insert(cb_data->task.target.device->uploads, upload_id, + upload); + + object_path = dls_path_from_id(cb_data->task.target.root_path, + object_id); + + DLEYNA_LOG_DEBUG("Upload ID %u", *upload_id); + DLEYNA_LOG_DEBUG("Object ID %s", object_id); + DLEYNA_LOG_DEBUG("Object Path %s", object_path); + + out_p[0] = g_variant_new_uint32(*upload_id); + out_p[1] = g_variant_new_object_path(object_path); + cb_data->task.result = g_variant_ref_sink(g_variant_new_tuple(out_p, + 2)); + + ++cb_data->task.target.device->upload_id; + if (cb_data->task.target.device->upload_id > G_MAXINT) + cb_data->task.target.device->upload_id = 0; + + g_free(object_path); + +on_error: + + if (cb_data->error && delete_needed) { + DLEYNA_LOG_WARNING( + "Upload failed deleting created object with id %s", + object_id); + + cb_data->action = gupnp_service_proxy_begin_action( + cb_data->proxy, "DestroyObject", + prv_upload_delete_cb, cb_data, + "ObjectID", G_TYPE_STRING, object_id, + NULL); + } else { + (void) g_idle_add(dls_async_task_complete, cb_data); + g_cancellable_disconnect(cb_data->cancellable, + cb_data->cancel_id); + } + + g_free(object_id); + g_free(import_uri); + + if (parser) + g_object_unref(parser); + + g_free(result); + + if (error) + g_error_free(error); + + DLEYNA_LOG_DEBUG("Exit"); +} + +static void prv_create_object_upload_cb(GUPnPServiceProxy *proxy, + GUPnPServiceProxyAction *action, + gpointer user_data) +{ + dls_async_task_t *cb_data = user_data; + + prv_generic_upload_cb(cb_data, + cb_data->task.ut.upload.file_path, + NULL, 0, + cb_data->ut.upload.mime_type); +} + +void dls_device_upload(dls_client_t *client, + dls_task_t *task, const gchar *parent_id) +{ + dls_async_task_t *cb_data = (dls_async_task_t *)task; + dls_device_context_t *context; + gchar *didl; + dls_async_upload_t *cb_task_data; + + DLEYNA_LOG_DEBUG("Enter"); + DLEYNA_LOG_DEBUG("Uploading file to %s", parent_id); + + context = dls_device_get_context(task->target.device, client); + cb_task_data = &cb_data->ut.upload; + + didl = prv_create_upload_didl(parent_id, task, + cb_task_data->object_class, + cb_task_data->mime_type); + + DLEYNA_LOG_DEBUG_NL(); + DLEYNA_LOG_DEBUG("DIDL: %s", didl); + DLEYNA_LOG_DEBUG_NL(); + + cb_data->action = gupnp_service_proxy_begin_action( + context->service_proxy, "CreateObject", + prv_create_object_upload_cb, cb_data, + "ContainerID", G_TYPE_STRING, parent_id, + "Elements", G_TYPE_STRING, didl, + NULL); + + cb_data->proxy = context->service_proxy; + + g_object_add_weak_pointer((G_OBJECT(context->service_proxy)), + (gpointer *)&cb_data->proxy); + + cb_data->cancel_id = g_cancellable_connect( + cb_data->cancellable, + G_CALLBACK(dls_async_task_cancelled_cb), + cb_data, NULL); + + g_free(didl); + + DLEYNA_LOG_DEBUG("Exit"); +} + +gboolean dls_device_get_upload_status(dls_task_t *task, GError **error) +{ + dls_device_upload_t *upload; + gboolean retval = FALSE; + GVariant *out_params[3]; + guint upload_id; + + DLEYNA_LOG_DEBUG("Enter"); + + upload_id = task->ut.upload_action.upload_id; + + upload = g_hash_table_lookup(task->target.device->uploads, &upload_id); + if (!upload) { + *error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OBJECT_NOT_FOUND, + "Unknown Upload ID %u ", upload_id); + goto on_error; + } + + out_params[0] = g_variant_new_string(upload->status); + out_params[1] = g_variant_new_uint64(upload->bytes_uploaded); + out_params[2] = g_variant_new_uint64(upload->bytes_to_upload); + + DLEYNA_LOG_DEBUG( + "Upload ( %s %"G_GUINT64_FORMAT" %"G_GUINT64_FORMAT" )", + upload->status, upload->bytes_uploaded, + upload->bytes_to_upload); + + task->result = g_variant_ref_sink(g_variant_new_tuple(out_params, 3)); + + retval = TRUE; + +on_error: + + DLEYNA_LOG_DEBUG("Exit"); + + return retval; +} + +void dls_device_get_upload_ids(dls_task_t *task) +{ + GVariantBuilder vb; + GHashTableIter iter; + gpointer key; + + DLEYNA_LOG_DEBUG("Enter"); + + g_variant_builder_init(&vb, G_VARIANT_TYPE("au")); + + g_hash_table_iter_init(&iter, task->target.device->uploads); + while (g_hash_table_iter_next(&iter, &key, NULL)) + g_variant_builder_add(&vb, "u", (guint32) (*((gint *)key))); + + task->result = g_variant_ref_sink(g_variant_builder_end(&vb)); + + DLEYNA_LOG_DEBUG("Exit"); +} + +gboolean dls_device_cancel_upload(dls_task_t *task, GError **error) +{ + dls_device_upload_t *upload; + gboolean retval = FALSE; + guint upload_id; + + DLEYNA_LOG_DEBUG("Enter"); + + upload_id = task->ut.upload_action.upload_id; + + upload = g_hash_table_lookup(task->target.device->uploads, &upload_id); + if (!upload) { + *error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OBJECT_NOT_FOUND, + "Unknown Upload ID %u ", upload_id); + goto on_error; + } + + if (upload->msg) { + soup_session_cancel_message(upload->soup_session, upload->msg, + SOUP_STATUS_CANCELLED); + DLEYNA_LOG_DEBUG("Cancelling Upload %u ", upload_id); + } + + retval = TRUE; + +on_error: + + DLEYNA_LOG_DEBUG("Exit"); + + return retval; +} + +static void prv_destroy_object_cb(GUPnPServiceProxy *proxy, + GUPnPServiceProxyAction *action, + gpointer user_data) +{ + GError *upnp_error = NULL; + dls_async_task_t *cb_data = user_data; + + DLEYNA_LOG_DEBUG("Enter"); + + if (!gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action, + &upnp_error, + NULL)) { + DLEYNA_LOG_WARNING("Destroy Object operation failed: %s", + upnp_error->message); + + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OPERATION_FAILED, + "Destroy Object operation failed: %s", + upnp_error->message); + } + + (void) g_idle_add(dls_async_task_complete, cb_data); + g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); + + if (upnp_error) + g_error_free(upnp_error); + + DLEYNA_LOG_DEBUG("Exit"); +} + +void dls_device_delete_object(dls_client_t *client, + dls_task_t *task) +{ + dls_async_task_t *cb_data = (dls_async_task_t *)task; + dls_device_context_t *context; + + DLEYNA_LOG_DEBUG("Enter"); + + context = dls_device_get_context(task->target.device, client); + + cb_data->action = gupnp_service_proxy_begin_action( + context->service_proxy, "DestroyObject", + prv_destroy_object_cb, cb_data, + "ObjectID", G_TYPE_STRING, task->target.id, + NULL); + + cb_data->proxy = context->service_proxy; + + g_object_add_weak_pointer((G_OBJECT(context->service_proxy)), + (gpointer *)&cb_data->proxy); + + cb_data->cancel_id = g_cancellable_connect(cb_data->cancellable, + G_CALLBACK(dls_async_task_cancelled_cb), + cb_data, NULL); + + DLEYNA_LOG_DEBUG("Exit"); +} + +void dls_device_create_container(dls_client_t *client, + dls_task_t *task, + const gchar *parent_id) +{ + dls_async_task_t *cb_data = (dls_async_task_t *)task; + dls_device_context_t *context; + gchar *didl; + + DLEYNA_LOG_DEBUG("Enter"); + + context = dls_device_get_context(task->target.device, client); + + didl = prv_create_new_container_didl(parent_id, task); + + DLEYNA_LOG_DEBUG("DIDL: %s", didl); + + cb_data->action = gupnp_service_proxy_begin_action( + context->service_proxy, "CreateObject", + prv_create_container_cb, cb_data, + "ContainerID", G_TYPE_STRING, parent_id, + "Elements", G_TYPE_STRING, didl, + NULL); + + cb_data->proxy = context->service_proxy; + + g_object_add_weak_pointer((G_OBJECT(context->service_proxy)), + (gpointer *)&cb_data->proxy); + + cb_data->cancel_id = g_cancellable_connect( + cb_data->cancellable, + G_CALLBACK(dls_async_task_cancelled_cb), + cb_data, NULL); + + g_free(didl); + + DLEYNA_LOG_DEBUG("Exit"); +} + +static void prv_update_object_update_cb(GUPnPServiceProxy *proxy, + GUPnPServiceProxyAction *action, + gpointer user_data) +{ + GError *upnp_error = NULL; + dls_async_task_t *cb_data = user_data; + + DLEYNA_LOG_DEBUG("Enter"); + + if (!gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action, + &upnp_error, + NULL)) { + DLEYNA_LOG_WARNING("Update Object operation failed: %s", + upnp_error->message); + + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OPERATION_FAILED, + "Update Object operation " + " failed: %s", + upnp_error->message); + } + + (void) g_idle_add(dls_async_task_complete, cb_data); + g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); + + if (upnp_error) + g_error_free(upnp_error); + + DLEYNA_LOG_DEBUG("Exit"); +} + +static gchar *prv_get_current_xml_fragment(GUPnPDIDLLiteObject *object, + dls_upnp_prop_mask mask) +{ + gchar *retval = NULL; + + if (mask & DLS_UPNP_MASK_PROP_DISPLAY_NAME) + retval = gupnp_didl_lite_object_get_title_xml_string(object); + else if (mask & DLS_UPNP_MASK_PROP_ALBUM) + retval = gupnp_didl_lite_object_get_album_xml_string(object); + else if (mask & DLS_UPNP_MASK_PROP_DATE) + retval = gupnp_didl_lite_object_get_date_xml_string(object); + else if (mask & DLS_UPNP_MASK_PROP_TYPE) + retval = gupnp_didl_lite_object_get_upnp_class_xml_string( + object); + else if (mask & DLS_UPNP_MASK_PROP_TRACK_NUMBER) + retval = gupnp_didl_lite_object_get_track_number_xml_string( + object); + else if (mask & DLS_UPNP_MASK_PROP_ARTISTS) + retval = gupnp_didl_lite_object_get_artists_xml_string(object); + + return retval; +} + +static gchar *prv_get_new_xml_fragment(GUPnPDIDLLiteObject *object, + dls_upnp_prop_mask mask, + GVariant *value) +{ + GUPnPDIDLLiteContributor *artist; + const gchar *artist_name; + const gchar *upnp_class; + GVariantIter viter; + gchar *retval = NULL; + + if (mask & DLS_UPNP_MASK_PROP_DISPLAY_NAME) { + gupnp_didl_lite_object_set_title( + object, + g_variant_get_string(value, NULL)); + + retval = gupnp_didl_lite_object_get_title_xml_string(object); + } else if (mask & DLS_UPNP_MASK_PROP_ALBUM) { + gupnp_didl_lite_object_set_album( + object, + g_variant_get_string(value, NULL)); + + retval = gupnp_didl_lite_object_get_album_xml_string(object); + } else if (mask & DLS_UPNP_MASK_PROP_DATE) { + gupnp_didl_lite_object_set_date( + object, + g_variant_get_string(value, NULL)); + + retval = gupnp_didl_lite_object_get_date_xml_string(object); + } else if (mask & DLS_UPNP_MASK_PROP_TYPE) { + upnp_class = dls_props_media_spec_to_upnp_class( + g_variant_get_string(value, NULL)); + + gupnp_didl_lite_object_set_upnp_class(object, upnp_class); + + retval = gupnp_didl_lite_object_get_upnp_class_xml_string( + object); + } else if (mask & DLS_UPNP_MASK_PROP_TRACK_NUMBER) { + gupnp_didl_lite_object_set_track_number( + object, + g_variant_get_int32(value)); + + retval = gupnp_didl_lite_object_get_track_number_xml_string( + object); + } else if (mask & DLS_UPNP_MASK_PROP_ARTISTS) { + gupnp_didl_lite_object_unset_artists(object); + + (void) g_variant_iter_init(&viter, value); + + while (g_variant_iter_next(&viter, "&s", &artist_name)) { + artist = gupnp_didl_lite_object_add_artist(object); + + gupnp_didl_lite_contributor_set_name(artist, + artist_name); + } + + retval = gupnp_didl_lite_object_get_artists_xml_string(object); + } + + return retval; +} + +static void prv_get_xml_fragments(GUPnPDIDLLiteParser *parser, + GUPnPDIDLLiteObject *object, + gpointer user_data) +{ + GString *current_str; + GString *new_str; + gchar *frag1; + gchar *frag2; + GVariantIter viter; + const gchar *prop; + GVariant *value; + dls_prop_map_t *prop_map; + GUPnPDIDLLiteWriter *writer; + GUPnPDIDLLiteObject *scratch_object; + gboolean first = TRUE; + dls_async_task_t *cb_data = user_data; + dls_async_update_t *cb_task_data = &cb_data->ut.update; + dls_task_t *task = &cb_data->task; + dls_task_update_t *task_data = &task->ut.update; + + DLEYNA_LOG_DEBUG("Enter"); + + current_str = g_string_new(""); + new_str = g_string_new(""); + + writer = gupnp_didl_lite_writer_new(NULL); + if (GUPNP_IS_DIDL_LITE_CONTAINER(object)) + scratch_object = GUPNP_DIDL_LITE_OBJECT( + gupnp_didl_lite_writer_add_container(writer)); + else + scratch_object = GUPNP_DIDL_LITE_OBJECT( + gupnp_didl_lite_writer_add_item(writer)); + + (void) g_variant_iter_init(&viter, task_data->to_add_update); + + while (g_variant_iter_next(&viter, "{&sv}", &prop, &value)) { + + DLEYNA_LOG_DEBUG("to_add_update = %s", prop); + + prop_map = g_hash_table_lookup(cb_task_data->map, prop); + + frag1 = prv_get_current_xml_fragment(object, prop_map->type); + frag2 = prv_get_new_xml_fragment(scratch_object, prop_map->type, + value); + + if (!frag2) { + DLEYNA_LOG_DEBUG("Unable to set %s. Skipping", prop); + + g_free(frag1); + continue; + } + + if (!first) { + g_string_append(current_str, ","); + g_string_append(new_str, ","); + } else { + first = FALSE; + } + + if (frag1) { + g_string_append(current_str, (const gchar *)frag1); + g_free(frag1); + } + + g_string_append(new_str, (const gchar *)frag2); + g_free(frag2); + } + + (void) g_variant_iter_init(&viter, task_data->to_delete); + + while (g_variant_iter_next(&viter, "&s", &prop)) { + DLEYNA_LOG_DEBUG("to_delete = %s", prop); + + prop_map = g_hash_table_lookup(cb_task_data->map, prop); + + frag1 = prv_get_current_xml_fragment(object, prop_map->type); + if (!frag1) + continue; + + if (!first) + g_string_append(current_str, ","); + else + first = FALSE; + + g_string_append(current_str, (const gchar *)frag1); + g_free(frag1); + } + + cb_task_data->current_tag_value = g_string_free(current_str, FALSE); + DLEYNA_LOG_DEBUG("current_tag_value = %s", + cb_task_data->current_tag_value); + + cb_task_data->new_tag_value = g_string_free(new_str, FALSE); + DLEYNA_LOG_DEBUG("new_tag_value = %s", cb_task_data->new_tag_value); + + g_object_unref(scratch_object); + g_object_unref(writer); + + DLEYNA_LOG_DEBUG("Exit"); +} + +static void prv_update_object_browse_cb(GUPnPServiceProxy *proxy, + GUPnPServiceProxyAction *action, + gpointer user_data) +{ + GError *upnp_error = NULL; + dls_async_task_t *cb_data = user_data; + dls_async_update_t *cb_task_data = &cb_data->ut.update; + GUPnPDIDLLiteParser *parser = NULL; + gchar *result = NULL; + + DLEYNA_LOG_DEBUG("Enter"); + + if (!gupnp_service_proxy_end_action(cb_data->proxy, cb_data->action, + &upnp_error, + "Result", G_TYPE_STRING, + &result, NULL)) { + DLEYNA_LOG_WARNING("Browse Object operation failed: %s", + upnp_error->message); + + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OPERATION_FAILED, + "Browse operation failed: %s", + upnp_error->message); + goto on_error; + } + + DLEYNA_LOG_DEBUG("dls_device_update_ex_object result: %s", result); + + parser = gupnp_didl_lite_parser_new(); + + g_signal_connect(parser, "object-available", + G_CALLBACK(prv_get_xml_fragments), + cb_data); + + if (!gupnp_didl_lite_parser_parse_didl(parser, result, &upnp_error)) { + if (upnp_error->code == GUPNP_XML_ERROR_EMPTY_NODE) { + DLEYNA_LOG_WARNING("Property not defined for object"); + + cb_data->error = + g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_UNKNOWN_PROPERTY, + "Property not defined for object"); + } else { + DLEYNA_LOG_WARNING( + "Unable to parse results of browse: %s", + upnp_error->message); + + cb_data->error = + g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OPERATION_FAILED, + "Unable to parse results of browse: %s", + upnp_error->message); + } + + goto on_error; + } + + cb_data->action = gupnp_service_proxy_begin_action( + cb_data->proxy, "UpdateObject", + prv_update_object_update_cb, cb_data, + "ObjectID", G_TYPE_STRING, cb_data->task.target.id, + "CurrentTagValue", G_TYPE_STRING, + cb_task_data->current_tag_value, + "NewTagValue", G_TYPE_STRING, cb_task_data->new_tag_value, + NULL); + + goto no_complete; + +on_error: + + (void) g_idle_add(dls_async_task_complete, cb_data); + g_cancellable_disconnect(cb_data->cancellable, cb_data->cancel_id); + +no_complete: + + if (parser) + g_object_unref(parser); + + g_free(result); + + if (upnp_error) + g_error_free(upnp_error); + + DLEYNA_LOG_DEBUG("Exit"); +} + +void dls_device_update_object(dls_client_t *client, + dls_task_t *task, + const gchar *upnp_filter) +{ + dls_async_task_t *cb_data = (dls_async_task_t *)task; + dls_device_context_t *context; + + DLEYNA_LOG_DEBUG("Enter"); + + context = dls_device_get_context(task->target.device, client); + + cb_data->action = gupnp_service_proxy_begin_action( + context->service_proxy, "Browse", + prv_update_object_browse_cb, cb_data, + "ObjectID", G_TYPE_STRING, task->target.id, + "BrowseFlag", G_TYPE_STRING, "BrowseMetadata", + "Filter", G_TYPE_STRING, upnp_filter, + "StartingIndex", G_TYPE_INT, 0, + "RequestedCount", G_TYPE_INT, 0, + "SortCriteria", G_TYPE_STRING, + "", NULL); + + cb_data->proxy = context->service_proxy; + + g_object_add_weak_pointer((G_OBJECT(context->service_proxy)), + (gpointer *)&cb_data->proxy); + + cb_data->cancel_id = g_cancellable_connect(cb_data->cancellable, + G_CALLBACK(dls_async_task_cancelled_cb), + cb_data, NULL); + + DLEYNA_LOG_DEBUG("Exit"); +} + +static void prv_didls_free(gpointer data) +{ + prv_new_playlist_ct_t *priv_t = (prv_new_playlist_ct_t *)data; + + if (priv_t) { + g_free(priv_t->id); + g_free(priv_t->parent_id); + g_free(priv_t); + } +} + +static void prv_playlist_upload_cb(GUPnPServiceProxy *proxy, + GUPnPServiceProxyAction *action, + gpointer user_data) +{ + dls_async_task_t *cb_data = user_data; + gchar *didls; + + didls = gupnp_media_collection_get_string( + cb_data->ut.playlist.collection); + + DLEYNA_LOG_DEBUG_NL(); + DLEYNA_LOG_DEBUG("Collection: %s", didls); + DLEYNA_LOG_DEBUG_NL(); + + prv_generic_upload_cb(cb_data, NULL, didls, strlen(didls), "text/xml"); +} + +static void prv_create_didls_item_parse_object(GUPnPDIDLLiteParser *parser, + GUPnPDIDLLiteObject *object, + gpointer user_data) +{ + const char *class; + const char *title; + const char *artist; + const char *album; + const char *uri = NULL; + GList *resources; + GUPnPDIDLLiteObject *item_obj; + GUPnPDIDLLiteItem *item; + GUPnPDIDLLiteResource *res; + GUPnPMediaCollection *collection = user_data; + + if (GUPNP_IS_DIDL_LITE_CONTAINER(object)) + goto exit; + + class = gupnp_didl_lite_object_get_upnp_class(object); + title = gupnp_didl_lite_object_get_title(object); + artist = gupnp_didl_lite_object_get_artist(object); + album = gupnp_didl_lite_object_get_album(object); + resources = gupnp_didl_lite_object_get_resources(object); + + if (resources != NULL) { + if (resources->data != NULL) + uri = gupnp_didl_lite_resource_get_uri(resources->data); + + g_list_free_full(resources, g_object_unref); + } + + DLEYNA_LOG_DEBUG("Create DIDL_S Item"); + DLEYNA_LOG_DEBUG("title: %s", title); + DLEYNA_LOG_DEBUG("class: %s", class); + DLEYNA_LOG_DEBUG("Artist: %s", artist); + DLEYNA_LOG_DEBUG("album: %s", album); + DLEYNA_LOG_DEBUG("URI: %s", uri); + DLEYNA_LOG_DEBUG_NL(); + + item = gupnp_media_collection_add_item(collection); + item_obj = GUPNP_DIDL_LITE_OBJECT(item); + + if (title && *title) + gupnp_didl_lite_object_set_title(item_obj, title); + + if (class && *class) + gupnp_didl_lite_object_set_upnp_class(item_obj, class); + + if (artist && *artist) + gupnp_didl_lite_object_set_artist(item_obj, artist); + + if (album && *album) + gupnp_didl_lite_object_set_album(item_obj, album); + + if (uri && *uri) { + res = gupnp_didl_lite_object_add_resource(item_obj); + gupnp_didl_lite_resource_set_uri(res, uri); + g_object_unref(res); + } + + g_object_unref(item); + +exit: + return; +} + +static void prv_create_didls_item_browse_cb(GUPnPServiceProxy *proxy, + GUPnPServiceProxyAction *action, + gpointer user_data) +{ + GError *error = NULL; + prv_new_playlist_ct_t *priv_t = user_data; + dls_async_task_t *cb_data = priv_t->cb_data; + GUPnPDIDLLiteParser *parser = NULL; + gchar *result = NULL; + + if (!gupnp_service_proxy_end_action(proxy, action, &error, + "Result", G_TYPE_STRING, + &result, NULL)) { + DLEYNA_LOG_WARNING("Browse Object operation failed: %s", + error->message); + DLEYNA_LOG_DEBUG_NL(); + + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OPERATION_FAILED, + "Browse operation failed: %s", + error->message); + goto on_exit; + } + + DLEYNA_LOG_DEBUG_NL(); + DLEYNA_LOG_DEBUG("Result: %s", result); + DLEYNA_LOG_DEBUG_NL(); + + parser = gupnp_didl_lite_parser_new(); + + g_signal_connect(parser, "object-available", + G_CALLBACK(prv_create_didls_item_parse_object), + cb_data->ut.playlist.collection); + + if (gupnp_didl_lite_parser_parse_didl(parser, result, &error)) + goto on_exit; + + if (error->code == GUPNP_XML_ERROR_EMPTY_NODE) { + DLEYNA_LOG_WARNING("Property not defined for object"); + + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_UNKNOWN_PROPERTY, + "Property not defined for object"); + } else { + DLEYNA_LOG_WARNING("Unable to parse results of browse: %s", + error->message); + + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OPERATION_FAILED, + "Unable to parse results of browse: %s", + error->message); + } + +on_exit: + + if (cb_data->error != NULL) + dleyna_task_processor_cancel_queue( + cb_data->ut.playlist.queue_id); + + if (parser) + g_object_unref(parser); + + if (error) + g_error_free(error); + + g_free(result); +} + +static GUPnPServiceProxyAction *prv_create_didls_item_browse( + dleyna_service_task_t *task, + GUPnPServiceProxy *proxy, + gboolean *failed) +{ + prv_new_playlist_ct_t *priv_t; + + priv_t = (prv_new_playlist_ct_t *)dleyna_service_task_get_user_data( + task); + *failed = FALSE; + + DLEYNA_LOG_DEBUG("Browse for ID: %s", priv_t->id); + + return gupnp_service_proxy_begin_action( + proxy, "Browse", + dleyna_service_task_begin_action_cb, task, + "ObjectID", G_TYPE_STRING, priv_t->id, + "BrowseFlag", G_TYPE_STRING, "BrowseMetadata", + "Filter", G_TYPE_STRING, "upnp:artist,upnp:album,res", + "StartingIndex", G_TYPE_INT, 0, + "RequestedCount", G_TYPE_INT, 1, + "SortCriteria", G_TYPE_STRING, "", NULL); +} + +static gboolean prv_create_chain_didls_items(dls_task_t *task, + GUPnPServiceProxy *proxy, + dls_async_task_t *cb_data) +{ + gchar *root_path = NULL; + gchar *path; + gchar *id = NULL; + prv_new_playlist_ct_t *priv_t; + dls_async_playlist_t *a_playlist = &cb_data->ut.playlist; + GVariantIter iter; + + DLEYNA_LOG_DEBUG_NL(); + + a_playlist->collection = gupnp_media_collection_new(); + gupnp_media_collection_set_title(a_playlist->collection, + task->ut.playlist.title); + gupnp_media_collection_set_author(a_playlist->collection, + task->ut.playlist.creator); + + g_variant_iter_init(&iter, task->ut.playlist.item_path); + + while (g_variant_iter_next(&iter, "&o", &path)) { + if (!dls_path_get_path_and_id(path, &root_path, &id, NULL)) { + DLEYNA_LOG_DEBUG("Can't get id for path %s", path); + cb_data->error = g_error_new( + DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OBJECT_NOT_FOUND, + "Unable to find object for path: %s", + path); + goto on_error; + } + + DLEYNA_LOG_DEBUG("Create Task: @id: %s - Root: %s", + id, root_path); + + g_free(root_path); + + priv_t = g_new0(prv_new_playlist_ct_t, 1); + priv_t->cb_data = cb_data; + priv_t->id = id; + + dleyna_service_task_add(a_playlist->queue_id, + prv_create_didls_item_browse, + proxy, + prv_create_didls_item_browse_cb, + prv_didls_free, priv_t); + } + + DLEYNA_LOG_DEBUG_NL(); + return TRUE; + +on_error: + + return FALSE; +} + +static void prv_create_playlist_object(dls_task_create_playlist_t *t_playlist, + dls_async_playlist_t *a_playlist, + char *parent_id) +{ + GUPnPDIDLLiteWriter *writer; + GUPnPDIDLLiteObject *item; + GUPnPProtocolInfo *protocol_info; + GUPnPDIDLLiteResource *res; + GUPnPDIDLLiteContributor *creator; + GUPnPDIDLLiteContributor *author; + GTimeVal time_v; + gchar *time_c; + + writer = gupnp_didl_lite_writer_new(NULL); + item = GUPNP_DIDL_LITE_OBJECT(gupnp_didl_lite_writer_add_item(writer)); + + gupnp_didl_lite_object_set_id(item, ""); + gupnp_didl_lite_object_set_title(item, t_playlist->title); + gupnp_didl_lite_object_set_genre(item, t_playlist->genre); + gupnp_didl_lite_object_set_description(item, t_playlist->desc); + + creator = gupnp_didl_lite_object_add_creator(item); + author = gupnp_didl_lite_object_add_author(item); + gupnp_didl_lite_contributor_set_name(creator, t_playlist->creator); + gupnp_didl_lite_contributor_set_name(author, t_playlist->creator); + + gupnp_didl_lite_object_set_parent_id(item, parent_id); + gupnp_didl_lite_object_set_upnp_class(item, "object.item.playlistItem"); + gupnp_didl_lite_object_set_restricted(item, FALSE); + + protocol_info = gupnp_protocol_info_new(); + gupnp_protocol_info_set_mime_type(protocol_info, "text/xml"); + gupnp_protocol_info_set_protocol(protocol_info, "*"); + gupnp_protocol_info_set_network(protocol_info, "*"); + gupnp_protocol_info_set_dlna_profile(protocol_info, "DIDL_S"); + + res = gupnp_didl_lite_object_add_resource(item); + gupnp_didl_lite_resource_set_protocol_info(res, protocol_info); + + g_get_current_time(&time_v); + time_c = g_time_val_to_iso8601(&time_v); + gupnp_didl_lite_object_set_date(item, time_c); + + /* TODO: Need to compute DLNA Profile */ + + a_playlist->didl = gupnp_didl_lite_writer_get_string(writer); + + DLEYNA_LOG_DEBUG("Playlist object %s created", t_playlist->title); + + g_object_unref(res); + g_object_unref(protocol_info); + g_object_unref(creator); + g_object_unref(author); + g_object_unref(item); + g_object_unref(writer); + g_free(time_c); +} + +static void prv_create_didls_chain_end(gboolean cancelled, gpointer data) +{ + prv_new_playlist_ct_t *priv_t = data; + dls_async_task_t *cb_data = priv_t->cb_data; + dls_async_playlist_t *a_playlist; + dls_task_create_playlist_t *t_playlist; + + DLEYNA_LOG_DEBUG("Enter"); + + if (cb_data->cancel_id) { + if (!g_cancellable_is_cancelled(cb_data->cancellable)) + g_cancellable_disconnect(cb_data->cancellable, + cb_data->cancel_id); + cb_data->cancel_id = 0; + } + + if (cancelled) + goto on_clear; + + t_playlist = &cb_data->task.ut.playlist; + a_playlist = &cb_data->ut.playlist; + prv_create_playlist_object(t_playlist, a_playlist, priv_t->parent_id); + + DLEYNA_LOG_DEBUG("Creating object"); + cb_data->action = gupnp_service_proxy_begin_action( + cb_data->proxy, + "CreateObject", + prv_playlist_upload_cb, cb_data, + "ContainerID", G_TYPE_STRING, priv_t->parent_id, + "Elements", G_TYPE_STRING, a_playlist->didl, + NULL); + + cb_data->cancel_id = g_cancellable_connect( + cb_data->cancellable, + G_CALLBACK(dls_async_task_cancelled_cb), + cb_data, NULL); +on_clear: + + if (cancelled) { + if (!cb_data->error) + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_CANCELLED, + "Operation cancelled."); + (void) g_idle_add(dls_async_task_complete, cb_data); + } + + prv_didls_free(priv_t); + + cb_data->ut.playlist.queue_id = NULL; + + DLEYNA_LOG_DEBUG("Exit"); +} + +static void prv_create_chain_cancelled(GCancellable *cancellable, + gpointer user_data) +{ + dls_async_task_t *cb_data = user_data; + const dleyna_task_queue_key_t *queue_id = cb_data->ut.playlist.queue_id; + + DLEYNA_LOG_DEBUG("Enter"); + + dleyna_task_processor_cancel_queue(queue_id); +} + +void dls_device_playlist_upload(dls_client_t *client, + dls_task_t *task, + const gchar *parent_id) +{ + dls_async_task_t *cb_data = (dls_async_task_t *)task; + dls_device_context_t *context; + prv_new_playlist_ct_t *priv_t; + const dleyna_task_queue_key_t *queue_id; + + DLEYNA_LOG_DEBUG("Enter"); + DLEYNA_LOG_DEBUG("Uploading playlist to %s", parent_id); + + priv_t = g_new0(prv_new_playlist_ct_t, 1); + priv_t->cb_data = cb_data; + priv_t->parent_id = g_strdup(parent_id); + + queue_id = dleyna_task_processor_add_queue( + dls_server_get_task_processor(), + dleyna_service_task_create_source(), + DLS_SERVER_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_create_didls_chain_end); + dleyna_task_queue_set_user_data(queue_id, priv_t); + + context = dls_device_get_context(task->target.device, client); + + cb_data->proxy = context->service_proxy; + cb_data->ut.playlist.queue_id = queue_id; + + g_object_add_weak_pointer((G_OBJECT(context->service_proxy)), + (gpointer *)&cb_data->proxy); + + if (prv_create_chain_didls_items(task, cb_data->proxy, cb_data)) { + cb_data->cancel_id = g_cancellable_connect( + cb_data->cancellable, + G_CALLBACK(prv_create_chain_cancelled), + cb_data, NULL); + dleyna_task_queue_start(queue_id); + } else { + (void) g_idle_add(dls_async_task_complete, cb_data); + } + + DLEYNA_LOG_DEBUG("Exit"); +} diff --git a/libdleyna/server/device.h b/libdleyna/server/device.h new file mode 100644 index 0000000..ef3e3df --- /dev/null +++ b/libdleyna/server/device.h @@ -0,0 +1,129 @@ +/* + * 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 DLS_DEVICE_H__ +#define DLS_DEVICE_H__ + +#include <libgupnp/gupnp-control-point.h> + +#include <libdleyna/core/connector.h> +#include <libdleyna/core/task-processor.h> + +#include "async.h" +#include "client.h" +#include "props.h" + +struct dls_device_context_t_ { + gchar *ip_address; + GUPnPDeviceProxy *device_proxy; + GUPnPServiceProxy *service_proxy; + dls_device_t *device; + gboolean subscribed; + guint timeout_id; +}; + +struct dls_device_t_ { + dleyna_connector_id_t connection; + guint id; + gchar *path; + GPtrArray *contexts; + guint timeout_id; + GHashTable *uploads; + GHashTable *upload_jobs; + guint upload_id; + guint system_update_id; + GVariant *search_caps; + GVariant *sort_caps; + GVariant *sort_ext_caps; + GVariant *feature_list; + gboolean shutting_down; +}; + +dls_device_context_t *dls_device_append_new_context(dls_device_t *device, + const gchar *ip_address, + GUPnPDeviceProxy *proxy); +void dls_device_delete(void *device); + +void dls_device_unsubscribe(void *device); + +dls_device_t *dls_device_new( + dleyna_connector_id_t connection, + GUPnPDeviceProxy *proxy, + const gchar *ip_address, + const dleyna_connector_dispatch_cb_t *dispatch_table, + GHashTable *filter_map, + guint counter, + const dleyna_task_queue_key_t *queue_id); + +dls_device_t *dls_device_from_path(const gchar *path, GHashTable *device_list); + +dls_device_context_t *dls_device_get_context(const dls_device_t *device, + dls_client_t *client); + +void dls_device_get_children(dls_client_t *client, + dls_task_t *task, + const gchar *upnp_filter, const gchar *sort_by); + +void dls_device_get_all_props(dls_client_t *client, + dls_task_t *task, + gboolean root_object); + +void dls_device_get_prop(dls_client_t *client, + dls_task_t *task, + dls_prop_map_t *prop_map, gboolean root_object); + +void dls_device_search(dls_client_t *client, + dls_task_t *task, + const gchar *upnp_filter, const gchar *upnp_query, + const gchar *sort_by); + +void dls_device_get_resource(dls_client_t *client, + dls_task_t *task, + const gchar *upnp_filter); + +void dls_device_subscribe_to_contents_change(dls_device_t *device); + +void dls_device_upload(dls_client_t *client, + dls_task_t *task, const gchar *parent_id); + +gboolean dls_device_get_upload_status(dls_task_t *task, GError **error); + +gboolean dls_device_cancel_upload(dls_task_t *task, GError **error); + +void dls_device_get_upload_ids(dls_task_t *task); + +void dls_device_delete_object(dls_client_t *client, + dls_task_t *task); + +void dls_device_create_container(dls_client_t *client, + dls_task_t *task, + const gchar *parent_id); + +void dls_device_update_object(dls_client_t *client, + dls_task_t *task, + const gchar *upnp_filter); + +void dls_device_playlist_upload(dls_client_t *client, + dls_task_t *task, + const gchar *parent_id); + +#endif /* DLS_DEVICE_H__ */ diff --git a/libdleyna/server/dleyna-server-1.0.pc.in b/libdleyna/server/dleyna-server-1.0.pc.in new file mode 100644 index 0000000..4f7f5a2 --- /dev/null +++ b/libdleyna/server/dleyna-server-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 server library +Libs: -L${libdir} -ldleyna-server-1.0 +Requires.private: glib-2.0 gio-2.0 gupnp-1.0 gupnp-av-1.0 dleyna-core-1.0 +Version: @VERSION@
\ No newline at end of file diff --git a/libdleyna/server/dleyna-server-service.conf.in b/libdleyna/server/dleyna-server-service.conf.in new file mode 100644 index 0000000..0acca2c --- /dev/null +++ b/libdleyna/server/dleyna-server-service.conf.in @@ -0,0 +1,37 @@ +# Configuration file for dleyna-server +# +# +# +# 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/server/interface.h b/libdleyna/server/interface.h new file mode 100644 index 0000000..fb2695e --- /dev/null +++ b/libdleyna/server/interface.h @@ -0,0 +1,191 @@ +/* + * 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 DLEYNA_SERVER_INTERFACE_H__ +#define DLEYNA_SERVER_INTERFACE_H__ + +enum dls_interface_type_ { + DLS_INTERFACE_INFO_PROPERTIES, + DLS_INTERFACE_INFO_OBJECT, + DLS_INTERFACE_INFO_CONTAINER, + DLS_INTERFACE_INFO_ITEM, + DLS_INTERFACE_INFO_DEVICE, + DLS_INTERFACE_INFO_MAX +}; + +#define DLS_INTERFACE_PROPERTIES "org.freedesktop.DBus.Properties" +#define DLS_INTERFACE_MEDIA_CONTAINER "org.gnome.UPnP.MediaContainer2" +#define DLS_INTERFACE_MEDIA_OBJECT "org.gnome.UPnP.MediaObject2" +#define DLS_INTERFACE_MEDIA_ITEM "org.gnome.UPnP.MediaItem2" + +/* Object Properties */ +#define DLS_INTERFACE_PROP_PATH "Path" +#define DLS_INTERFACE_PROP_PARENT "Parent" +#define DLS_INTERFACE_PROP_RESTRICTED "Restricted" +#define DLS_INTERFACE_PROP_DISPLAY_NAME "DisplayName" +#define DLS_INTERFACE_PROP_TYPE "Type" +#define DLS_INTERFACE_PROP_CREATOR "Creator" +#define DLS_INTERFACE_PROP_DLNA_MANAGED "DLNAManaged" +#define DLS_INTERFACE_PROP_OBJECT_UPDATE_ID "ObjectUpdateID" + +/* Item Properties */ +#define DLS_INTERFACE_PROP_REFPATH "RefPath" +#define DLS_INTERFACE_PROP_ARTIST "Artist" +#define DLS_INTERFACE_PROP_ARTISTS "Artists" +#define DLS_INTERFACE_PROP_ALBUM "Album" +#define DLS_INTERFACE_PROP_DATE "Date" +#define DLS_INTERFACE_PROP_GENRE "Genre" +#define DLS_INTERFACE_PROP_TRACK_NUMBER "TrackNumber" +#define DLS_INTERFACE_PROP_ALBUM_ART_URL "AlbumArtURL" +#define DLS_INTERFACE_PROP_RESOURCES "Resources" + +/* Container Properties */ +#define DLS_INTERFACE_PROP_SEARCHABLE "Searchable" +#define DLS_INTERFACE_PROP_CHILD_COUNT "ChildCount" +#define DLS_INTERFACE_PROP_CREATE_CLASSES "CreateClasses" +#define DLS_INTERFACE_PROP_CONTAINER_UPDATE_ID "ContainerUpdateID" +#define DLS_INTERFACE_PROP_TOTAL_DELETED_CHILD_COUNT "TotalDeletedChildCount" + +/* Device Properties */ +#define DLS_INTERFACE_PROP_LOCATION "Location" +#define DLS_INTERFACE_PROP_UDN "UDN" +#define DLS_INTERFACE_PROP_DEVICE_TYPE "DeviceType" +#define DLS_INTERFACE_PROP_FRIENDLY_NAME "FriendlyName" +#define DLS_INTERFACE_PROP_MANUFACTURER "Manufacturer" +#define DLS_INTERFACE_PROP_MANUFACTURER_URL "ManufacturerUrl" +#define DLS_INTERFACE_PROP_MODEL_DESCRIPTION "ModelDescription" +#define DLS_INTERFACE_PROP_MODEL_NAME "ModelName" +#define DLS_INTERFACE_PROP_MODEL_NUMBER "ModelNumber" +#define DLS_INTERFACE_PROP_MODEL_URL "ModelURL" +#define DLS_INTERFACE_PROP_SERIAL_NUMBER "SerialNumber" +#define DLS_INTERFACE_PROP_PRESENTATION_URL "PresentationURL" +#define DLS_INTERFACE_PROP_ICON_URL "IconURL" +#define DLS_INTERFACE_PROP_SV_DLNA_CAPABILITIES "DLNACaps" +#define DLS_INTERFACE_PROP_SV_SEARCH_CAPABILITIES "SearchCaps" +#define DLS_INTERFACE_PROP_SV_SORT_CAPABILITIES "SortCaps" +#define DLS_INTERFACE_PROP_SV_SORT_EXT_CAPABILITIES "SortExtCaps" +#define DLS_INTERFACE_PROP_SV_FEATURE_LIST "FeatureList" +#define DLS_INTERFACE_PROP_SV_SERVICE_RESET_TOKEN "ServiceResetToken" + +/* Resources Properties */ +#define DLS_INTERFACE_PROP_MIME_TYPE "MIMEType" +#define DLS_INTERFACE_PROP_DLNA_PROFILE "DLNAProfile" +#define DLS_INTERFACE_PROP_SIZE "Size" +#define DLS_INTERFACE_PROP_DURATION "Duration" +#define DLS_INTERFACE_PROP_BITRATE "Bitrate" +#define DLS_INTERFACE_PROP_SAMPLE_RATE "SampleRate" +#define DLS_INTERFACE_PROP_BITS_PER_SAMPLE "BitsPerSample" +#define DLS_INTERFACE_PROP_WIDTH "Width" +#define DLS_INTERFACE_PROP_HEIGHT "Height" +#define DLS_INTERFACE_PROP_COLOR_DEPTH "ColorDepth" +#define DLS_INTERFACE_PROP_URLS "URLs" +#define DLS_INTERFACE_PROP_URL "URL" +#define DLS_INTERFACE_PROP_UPDATE_COUNT "UpdateCount" + +/* Evented State Variable Properties */ +#define DLS_INTERFACE_PROP_ESV_SYSTEM_UPDATE_ID "SystemUpdateID" + +#define DLS_INTERFACE_GET_VERSION "GetVersion" +#define DLS_INTERFACE_GET_SERVERS "GetServers" +#define DLS_INTERFACE_RELEASE "Release" +#define DLS_INTERFACE_SET_PROTOCOL_INFO "SetProtocolInfo" +#define DLS_INTERFACE_PREFER_LOCAL_ADDRESSES "PreferLocalAddresses" + +#define DLS_INTERFACE_FOUND_SERVER "FoundServer" +#define DLS_INTERFACE_LOST_SERVER "LostServer" + +#define DLS_INTERFACE_LIST_CHILDREN "ListChildren" +#define DLS_INTERFACE_LIST_CHILDREN_EX "ListChildrenEx" +#define DLS_INTERFACE_LIST_ITEMS "ListItems" +#define DLS_INTERFACE_LIST_ITEMS_EX "ListItemsEx" +#define DLS_INTERFACE_LIST_CONTAINERS "ListContainers" +#define DLS_INTERFACE_LIST_CONTAINERS_EX "ListContainersEx" +#define DLS_INTERFACE_SEARCH_OBJECTS "SearchObjects" +#define DLS_INTERFACE_SEARCH_OBJECTS_EX "SearchObjectsEx" +#define DLS_INTERFACE_UPDATE "Update" + +#define DLS_INTERFACE_GET_COMPATIBLE_RESOURCE "GetCompatibleResource" + +#define DLS_INTERFACE_GET "Get" +#define DLS_INTERFACE_GET_ALL "GetAll" +#define DLS_INTERFACE_INTERFACE_NAME "InterfaceName" +#define DLS_INTERFACE_PROPERTY_NAME "PropertyName" +#define DLS_INTERFACE_PROPERTIES_VALUE "Properties" +#define DLS_INTERFACE_VALUE "value" +#define DLS_INTERFACE_CHILD_TYPES "ChildTypes" + +#define DLS_INTERFACE_VERSION "Version" +#define DLS_INTERFACE_SERVERS "Servers" + +#define DLS_INTERFACE_CRITERIA "Criteria" +#define DLS_INTERFACE_DICT "Dictionary" +#define DLS_INTERFACE_PATH "Path" +#define DLS_INTERFACE_QUERY "Query" +#define DLS_INTERFACE_PROTOCOL_INFO "ProtocolInfo" +#define DLS_INTERFACE_PREFER "Prefer" + +#define DLS_INTERFACE_OFFSET "Offset" +#define DLS_INTERFACE_MAX "Max" +#define DLS_INTERFACE_FILTER "Filter" +#define DLS_INTERFACE_CHILDREN "Children" +#define DLS_INTERFACE_SORT_BY "SortBy" +#define DLS_INTERFACE_TOTAL_ITEMS "TotalItems" + +#define DLS_INTERFACE_PROPERTIES_CHANGED "PropertiesChanged" +#define DLS_INTERFACE_CHANGED_PROPERTIES "ChangedProperties" +#define DLS_INTERFACE_INVALIDATED_PROPERTIES "InvalidatedProperties" +#define DLS_INTERFACE_ESV_CONTAINER_UPDATE_IDS "ContainerUpdateIDs" +#define DLS_INTERFACE_CONTAINER_PATHS_ID "ContainerPathsIDs" +#define DLS_INTERFACE_ESV_LAST_CHANGE "LastChange" +#define DLS_INTERFACE_LAST_CHANGE_STATE_EVENT "StateEvent" + +#define DLS_INTERFACE_DELETE "Delete" + +#define DLS_INTERFACE_CREATE_CONTAINER "CreateContainer" +#define DLS_INTERFACE_CREATE_CONTAINER_IN_ANY "CreateContainerInAnyContainer" + +#define DLS_INTERFACE_UPLOAD "Upload" +#define DLS_INTERFACE_UPLOAD_TO_ANY "UploadToAnyContainer" +#define DLS_INTERFACE_GET_UPLOAD_STATUS "GetUploadStatus" +#define DLS_INTERFACE_GET_UPLOAD_IDS "GetUploadIDs" +#define DLS_INTERFACE_CANCEL_UPLOAD "CancelUpload" +#define DLS_INTERFACE_TOTAL "Total" +#define DLS_INTERFACE_LENGTH "Length" +#define DLS_INTERFACE_FILE_PATH "FilePath" +#define DLS_INTERFACE_UPLOAD_ID "UploadId" +#define DLS_INTERFACE_UPLOAD_IDS "UploadIDs" +#define DLS_INTERFACE_UPLOAD_STATUS "UploadStatus" +#define DLS_INTERFACE_UPLOAD_UPDATE "UploadUpdate" +#define DLS_INTERFACE_TO_ADD_UPDATE "ToAddUpdate" +#define DLS_INTERFACE_TO_DELETE "ToDelete" +#define DLS_INTERFACE_CANCEL "Cancel" + +#define DLS_INTERFACE_CREATE_PLAYLIST "CreatePlaylist" +#define DLS_INTERFACE_CREATE_PLAYLIST_TO_ANY "CreatePlaylistInAnyContainer" +#define DLS_INTERFACE_TITLE "Title" +#define DLS_INTERFACE_CREATOR "Creator" +#define DLS_INTERFACE_GENRE "Genre" +#define DLS_INTERFACE_DESCRIPTION "Description" +#define DLS_INTERFACE_PLAYLIST_ITEMS "PlaylistItems" + + +#endif /* DLEYNA_SERVER_INTERFACE_H__ */ diff --git a/libdleyna/server/path.c b/libdleyna/server/path.c new file mode 100644 index 0000000..345cba4 --- /dev/null +++ b/libdleyna/server/path.c @@ -0,0 +1,154 @@ +/* + * 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 <string.h> + +#include <libdleyna/core/error.h> + +#include "path.h" +#include "server.h" + +gboolean dls_path_get_non_root_id(const gchar *object_path, + const gchar **slash_before_id) +{ + gboolean retval = FALSE; + unsigned int offset = strlen(DLEYNA_SERVER_PATH) + 1; + + if (!g_str_has_prefix(object_path, DLEYNA_SERVER_PATH "/")) + goto on_error; + + if (!object_path[offset]) + goto on_error; + + *slash_before_id = strchr(&object_path[offset], '/'); + retval = TRUE; + +on_error: + + return retval; +} + +static gchar *prv_object_name_to_id(const gchar *object_name) +{ + gchar *retval = NULL; + unsigned int object_len = strlen(object_name); + unsigned int i; + gint hex; + gchar byte; + + if (object_len & 1) + goto on_error; + + retval = g_malloc((object_len >> 1) + 1); + + for (i = 0; i < object_len; i += 2) { + hex = g_ascii_xdigit_value(object_name[i]); + + if (hex == -1) + goto on_error; + + byte = hex << 4; + hex = g_ascii_xdigit_value(object_name[i + 1]); + + if (hex == -1) + goto on_error; + + byte |= hex; + retval[i >> 1] = byte; + } + retval[i >> 1] = 0; + + return retval; + +on_error: + + g_free(retval); + + return NULL; +} + +gboolean dls_path_get_path_and_id(const gchar *object_path, gchar **root_path, + gchar **id, GError **error) +{ + const gchar *slash; + gchar *coded_id; + + if (!dls_path_get_non_root_id(object_path, &slash)) + goto on_error; + + if (!slash) { + *root_path = g_strdup(object_path); + *id = g_strdup("0"); + } else { + if (!slash[1]) + goto on_error; + + coded_id = prv_object_name_to_id(slash + 1); + + if (!coded_id) + goto on_error; + + *root_path = g_strndup(object_path, slash - object_path); + *id = coded_id; + } + + return TRUE; + +on_error: + if (error) + *error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_BAD_PATH, + "object path is badly formed."); + + return FALSE; +} + +static gchar *prv_id_to_object_name(const gchar *id) +{ + gchar *retval; + unsigned int i; + unsigned int data_len = strlen(id); + + retval = g_malloc((data_len << 1) + 1); + retval[0] = 0; + + for (i = 0; i < data_len; i++) + sprintf(&retval[i << 1], "%0x", (guint8) id[i]); + + return retval; +} + +gchar *dls_path_from_id(const gchar *root_path, const gchar *id) +{ + gchar *coded_id; + gchar *path; + + if (!strcmp(id, "0")) { + path = g_strdup(root_path); + } else { + coded_id = prv_id_to_object_name(id); + path = g_strdup_printf("%s/%s", root_path, coded_id); + g_free(coded_id); + } + + return path; +} diff --git a/libdleyna/server/path.h b/libdleyna/server/path.h new file mode 100644 index 0000000..1ccd12d --- /dev/null +++ b/libdleyna/server/path.h @@ -0,0 +1,36 @@ +/* + * 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 DLS_PATH_H__ +#define DLS_PATH_H__ + +#include <glib.h> + +gboolean dls_path_get_non_root_id(const gchar *object_path, + const gchar **slash_before_id); + +gboolean dls_path_get_path_and_id(const gchar *object_path, gchar **root_path, + gchar **id, GError **error); + +gchar *dls_path_from_id(const gchar *root_path, const gchar *id); + +#endif /* DLS_PATH_H__ */ diff --git a/libdleyna/server/props.c b/libdleyna/server/props.c new file mode 100644 index 0000000..2dbd63b --- /dev/null +++ b/libdleyna/server/props.c @@ -0,0 +1,1792 @@ +/* + * 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 <libgupnp-av/gupnp-didl-lite-contributor.h> + +#include <libdleyna/core/log.h> + +#include "device.h" +#include "interface.h" +#include "path.h" +#include "props.h" + +static const gchar gUPnPContainer[] = "object.container"; +static const gchar gUPnPAlbum[] = "object.container.album"; +static const gchar gUPnPPerson[] = "object.container.person"; +static const gchar gUPnPGenre[] = "object.container.genre"; +static const gchar gUPnPAudioItem[] = "object.item.audioItem"; +static const gchar gUPnPVideoItem[] = "object.item.videoItem"; +static const gchar gUPnPImageItem[] = "object.item.imageItem"; +static const gchar gUPnPPlaylistItem[] = "object.item.playlistItem"; +static const gchar gUPnPItem[] = "object.item"; + +static const unsigned int gUPnPContainerLen = + (sizeof(gUPnPContainer) / sizeof(gchar)) - 1; +static const unsigned int gUPnPAlbumLen = + (sizeof(gUPnPAlbum) / sizeof(gchar)) - 1; +static const unsigned int gUPnPPersonLen = + (sizeof(gUPnPPerson) / sizeof(gchar)) - 1; +static const unsigned int gUPnPGenreLen = + (sizeof(gUPnPGenre) / sizeof(gchar)) - 1; +static const unsigned int gUPnPAudioItemLen = + (sizeof(gUPnPAudioItem) / sizeof(gchar)) - 1; +static const unsigned int gUPnPVideoItemLen = + (sizeof(gUPnPVideoItem) / sizeof(gchar)) - 1; +static const unsigned int gUPnPImageItemLen = + (sizeof(gUPnPImageItem) / sizeof(gchar)) - 1; +static const unsigned int gUPnPPlaylistItemLen = + (sizeof(gUPnPPlaylistItem) / sizeof(gchar)) - 1; +static const unsigned int gUPnPItemLen = + (sizeof(gUPnPItem) / sizeof(gchar)) - 1; + +static const gchar gUPnPPhotoAlbum[] = "object.container.album.photoAlbum"; +static const gchar gUPnPMusicAlbum[] = "object.container.album.musicAlbum"; +static const gchar gUPnPMusicArtist[] = "object.container.person.musicArtist"; +static const gchar gUPnPMovieGenre[] = "object.container.genre.movieGenre"; +static const gchar gUPnPMusicGenre[] = "object.container.genre.musicGenre"; +static const gchar gUPnPMusicTrack[] = "object.item.audioItem.musicTrack"; +static const gchar gUPnPAudioBroadcast[] = + "object.item.audioItem.audioBroadcast"; +static const gchar gUPnPAudioBook[] = "object.item.audioItem.audioBook"; +static const gchar gUPnPMovie[] = "object.item.videoItem.movie"; +static const gchar gUPnPMusicVideoClip[] = + "object.item.videoItem.musicVideoClip"; +static const gchar gUPnPVideoBroadcast[] = + "object.item.videoItem.videoBroadcast"; +static const gchar gUPnPPhoto[] = "object.item.imageItem.photo"; + +static const gchar gMediaSpec2Container[] = "container"; +static const gchar gMediaSpec2Album[] = "album"; +static const gchar gMediaSpec2AlbumPhoto[] = "album.photo"; +static const gchar gMediaSpec2AlbumMusic[] = "album.music"; +static const gchar gMediaSpec2Person[] = "person"; +static const gchar gMediaSpec2PersonMusicArtist[] = "person.musicartist"; +static const gchar gMediaSpec2Genre[] = "genre"; +static const gchar gMediaSpec2GenreMovie[] = "genre.movie"; +static const gchar gMediaSpec2GenreMusic[] = "genre.music"; +static const gchar gMediaSpec2AudioMusic[] = "audio.music"; +static const gchar gMediaSpec2AudioBroadcast[] = "audio.broadcast"; +static const gchar gMediaSpec2AudioBook[] = "audio.book"; +static const gchar gMediaSpec2Audio[] = "audio"; +static const gchar gMediaSpec2VideoMovie[] = "video.movie"; +static const gchar gMediaSpec2VideoMusicClip[] = "video.musicclip"; +static const gchar gMediaSpec2VideoBroadcast[] = "video.broadcast"; +static const gchar gMediaSpec2Video[] = "video"; +static const gchar gMediaSpec2ImagePhoto[] = "image.photo"; +static const gchar gMediaSpec2Image[] = "image"; +static const gchar gMediaSpec2Playlist[] = "playlist"; +static const gchar gMediaSpec2Item[] = "item"; + +static dls_prop_map_t *prv_prop_map_new(const gchar *prop_name, + dls_upnp_prop_mask type, + gboolean filter, + gboolean searchable, + gboolean updateable) +{ + dls_prop_map_t *retval = g_new(dls_prop_map_t, 1); + retval->upnp_prop_name = prop_name; + retval->type = type; + retval->filter = filter; + retval->searchable = searchable; + retval->updateable = updateable; + return retval; +} + +void dls_prop_maps_new(GHashTable **property_map, GHashTable **filter_map) +{ + dls_prop_map_t *prop_t; + GHashTable *p_map; + GHashTable *f_map; + + p_map = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL); + f_map = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free); + + /* @childCount */ + prop_t = prv_prop_map_new("@childCount", + DLS_UPNP_MASK_PROP_CHILD_COUNT, + TRUE, TRUE, FALSE); + g_hash_table_insert(f_map, DLS_INTERFACE_PROP_CHILD_COUNT, prop_t); + g_hash_table_insert(p_map, "@childCount", + DLS_INTERFACE_PROP_CHILD_COUNT); + + /* @id */ + prop_t = prv_prop_map_new("@id", + DLS_UPNP_MASK_PROP_PATH, + FALSE, TRUE, FALSE); + g_hash_table_insert(f_map, DLS_INTERFACE_PROP_PATH, prop_t); + g_hash_table_insert(p_map, "@id", DLS_INTERFACE_PROP_PATH); + + /* @parentID */ + prop_t = prv_prop_map_new("@parentID", + DLS_UPNP_MASK_PROP_PARENT, + FALSE, TRUE, FALSE); + g_hash_table_insert(f_map, DLS_INTERFACE_PROP_PARENT, prop_t); + g_hash_table_insert(p_map, "@parentID", DLS_INTERFACE_PROP_PARENT); + + /* @refID */ + prop_t = prv_prop_map_new("@refID", + DLS_UPNP_MASK_PROP_REFPATH, + TRUE, TRUE, FALSE); + g_hash_table_insert(f_map, DLS_INTERFACE_PROP_REFPATH, prop_t); + g_hash_table_insert(p_map, "@refID", DLS_INTERFACE_PROP_REFPATH); + + /* @restricted */ + prop_t = prv_prop_map_new("@restricted", + DLS_UPNP_MASK_PROP_RESTRICTED, + TRUE, TRUE, FALSE); + g_hash_table_insert(f_map, DLS_INTERFACE_PROP_RESTRICTED, prop_t); + g_hash_table_insert(p_map, "@restricted", + DLS_INTERFACE_PROP_RESTRICTED); + + /* @searchable */ + prop_t = prv_prop_map_new("@searchable", + DLS_UPNP_MASK_PROP_SEARCHABLE, + TRUE, TRUE, FALSE); + g_hash_table_insert(f_map, DLS_INTERFACE_PROP_SEARCHABLE, prop_t); + g_hash_table_insert(p_map, "@searchable", + DLS_INTERFACE_PROP_SEARCHABLE); + + /* dc:creator */ + prop_t = prv_prop_map_new("dc:creator", + DLS_UPNP_MASK_PROP_CREATOR, + TRUE, TRUE, FALSE); + g_hash_table_insert(f_map, DLS_INTERFACE_PROP_CREATOR, prop_t); + g_hash_table_insert(p_map, "dc:creator", DLS_INTERFACE_PROP_CREATOR); + + /* dc:date */ + prop_t = prv_prop_map_new("dc:date", + DLS_UPNP_MASK_PROP_DATE, + TRUE, TRUE, TRUE); + g_hash_table_insert(f_map, DLS_INTERFACE_PROP_DATE, prop_t); + g_hash_table_insert(p_map, "dc:date", DLS_INTERFACE_PROP_DATE); + + /* dc:title */ + prop_t = prv_prop_map_new("dc:title", + DLS_UPNP_MASK_PROP_DISPLAY_NAME, + FALSE, TRUE, TRUE); + g_hash_table_insert(f_map, DLS_INTERFACE_PROP_DISPLAY_NAME, prop_t); + g_hash_table_insert(p_map, "dc:title", DLS_INTERFACE_PROP_DISPLAY_NAME); + + /* dlna:dlnaManaged */ + prop_t = prv_prop_map_new("dlna:dlnaManaged", + DLS_UPNP_MASK_PROP_DLNA_MANAGED, + TRUE, FALSE, FALSE); + g_hash_table_insert(f_map, DLS_INTERFACE_PROP_DLNA_MANAGED, prop_t); + g_hash_table_insert(p_map, "dlna:dlnaManaged", + DLS_INTERFACE_PROP_DLNA_MANAGED); + + /* res */ + /* res - RES */ + prop_t = prv_prop_map_new("res", + DLS_UPNP_MASK_PROP_RESOURCES, + TRUE, FALSE, FALSE); + g_hash_table_insert(f_map, DLS_INTERFACE_PROP_RESOURCES, prop_t); + + /* res - URL */ + prop_t = prv_prop_map_new("res", + DLS_UPNP_MASK_PROP_URL, + TRUE, FALSE, FALSE); + g_hash_table_insert(f_map, DLS_INTERFACE_PROP_URL, prop_t); + + /* res - URLS */ + prop_t = prv_prop_map_new("res", + DLS_UPNP_MASK_PROP_URLS, + TRUE, FALSE, FALSE); + g_hash_table_insert(f_map, DLS_INTERFACE_PROP_URLS, prop_t); + + /* res@bitrate */ + prop_t = prv_prop_map_new("res@bitrate", + DLS_UPNP_MASK_PROP_BITRATE, + TRUE, TRUE, FALSE); + g_hash_table_insert(f_map, DLS_INTERFACE_PROP_BITRATE, prop_t); + g_hash_table_insert(p_map, "res@bitrate", DLS_INTERFACE_PROP_BITRATE); + + /* res@bitsPerSample */ + prop_t = prv_prop_map_new("res@bitsPerSample", + DLS_UPNP_MASK_PROP_BITS_PER_SAMPLE, + TRUE, TRUE, FALSE); + g_hash_table_insert(f_map, DLS_INTERFACE_PROP_BITS_PER_SAMPLE, prop_t); + g_hash_table_insert(p_map, "res@bitsPerSample", + DLS_INTERFACE_PROP_BITS_PER_SAMPLE); + + /* res@colorDepth */ + prop_t = prv_prop_map_new("res@colorDepth", + DLS_UPNP_MASK_PROP_COLOR_DEPTH, + TRUE, TRUE, FALSE); + g_hash_table_insert(f_map, DLS_INTERFACE_PROP_COLOR_DEPTH, prop_t); + g_hash_table_insert(p_map, "res@colorDepth", + DLS_INTERFACE_PROP_COLOR_DEPTH); + + /* res@duration */ + prop_t = prv_prop_map_new("res@duration", + DLS_UPNP_MASK_PROP_DURATION, + TRUE, TRUE, FALSE); + g_hash_table_insert(f_map, DLS_INTERFACE_PROP_DURATION, prop_t); + g_hash_table_insert(p_map, "res@duration", + DLS_INTERFACE_PROP_DURATION); + + /* res@protocolInfo */ + /* res@protocolInfo - DLNA PROFILE*/ + prop_t = prv_prop_map_new("res@protocolInfo", + DLS_UPNP_MASK_PROP_DLNA_PROFILE, + TRUE, FALSE, FALSE); + g_hash_table_insert(f_map, DLS_INTERFACE_PROP_DLNA_PROFILE, prop_t); + + /* res@protocolInfo - MIME TYPES*/ + prop_t = prv_prop_map_new("res@protocolInfo", + DLS_UPNP_MASK_PROP_MIME_TYPE, + TRUE, FALSE, FALSE); + g_hash_table_insert(f_map, DLS_INTERFACE_PROP_MIME_TYPE, prop_t); + + /* res@resolution */ + /* res@resolution - HEIGH */ + prop_t = prv_prop_map_new("res@resolution", + DLS_UPNP_MASK_PROP_HEIGHT, + TRUE, FALSE, FALSE); + g_hash_table_insert(f_map, DLS_INTERFACE_PROP_HEIGHT, prop_t); + + /* res@resolution - WIDTH */ + prop_t = prv_prop_map_new("res@resolution", + DLS_UPNP_MASK_PROP_WIDTH, + TRUE, FALSE, FALSE); + g_hash_table_insert(f_map, DLS_INTERFACE_PROP_WIDTH, prop_t); + + /* res@sampleFrequency */ + prop_t = prv_prop_map_new("res@sampleFrequency", + DLS_UPNP_MASK_PROP_SAMPLE_RATE, + TRUE, TRUE, FALSE); + g_hash_table_insert(f_map, DLS_INTERFACE_PROP_SAMPLE_RATE, prop_t); + g_hash_table_insert(p_map, "res@sampleFrequency", + DLS_INTERFACE_PROP_SAMPLE_RATE); + + /* res@size */ + prop_t = prv_prop_map_new("res@size", + DLS_UPNP_MASK_PROP_SIZE, + TRUE, TRUE, FALSE); + g_hash_table_insert(f_map, DLS_INTERFACE_PROP_SIZE, prop_t); + g_hash_table_insert(p_map, "res@size", DLS_INTERFACE_PROP_SIZE); + + /* res@updateCount */ + prop_t = prv_prop_map_new("res@updateCount", + DLS_UPNP_MASK_PROP_UPDATE_COUNT, + TRUE, TRUE, FALSE); + g_hash_table_insert(f_map, DLS_INTERFACE_PROP_UPDATE_COUNT, prop_t); + g_hash_table_insert(p_map, "res@updateCount", + DLS_INTERFACE_PROP_UPDATE_COUNT); + + /* upnp:album */ + prop_t = prv_prop_map_new("upnp:album", + DLS_UPNP_MASK_PROP_ALBUM, + TRUE, TRUE, TRUE); + g_hash_table_insert(f_map, DLS_INTERFACE_PROP_ALBUM, prop_t); + g_hash_table_insert(p_map, "upnp:album", DLS_INTERFACE_PROP_ALBUM); + + /* upnp:albumArtURI */ + prop_t = prv_prop_map_new("upnp:albumArtURI", + DLS_UPNP_MASK_PROP_ALBUM_ART_URL, + TRUE, TRUE, FALSE); + g_hash_table_insert(f_map, DLS_INTERFACE_PROP_ALBUM_ART_URL, prop_t); + g_hash_table_insert(p_map, "upnp:albumArtURI", + DLS_INTERFACE_PROP_ALBUM_ART_URL); + + /* upnp:artist */ + /* upnp:artist - ARTIST*/ + prop_t = prv_prop_map_new("upnp:artist", + DLS_UPNP_MASK_PROP_ARTIST, + TRUE, TRUE, FALSE); + g_hash_table_insert(f_map, DLS_INTERFACE_PROP_ARTIST, prop_t); + g_hash_table_insert(p_map, "upnp:artist", DLS_INTERFACE_PROP_ARTIST); + + /* upnp:artist - ARTISTS*/ + prop_t = prv_prop_map_new("upnp:artist", + DLS_UPNP_MASK_PROP_ARTISTS, + TRUE, FALSE, TRUE); + g_hash_table_insert(f_map, DLS_INTERFACE_PROP_ARTISTS, prop_t); + + /* upnp:class */ + prop_t = prv_prop_map_new("upnp:class", + DLS_UPNP_MASK_PROP_TYPE, + FALSE, TRUE, TRUE); + g_hash_table_insert(f_map, DLS_INTERFACE_PROP_TYPE, prop_t); + g_hash_table_insert(p_map, "upnp:class", DLS_INTERFACE_PROP_TYPE); + + /* upnp:containerUpdateID */ + prop_t = prv_prop_map_new("upnp:containerUpdateID", + DLS_UPNP_MASK_PROP_CONTAINER_UPDATE_ID, + TRUE, TRUE, FALSE); + g_hash_table_insert(f_map, DLS_INTERFACE_PROP_CONTAINER_UPDATE_ID, + prop_t); + g_hash_table_insert(p_map, "upnp:containerUpdateID", + DLS_INTERFACE_PROP_CONTAINER_UPDATE_ID); + + /* upnp:createClass */ + prop_t = prv_prop_map_new("upnp:createClass", + DLS_UPNP_MASK_PROP_CREATE_CLASSES, + TRUE, FALSE, FALSE); + g_hash_table_insert(f_map, DLS_INTERFACE_PROP_CREATE_CLASSES, prop_t); + + /* upnp:genre */ + prop_t = prv_prop_map_new("upnp:genre", + DLS_UPNP_MASK_PROP_GENRE, + TRUE, TRUE, FALSE); + g_hash_table_insert(f_map, DLS_INTERFACE_PROP_GENRE, prop_t); + g_hash_table_insert(p_map, "upnp:genre", DLS_INTERFACE_PROP_GENRE); + + /* upnp:objectUpdateID */ + prop_t = prv_prop_map_new("upnp:objectUpdateID", + DLS_UPNP_MASK_PROP_OBJECT_UPDATE_ID, + TRUE, TRUE, FALSE); + g_hash_table_insert(f_map, DLS_INTERFACE_PROP_OBJECT_UPDATE_ID, prop_t); + g_hash_table_insert(p_map, "upnp:objectUpdateID", + DLS_INTERFACE_PROP_OBJECT_UPDATE_ID); + + /* upnp:originalTrackNumber */ + prop_t = prv_prop_map_new("upnp:originalTrackNumber", + DLS_UPNP_MASK_PROP_TRACK_NUMBER, + TRUE, TRUE, TRUE); + g_hash_table_insert(f_map, DLS_INTERFACE_PROP_TRACK_NUMBER, prop_t); + g_hash_table_insert(p_map, "upnp:originalTrackNumber", + DLS_INTERFACE_PROP_TRACK_NUMBER); + + /* upnp:totalDeletedChildCount */ + prop_t = prv_prop_map_new("upnp:totalDeletedChildCount", + DLS_UPNP_MASK_PROP_TOTAL_DELETED_CHILD_COUNT, + TRUE, TRUE, FALSE); + g_hash_table_insert(f_map, DLS_INTERFACE_PROP_TOTAL_DELETED_CHILD_COUNT, + prop_t); + g_hash_table_insert(p_map, "upnp:totalDeletedChildCount", + DLS_INTERFACE_PROP_TOTAL_DELETED_CHILD_COUNT); + + *filter_map = f_map; + *property_map = p_map; +} + +static gchar *prv_compute_upnp_filter(GHashTable *upnp_props) +{ + gpointer key; + GString *str; + GHashTableIter iter; + + str = g_string_new(""); + g_hash_table_iter_init(&iter, upnp_props); + if (g_hash_table_iter_next(&iter, &key, NULL)) { + g_string_append(str, (const gchar *)key); + while (g_hash_table_iter_next(&iter, &key, NULL)) { + g_string_append(str, ","); + g_string_append(str, (const gchar *)key); + } + } + + return g_string_free(str, FALSE); +} + +static dls_upnp_prop_mask prv_parse_filter_list(GHashTable *filter_map, + GVariant *filter, + gchar **upnp_filter) +{ + GVariantIter viter; + const gchar *prop; + dls_prop_map_t *prop_map; + GHashTable *upnp_props; + dls_upnp_prop_mask mask = 0; + + upnp_props = g_hash_table_new_full(g_str_hash, g_str_equal, + NULL, NULL); + (void) g_variant_iter_init(&viter, filter); + + while (g_variant_iter_next(&viter, "&s", &prop)) { + prop_map = g_hash_table_lookup(filter_map, prop); + if (!prop_map) + continue; + + mask |= prop_map->type; + + if (!prop_map->filter) + continue; + + g_hash_table_insert(upnp_props, + (gpointer) prop_map->upnp_prop_name, NULL); + } + + *upnp_filter = prv_compute_upnp_filter(upnp_props); + g_hash_table_unref(upnp_props); + + return mask; +} + +dls_upnp_prop_mask dls_props_parse_filter(GHashTable *filter_map, + GVariant *filter, + gchar **upnp_filter) +{ + gchar *str; + gboolean parse_filter = TRUE; + dls_upnp_prop_mask mask; + + if (g_variant_n_children(filter) == 1) { + g_variant_get_child(filter, 0, "&s", &str); + if (!strcmp(str, "*")) + parse_filter = FALSE; + } + + if (parse_filter) { + mask = prv_parse_filter_list(filter_map, filter, upnp_filter); + } else { + mask = DLS_UPNP_MASK_ALL_PROPS; + *upnp_filter = g_strdup("*"); + } + + return mask; +} + +gboolean dls_props_parse_update_filter(GHashTable *filter_map, + GVariant *to_add_update, + GVariant *to_delete, + dls_upnp_prop_mask *mask, + gchar **upnp_filter) +{ + GVariantIter viter; + const gchar *prop; + GVariant *value; + dls_prop_map_t *prop_map; + GHashTable *upnp_props; + gboolean retval = FALSE; + + *mask = 0; + + upnp_props = g_hash_table_new_full(g_str_hash, g_str_equal, + NULL, NULL); + + (void) g_variant_iter_init(&viter, to_add_update); + + while (g_variant_iter_next(&viter, "{&sv}", &prop, &value)) { + DLEYNA_LOG_DEBUG("to_add_update = %s", prop); + + prop_map = g_hash_table_lookup(filter_map, prop); + + if ((!prop_map) || (!prop_map->updateable)) + goto on_error; + + *mask |= prop_map->type; + + if (!prop_map->filter) + continue; + + g_hash_table_insert(upnp_props, + (gpointer) prop_map->upnp_prop_name, NULL); + } + + (void) g_variant_iter_init(&viter, to_delete); + + while (g_variant_iter_next(&viter, "&s", &prop)) { + DLEYNA_LOG_DEBUG("to_delete = %s", prop); + + prop_map = g_hash_table_lookup(filter_map, prop); + + if ((!prop_map) || (!prop_map->updateable) || + (*mask & prop_map->type) != 0) + goto on_error; + + *mask |= prop_map->type; + + if (!prop_map->filter) + continue; + + g_hash_table_insert(upnp_props, + (gpointer) prop_map->upnp_prop_name, NULL); + } + + *upnp_filter = prv_compute_upnp_filter(upnp_props); + + retval = TRUE; + +on_error: + + g_hash_table_unref(upnp_props); + + return retval; +} + +static void prv_add_string_prop(GVariantBuilder *vb, const gchar *key, + const gchar *value) +{ + if (value) { + DLEYNA_LOG_DEBUG("Prop %s = %s", key, value); + + g_variant_builder_add(vb, "{sv}", key, + g_variant_new_string(value)); + } +} + +static void prv_add_strv_prop(GVariantBuilder *vb, const gchar *key, + const gchar **value, unsigned int len) +{ + if (len > 0) + g_variant_builder_add(vb, "{sv}", key, + g_variant_new_strv(value, len)); +} + +static void prv_add_path_prop(GVariantBuilder *vb, const gchar *key, + const gchar *value) +{ + if (value) { + DLEYNA_LOG_DEBUG("Prop %s = %s", key, value); + + g_variant_builder_add(vb, "{sv}", key, + g_variant_new_object_path(value)); + } +} + +static void prv_add_uint_prop(GVariantBuilder *vb, const gchar *key, + unsigned int value) +{ + DLEYNA_LOG_DEBUG("Prop %s = %u", key, value); + + g_variant_builder_add(vb, "{sv}", key, g_variant_new_uint32(value)); +} + +static void prv_add_int_prop(GVariantBuilder *vb, const gchar *key, + int value) +{ + if (value != -1) + g_variant_builder_add(vb, "{sv}", key, + g_variant_new_int32(value)); +} + +static void prv_add_variant_prop(GVariantBuilder *vb, const gchar *key, + GVariant *prop) +{ + if (prop) + g_variant_builder_add(vb, "{sv}", key, prop); +} + +void dls_props_add_child_count(GVariantBuilder *item_vb, gint value) +{ + prv_add_int_prop(item_vb, DLS_INTERFACE_PROP_CHILD_COUNT, value); +} + +static void prv_add_bool_prop(GVariantBuilder *vb, const gchar *key, + gboolean value) +{ + DLEYNA_LOG_DEBUG("Prop %s = %u", key, value); + + g_variant_builder_add(vb, "{sv}", key, g_variant_new_boolean(value)); +} + +static void prv_add_int64_prop(GVariantBuilder *vb, const gchar *key, + gint64 value) +{ + if (value != -1) { + DLEYNA_LOG_DEBUG("Prop %s = %"G_GINT64_FORMAT, key, value); + + g_variant_builder_add(vb, "{sv}", key, + g_variant_new_int64(value)); + } +} + +static void prv_add_list_dlna_str(gpointer data, gpointer user_data) +{ + GVariantBuilder *vb = (GVariantBuilder *)user_data; + gchar *cap_str = (gchar *)data; + gchar *str; + int value = 0; + + if (g_str_has_prefix(cap_str, "srs-rt-retention-period-")) { + str = cap_str + strlen("srs-rt-retention-period-"); + cap_str = "srs-rt-retention-period"; + + if (*str) { + if (!g_strcmp0(str, "infinity")) + value = -1; + else + value = atoi(str); + } + } + + prv_add_uint_prop(vb, cap_str, value); +} + +static GVariant *prv_add_list_dlna_prop(GList *list) +{ + GVariantBuilder vb; + + g_variant_builder_init(&vb, G_VARIANT_TYPE("a{sv}")); + + g_list_foreach(list, prv_add_list_dlna_str, &vb); + + return g_variant_builder_end(&vb); +} + +static void prv_add_list_artists_str(gpointer data, gpointer user_data) +{ + GVariantBuilder *vb = (GVariantBuilder *)user_data; + GUPnPDIDLLiteContributor *contributor = data; + const char *str; + + str = gupnp_didl_lite_contributor_get_name(contributor); + g_variant_builder_add(vb, "s", str); +} + +static GVariant *prv_get_artists_prop(GList *list) +{ + GVariantBuilder vb; + + g_variant_builder_init(&vb, G_VARIANT_TYPE("as")); + g_list_foreach(list, prv_add_list_artists_str, &vb); + + return g_variant_builder_end(&vb); +} + +void dls_props_add_device(GUPnPDeviceInfo *proxy, + const dls_device_t *device, + GVariantBuilder *vb) +{ + gchar *str; + GList *list; + GVariant *dlna_caps; + + prv_add_string_prop(vb, DLS_INTERFACE_PROP_LOCATION, + gupnp_device_info_get_location(proxy)); + + prv_add_string_prop(vb, DLS_INTERFACE_PROP_UDN, + gupnp_device_info_get_udn(proxy)); + + prv_add_string_prop(vb, DLS_INTERFACE_PROP_DEVICE_TYPE, + gupnp_device_info_get_device_type(proxy)); + + str = gupnp_device_info_get_friendly_name(proxy); + prv_add_string_prop(vb, DLS_INTERFACE_PROP_FRIENDLY_NAME, str); + g_free(str); + + str = gupnp_device_info_get_manufacturer(proxy); + prv_add_string_prop(vb, DLS_INTERFACE_PROP_MANUFACTURER, str); + g_free(str); + + str = gupnp_device_info_get_manufacturer_url(proxy); + prv_add_string_prop(vb, DLS_INTERFACE_PROP_MANUFACTURER_URL, str); + g_free(str); + + str = gupnp_device_info_get_model_description(proxy); + prv_add_string_prop(vb, DLS_INTERFACE_PROP_MODEL_DESCRIPTION, str); + g_free(str); + + str = gupnp_device_info_get_model_name(proxy); + prv_add_string_prop(vb, DLS_INTERFACE_PROP_MODEL_NAME, str); + g_free(str); + + str = gupnp_device_info_get_model_number(proxy); + prv_add_string_prop(vb, DLS_INTERFACE_PROP_MODEL_NUMBER, str); + g_free(str); + + str = gupnp_device_info_get_model_url(proxy); + prv_add_string_prop(vb, DLS_INTERFACE_PROP_MODEL_URL, str); + g_free(str); + + str = gupnp_device_info_get_serial_number(proxy); + prv_add_string_prop(vb, DLS_INTERFACE_PROP_SERIAL_NUMBER, str); + g_free(str); + + str = gupnp_device_info_get_presentation_url(proxy); + prv_add_string_prop(vb, DLS_INTERFACE_PROP_PRESENTATION_URL, str); + g_free(str); + + str = gupnp_device_info_get_icon_url(proxy, NULL, -1, -1, -1, FALSE, + NULL, NULL, NULL, NULL); + prv_add_string_prop(vb, DLS_INTERFACE_PROP_ICON_URL, str); + g_free(str); + + list = gupnp_device_info_list_dlna_capabilities(proxy); + if (list != NULL) { + dlna_caps = prv_add_list_dlna_prop(list); + g_variant_builder_add(vb, "{sv}", + DLS_INTERFACE_PROP_SV_DLNA_CAPABILITIES, + dlna_caps); + g_list_free_full(list, g_free); + } + + if (device->search_caps != NULL) + g_variant_builder_add(vb, "{sv}", + DLS_INTERFACE_PROP_SV_SEARCH_CAPABILITIES, + device->search_caps); + + if (device->sort_caps != NULL) + g_variant_builder_add(vb, "{sv}", + DLS_INTERFACE_PROP_SV_SORT_CAPABILITIES, + device->sort_caps); + + if (device->sort_ext_caps != NULL) + g_variant_builder_add( + vb, "{sv}", + DLS_INTERFACE_PROP_SV_SORT_EXT_CAPABILITIES, + device->sort_ext_caps); + + if (device->feature_list != NULL) + g_variant_builder_add(vb, "{sv}", + DLS_INTERFACE_PROP_SV_FEATURE_LIST, + device->feature_list); +} + +GVariant *dls_props_get_device_prop(GUPnPDeviceInfo *proxy, + const dls_device_t *device, + const gchar *prop) +{ + GVariant *dlna_caps = NULL; + GVariant *retval = NULL; + const gchar *str = NULL; + gchar *copy = NULL; + GList *list; + + if (!strcmp(DLS_INTERFACE_PROP_LOCATION, prop)) { + str = gupnp_device_info_get_location(proxy); + } else if (!strcmp(DLS_INTERFACE_PROP_UDN, prop)) { + str = gupnp_device_info_get_udn(proxy); + } else if (!strcmp(DLS_INTERFACE_PROP_DEVICE_TYPE, prop)) { + str = gupnp_device_info_get_device_type(proxy); + } else if (!strcmp(DLS_INTERFACE_PROP_FRIENDLY_NAME, prop)) { + copy = gupnp_device_info_get_friendly_name(proxy); + str = copy; + } else if (!strcmp(DLS_INTERFACE_PROP_MANUFACTURER, prop)) { + copy = gupnp_device_info_get_manufacturer(proxy); + str = copy; + } else if (!strcmp(DLS_INTERFACE_PROP_MANUFACTURER_URL, prop)) { + copy = gupnp_device_info_get_manufacturer_url(proxy); + str = copy; + } else if (!strcmp(DLS_INTERFACE_PROP_MODEL_DESCRIPTION, prop)) { + copy = gupnp_device_info_get_model_description(proxy); + str = copy; + } else if (!strcmp(DLS_INTERFACE_PROP_MODEL_NAME, prop)) { + copy = gupnp_device_info_get_model_name(proxy); + str = copy; + } else if (!strcmp(DLS_INTERFACE_PROP_MODEL_NUMBER, prop)) { + copy = gupnp_device_info_get_model_number(proxy); + str = copy; + } else if (!strcmp(DLS_INTERFACE_PROP_MODEL_URL, prop)) { + copy = gupnp_device_info_get_model_url(proxy); + str = copy; + } else if (!strcmp(DLS_INTERFACE_PROP_SERIAL_NUMBER, prop)) { + copy = gupnp_device_info_get_serial_number(proxy); + str = copy; + } else if (!strcmp(DLS_INTERFACE_PROP_PRESENTATION_URL, prop)) { + copy = gupnp_device_info_get_presentation_url(proxy); + str = copy; + } else if (!strcmp(DLS_INTERFACE_PROP_ICON_URL, prop)) { + copy = gupnp_device_info_get_icon_url(proxy, NULL, + -1, -1, -1, FALSE, + NULL, NULL, NULL, NULL); + str = copy; + } else if (!strcmp(DLS_INTERFACE_PROP_SV_DLNA_CAPABILITIES, prop)) { + list = gupnp_device_info_list_dlna_capabilities(proxy); + if (list != NULL) { + dlna_caps = prv_add_list_dlna_prop(list); + g_list_free_full(list, g_free); + retval = g_variant_ref_sink(dlna_caps); + +#if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_DEBUG + copy = g_variant_print(dlna_caps, FALSE); + DLEYNA_LOG_DEBUG("Prop %s = %s", prop, copy); +#endif + } + } else if (!strcmp(DLS_INTERFACE_PROP_SV_SEARCH_CAPABILITIES, prop)) { + if (device->search_caps != NULL) { + retval = g_variant_ref(device->search_caps); + +#if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_DEBUG + copy = g_variant_print(device->search_caps, FALSE); + DLEYNA_LOG_DEBUG("Prop %s = %s", prop, copy); +#endif + } + } else if (!strcmp(DLS_INTERFACE_PROP_SV_SORT_CAPABILITIES, prop)) { + if (device->sort_caps != NULL) { + retval = g_variant_ref(device->sort_caps); + +#if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_DEBUG + copy = g_variant_print(device->sort_caps, FALSE); + DLEYNA_LOG_DEBUG("Prop %s = %s", prop, copy); +#endif + } + } else if (!strcmp(DLS_INTERFACE_PROP_SV_SORT_EXT_CAPABILITIES, prop)) { + if (device->sort_ext_caps != NULL) { + retval = g_variant_ref(device->sort_ext_caps); + +#if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_DEBUG + copy = g_variant_print(device->sort_ext_caps, FALSE); + DLEYNA_LOG_DEBUG("Prop %s = %s", prop, copy); +#endif + } + } else if (!strcmp(DLS_INTERFACE_PROP_SV_FEATURE_LIST, prop)) { + if (device->feature_list != NULL) { + retval = g_variant_ref(device->feature_list); + +#if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_DEBUG + copy = g_variant_print(device->feature_list, FALSE); + DLEYNA_LOG_DEBUG("Prop %s = %s", prop, copy); +#endif + } + } + + if (!retval) { + if (str) { + DLEYNA_LOG_DEBUG("Prop %s = %s", prop, str); + + retval = g_variant_ref_sink(g_variant_new_string(str)); + } +#if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_WARNING + else + DLEYNA_LOG_WARNING("Property %s not defined", prop); +#endif + } + + g_free(copy); + + return retval; +} + +static GUPnPDIDLLiteResource *prv_match_resource(GUPnPDIDLLiteResource *res, + gchar **pi_str_array) +{ + GUPnPDIDLLiteResource *retval = NULL; + GUPnPProtocolInfo *res_pi; + GUPnPProtocolInfo *pi; + unsigned int i; + gboolean match; + + if (!pi_str_array) { + retval = res; + goto done; + } + + res_pi = gupnp_didl_lite_resource_get_protocol_info(res); + if (!res_pi) + goto done; + + for (i = 0; pi_str_array[i]; ++i) { + pi = gupnp_protocol_info_new_from_string(pi_str_array[i], + NULL); + if (!pi) + continue; + match = gupnp_protocol_info_is_compatible(pi, res_pi); + g_object_unref(pi); + if (match) { + retval = res; + break; + } + } +done: + + return retval; +} + +static GUPnPDIDLLiteResource *prv_get_matching_resource + (GUPnPDIDLLiteObject *object, const gchar *protocol_info) +{ + GUPnPDIDLLiteResource *retval = NULL; + GUPnPDIDLLiteResource *res; + GList *resources; + GList *ptr; + gchar **pi_str_array = NULL; + + if (protocol_info) + pi_str_array = g_strsplit(protocol_info, ",", 0); + + resources = gupnp_didl_lite_object_get_resources(object); + ptr = resources; + + while (ptr) { + res = ptr->data; + if (!retval) { + retval = prv_match_resource(res, pi_str_array); + if (!retval) + g_object_unref(res); + } else { + g_object_unref(res); + } + + ptr = ptr->next; + } + + g_list_free(resources); + if (pi_str_array) + g_strfreev(pi_str_array); + + return retval; +} + +static void prv_parse_resources(GVariantBuilder *item_vb, + GUPnPDIDLLiteResource *res, + dls_upnp_prop_mask filter_mask) +{ + GUPnPProtocolInfo *protocol_info; + int int_val; + gint64 int64_val; + const char *str_val; + guint uint_val; + + if (filter_mask & DLS_UPNP_MASK_PROP_SIZE) { + int64_val = gupnp_didl_lite_resource_get_size64(res); + prv_add_int64_prop(item_vb, DLS_INTERFACE_PROP_SIZE, int64_val); + } + + if (filter_mask & DLS_UPNP_MASK_PROP_BITRATE) { + int_val = gupnp_didl_lite_resource_get_bitrate(res); + prv_add_int_prop(item_vb, DLS_INTERFACE_PROP_BITRATE, int_val); + } + + if (filter_mask & DLS_UPNP_MASK_PROP_SAMPLE_RATE) { + int_val = gupnp_didl_lite_resource_get_sample_freq(res); + prv_add_int_prop(item_vb, DLS_INTERFACE_PROP_SAMPLE_RATE, + int_val); + } + + if (filter_mask & DLS_UPNP_MASK_PROP_BITS_PER_SAMPLE) { + int_val = gupnp_didl_lite_resource_get_bits_per_sample(res); + prv_add_int_prop(item_vb, DLS_INTERFACE_PROP_BITS_PER_SAMPLE, + int_val); + } + + if (filter_mask & DLS_UPNP_MASK_PROP_DURATION) { + int_val = (int) gupnp_didl_lite_resource_get_duration(res); + prv_add_int_prop(item_vb, DLS_INTERFACE_PROP_DURATION, int_val); + } + + if (filter_mask & DLS_UPNP_MASK_PROP_WIDTH) { + int_val = (int) gupnp_didl_lite_resource_get_width(res); + prv_add_int_prop(item_vb, DLS_INTERFACE_PROP_WIDTH, int_val); + } + + if (filter_mask & DLS_UPNP_MASK_PROP_HEIGHT) { + int_val = (int) gupnp_didl_lite_resource_get_height(res); + prv_add_int_prop(item_vb, DLS_INTERFACE_PROP_HEIGHT, int_val); + } + + if (filter_mask & DLS_UPNP_MASK_PROP_COLOR_DEPTH) { + int_val = (int) gupnp_didl_lite_resource_get_color_depth(res); + prv_add_int_prop(item_vb, DLS_INTERFACE_PROP_COLOR_DEPTH, + int_val); + } + + if (filter_mask & DLS_UPNP_MASK_PROP_UPDATE_COUNT) { + uint_val = gupnp_didl_lite_resource_get_update_count(res); + prv_add_uint_prop(item_vb, DLS_INTERFACE_PROP_UPDATE_COUNT, + uint_val); + } + + protocol_info = gupnp_didl_lite_resource_get_protocol_info(res); + + if (filter_mask & DLS_UPNP_MASK_PROP_DLNA_PROFILE) { + str_val = gupnp_protocol_info_get_dlna_profile(protocol_info); + prv_add_string_prop(item_vb, DLS_INTERFACE_PROP_DLNA_PROFILE, + str_val); + } + + if (filter_mask & DLS_UPNP_MASK_PROP_MIME_TYPE) { + str_val = gupnp_protocol_info_get_mime_type(protocol_info); + prv_add_string_prop(item_vb, DLS_INTERFACE_PROP_MIME_TYPE, + str_val); + } +} + +static GVariant *prv_compute_create_classes(GUPnPDIDLLiteContainer *container) +{ + GVariantBuilder create_classes_vb; + GList *create_classes; + GList *ptr; + GUPnPDIDLLiteCreateClass *create_class; + const char *content; + gboolean inc_derived; + + g_variant_builder_init(&create_classes_vb, G_VARIANT_TYPE("a(sb)")); + + create_classes = gupnp_didl_lite_container_get_create_classes_full( + container); + ptr = create_classes; + while (ptr) { + create_class = ptr->data; + content = gupnp_didl_lite_create_class_get_content( + create_class); + inc_derived = gupnp_didl_lite_create_class_get_include_derived( + create_class); + g_variant_builder_add(&create_classes_vb, + "(sb)", content, inc_derived); + g_object_unref(ptr->data); + ptr = g_list_next(ptr); + } + g_list_free(create_classes); + + return g_variant_builder_end(&create_classes_vb); +} + +const gchar *dls_props_media_spec_to_upnp_class(const gchar *m2spec_class) +{ + const gchar *retval = NULL; + + if (!strcmp(m2spec_class, gMediaSpec2AlbumPhoto)) + retval = gUPnPPhotoAlbum; + else if (!strcmp(m2spec_class, gMediaSpec2AlbumMusic)) + retval = gUPnPMusicAlbum; + else if (!strcmp(m2spec_class, gMediaSpec2Album)) + retval = gUPnPAlbum; + else if (!strcmp(m2spec_class, gMediaSpec2PersonMusicArtist)) + retval = gUPnPMusicArtist; + else if (!strcmp(m2spec_class, gMediaSpec2Person)) + retval = gUPnPPerson; + else if (!strcmp(m2spec_class, gMediaSpec2GenreMovie)) + retval = gUPnPMovieGenre; + else if (!strcmp(m2spec_class, gMediaSpec2GenreMusic)) + retval = gUPnPMusicGenre; + else if (!strcmp(m2spec_class, gMediaSpec2Genre)) + retval = gUPnPGenre; + else if (!strcmp(m2spec_class, gMediaSpec2Container)) + retval = gUPnPContainer; + else if (!strcmp(m2spec_class, gMediaSpec2AudioMusic)) + retval = gUPnPMusicTrack; + else if (!strcmp(m2spec_class, gMediaSpec2AudioBroadcast)) + retval = gUPnPAudioBroadcast; + else if (!strcmp(m2spec_class, gMediaSpec2AudioBook)) + retval = gUPnPAudioBook; + else if (!strcmp(m2spec_class, gMediaSpec2Audio)) + retval = gUPnPAudioItem; + else if (!strcmp(m2spec_class, gMediaSpec2VideoMovie)) + retval = gUPnPMovie; + else if (!strcmp(m2spec_class, gMediaSpec2VideoMusicClip)) + retval = gUPnPMusicVideoClip; + else if (!strcmp(m2spec_class, gMediaSpec2VideoBroadcast)) + retval = gUPnPVideoBroadcast; + else if (!strcmp(m2spec_class, gMediaSpec2Video)) + retval = gUPnPVideoItem; + else if (!strcmp(m2spec_class, gMediaSpec2ImagePhoto)) + retval = gUPnPPhoto; + else if (!strcmp(m2spec_class, gMediaSpec2Image)) + retval = gUPnPImageItem; + else if (!strcmp(m2spec_class, gMediaSpec2Playlist)) + retval = gUPnPPlaylistItem; + else if (!strcmp(m2spec_class, gMediaSpec2Item)) + retval = gUPnPItem; + + return retval; +} + +const gchar *dls_props_upnp_class_to_media_spec(const gchar *upnp_class) +{ + const gchar *retval = NULL; + const gchar *ptr; + + if (!strncmp(upnp_class, gUPnPAlbum, gUPnPAlbumLen)) { + ptr = upnp_class + gUPnPAlbumLen; + if (!strcmp(ptr, ".photoAlbum")) + retval = gMediaSpec2AlbumPhoto; + else if (!strcmp(ptr, ".musicAlbum")) + retval = gMediaSpec2AlbumMusic; + else + retval = gMediaSpec2Album; + } else if (!strncmp(upnp_class, gUPnPPerson, gUPnPPersonLen)) { + ptr = upnp_class + gUPnPPersonLen; + if (!strcmp(ptr, ".musicArtist")) + retval = gMediaSpec2PersonMusicArtist; + else + retval = gMediaSpec2Person; + } else if (!strncmp(upnp_class, gUPnPGenre, gUPnPGenreLen)) { + ptr = upnp_class + gUPnPGenreLen; + if (!strcmp(ptr, ".movieGenre")) + retval = gMediaSpec2GenreMovie; + else if (!strcmp(ptr, ".musicGenre")) + retval = gMediaSpec2GenreMusic; + else + retval = gMediaSpec2Genre; + } else if (!strncmp(upnp_class, gUPnPContainer, gUPnPContainerLen)) { + ptr = upnp_class + gUPnPContainerLen; + if (!*ptr || *ptr == '.') + retval = gMediaSpec2Container; + } else if (!strncmp(upnp_class, gUPnPAudioItem, gUPnPAudioItemLen)) { + ptr = upnp_class + gUPnPAudioItemLen; + if (!strcmp(ptr, ".musicTrack")) + retval = gMediaSpec2AudioMusic; + else if (!strcmp(ptr, ".audioBroadcast")) + retval = gMediaSpec2AudioBroadcast; + else if (!strcmp(ptr, ".audioBook")) + retval = gMediaSpec2AudioBook; + else + retval = gMediaSpec2Audio; + } else if (!strncmp(upnp_class, gUPnPVideoItem, gUPnPVideoItemLen)) { + ptr = upnp_class + gUPnPVideoItemLen; + if (!strcmp(ptr, ".movie")) + retval = gMediaSpec2VideoMovie; + else if (!strcmp(ptr, ".musicVideoClip")) + retval = gMediaSpec2VideoMusicClip; + else if (!strcmp(ptr, ".videoBroadcast")) + retval = gMediaSpec2VideoBroadcast; + else + retval = gMediaSpec2Video; + } else if (!strncmp(upnp_class, gUPnPImageItem, gUPnPImageItemLen)) { + ptr = upnp_class + gUPnPImageItemLen; + if (!strcmp(ptr, ".photo")) + retval = gMediaSpec2ImagePhoto; + else + retval = gMediaSpec2Image; + } else if (!strncmp(upnp_class, gUPnPPlaylistItem, + gUPnPPlaylistItemLen)) { + retval = gMediaSpec2Playlist; + } else if (!strncmp(upnp_class, gUPnPItem, gUPnPItemLen)) { + ptr = upnp_class + gUPnPItemLen; + if (!*ptr || *ptr == '.') + retval = gMediaSpec2Item; + } + + return retval; +} + +static GVariant *prv_props_get_dlna_managed_dict(GUPnPOCMFlags flags) +{ + GVariantBuilder builder; + gboolean managed; + + g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sb}")); + + managed = (flags & GUPNP_OCM_FLAGS_UPLOAD); + g_variant_builder_add(&builder, "{sb}", "Upload", managed); + + managed = (flags & GUPNP_OCM_FLAGS_CREATE_CONTAINER); + g_variant_builder_add(&builder, "{sb}", "CreateContainer", managed); + + managed = (flags & GUPNP_OCM_FLAGS_DESTROYABLE); + g_variant_builder_add(&builder, "{sb}", "Delete", managed); + + managed = (flags & GUPNP_OCM_FLAGS_UPLOAD_DESTROYABLE); + g_variant_builder_add(&builder, "{sb}", "UploadDelete", managed); + + managed = (flags & GUPNP_OCM_FLAGS_CHANGE_METADATA); + g_variant_builder_add(&builder, "{sb}", "ChangeMeta", managed); + + return g_variant_builder_end(&builder); +} + +gboolean dls_props_add_object(GVariantBuilder *item_vb, + GUPnPDIDLLiteObject *object, + const char *root_path, + const gchar *parent_path, + dls_upnp_prop_mask filter_mask) +{ + gchar *path = NULL; + const char *id; + const char *title; + const char *creator; + const char *upnp_class; + const char *media_spec_type; + gboolean retval = FALSE; + gboolean rest; + GUPnPOCMFlags flags; + guint uint_val; + + id = gupnp_didl_lite_object_get_id(object); + if (!id) + goto on_error; + + upnp_class = gupnp_didl_lite_object_get_upnp_class(object); + media_spec_type = dls_props_upnp_class_to_media_spec(upnp_class); + + if (!media_spec_type) + goto on_error; + + title = gupnp_didl_lite_object_get_title(object); + creator = gupnp_didl_lite_object_get_creator(object); + rest = gupnp_didl_lite_object_get_restricted(object); + path = dls_path_from_id(root_path, id); + + if (filter_mask & DLS_UPNP_MASK_PROP_DISPLAY_NAME) + prv_add_string_prop(item_vb, DLS_INTERFACE_PROP_DISPLAY_NAME, + title); + + if (filter_mask & DLS_UPNP_MASK_PROP_CREATOR) + prv_add_string_prop(item_vb, DLS_INTERFACE_PROP_CREATOR, + creator); + + if (filter_mask & DLS_UPNP_MASK_PROP_PATH) + prv_add_path_prop(item_vb, DLS_INTERFACE_PROP_PATH, path); + + if (filter_mask & DLS_UPNP_MASK_PROP_PARENT) + prv_add_path_prop(item_vb, DLS_INTERFACE_PROP_PARENT, + parent_path); + + if (filter_mask & DLS_UPNP_MASK_PROP_TYPE) + prv_add_string_prop(item_vb, DLS_INTERFACE_PROP_TYPE, + media_spec_type); + + if (filter_mask & DLS_UPNP_MASK_PROP_RESTRICTED) + prv_add_bool_prop(item_vb, DLS_INTERFACE_PROP_RESTRICTED, rest); + + if (filter_mask & DLS_UPNP_MASK_PROP_DLNA_MANAGED) { + flags = gupnp_didl_lite_object_get_dlna_managed(object); + prv_add_variant_prop(item_vb, + DLS_INTERFACE_PROP_DLNA_MANAGED, + prv_props_get_dlna_managed_dict(flags)); + } + + if (filter_mask & DLS_UPNP_MASK_PROP_OBJECT_UPDATE_ID) { + uint_val = gupnp_didl_lite_object_get_update_id(object); + prv_add_uint_prop(item_vb, DLS_INTERFACE_PROP_OBJECT_UPDATE_ID, + uint_val); + } + + retval = TRUE; + +on_error: + + g_free(path); + + return retval; +} + +void dls_props_add_container(GVariantBuilder *item_vb, + GUPnPDIDLLiteContainer *object, + dls_upnp_prop_mask filter_mask, + gboolean *have_child_count) +{ + int child_count; + gboolean searchable; + guint uint_val; + + *have_child_count = FALSE; + if (filter_mask & DLS_UPNP_MASK_PROP_CHILD_COUNT) { + child_count = gupnp_didl_lite_container_get_child_count(object); + if (child_count >= 0) { + prv_add_uint_prop(item_vb, + DLS_INTERFACE_PROP_CHILD_COUNT, + (unsigned int) child_count); + *have_child_count = TRUE; + } + } + + if (filter_mask & DLS_UPNP_MASK_PROP_SEARCHABLE) { + searchable = gupnp_didl_lite_container_get_searchable(object); + prv_add_bool_prop(item_vb, DLS_INTERFACE_PROP_SEARCHABLE, + searchable); + } + + if (filter_mask & DLS_UPNP_MASK_PROP_CREATE_CLASSES) + prv_add_variant_prop(item_vb, + DLS_INTERFACE_PROP_CREATE_CLASSES, + prv_compute_create_classes(object)); + + if (filter_mask & DLS_UPNP_MASK_PROP_CONTAINER_UPDATE_ID) { + uint_val = gupnp_didl_lite_container_get_container_update_id( + object); + prv_add_uint_prop(item_vb, + DLS_INTERFACE_PROP_CONTAINER_UPDATE_ID, + uint_val); + } + + if (filter_mask & DLS_UPNP_MASK_PROP_TOTAL_DELETED_CHILD_COUNT) { + uint_val = + gupnp_didl_lite_container_get_total_deleted_child_count(object); + prv_add_uint_prop(item_vb, + DLS_INTERFACE_PROP_TOTAL_DELETED_CHILD_COUNT, + uint_val); + } +} + +static GVariant *prv_compute_resources(GUPnPDIDLLiteObject *object, + dls_upnp_prop_mask filter_mask) +{ + GUPnPDIDLLiteResource *res = NULL; + GList *resources; + GList *ptr; + GVariantBuilder *res_array_vb; + GVariantBuilder *res_vb; + const char *str_val; + GVariant *retval; + + res_array_vb = g_variant_builder_new(G_VARIANT_TYPE("aa{sv}")); + + resources = gupnp_didl_lite_object_get_resources(object); + ptr = resources; + + while (ptr) { + res = ptr->data; + res_vb = g_variant_builder_new(G_VARIANT_TYPE("a{sv}")); + if (filter_mask & DLS_UPNP_MASK_PROP_URL) { + str_val = gupnp_didl_lite_resource_get_uri(res); + if (str_val) + prv_add_string_prop(res_vb, + DLS_INTERFACE_PROP_URL, + str_val); + } + prv_parse_resources(res_vb, res, filter_mask); + g_variant_builder_add(res_array_vb, "@a{sv}", + g_variant_builder_end(res_vb)); + g_variant_builder_unref(res_vb); + g_object_unref(ptr->data); + ptr = g_list_next(ptr); + } + retval = g_variant_builder_end(res_array_vb); + g_variant_builder_unref(res_array_vb); + + g_list_free(resources); + + return retval; +} + +static void prv_add_resources(GVariantBuilder *item_vb, + GUPnPDIDLLiteObject *object, + dls_upnp_prop_mask filter_mask) +{ + GVariant *val; + + val = prv_compute_resources(object, filter_mask); + g_variant_builder_add(item_vb, "{sv}", DLS_INTERFACE_PROP_RESOURCES, + val); +} + +void dls_props_add_item(GVariantBuilder *item_vb, + GUPnPDIDLLiteObject *object, + const gchar *root_path, + dls_upnp_prop_mask filter_mask, + const gchar *protocol_info) +{ + int track_number; + GUPnPDIDLLiteResource *res; + const char *str_val; + char *path; + GList *list; + + if (filter_mask & DLS_UPNP_MASK_PROP_ARTIST) + prv_add_string_prop(item_vb, DLS_INTERFACE_PROP_ARTIST, + gupnp_didl_lite_object_get_artist(object)); + + if (filter_mask & DLS_UPNP_MASK_PROP_ARTISTS) { + list = gupnp_didl_lite_object_get_artists(object); + prv_add_variant_prop(item_vb, DLS_INTERFACE_PROP_ARTISTS, + prv_get_artists_prop(list)); + g_list_free_full(list, g_object_unref); + } + + if (filter_mask & DLS_UPNP_MASK_PROP_ALBUM) + prv_add_string_prop(item_vb, DLS_INTERFACE_PROP_ALBUM, + gupnp_didl_lite_object_get_album(object)); + + if (filter_mask & DLS_UPNP_MASK_PROP_DATE) + prv_add_string_prop(item_vb, DLS_INTERFACE_PROP_DATE, + gupnp_didl_lite_object_get_date(object)); + + if (filter_mask & DLS_UPNP_MASK_PROP_GENRE) + prv_add_string_prop(item_vb, DLS_INTERFACE_PROP_GENRE, + gupnp_didl_lite_object_get_genre(object)); + + if (filter_mask & DLS_UPNP_MASK_PROP_TRACK_NUMBER) { + track_number = gupnp_didl_lite_object_get_track_number(object); + if (track_number >= 0) + prv_add_int_prop(item_vb, + DLS_INTERFACE_PROP_TRACK_NUMBER, + track_number); + } + + if (filter_mask & DLS_UPNP_MASK_PROP_ALBUM_ART_URL) + prv_add_string_prop(item_vb, DLS_INTERFACE_PROP_ALBUM_ART_URL, + gupnp_didl_lite_object_get_album_art( + object)); + + if (filter_mask & DLS_UPNP_MASK_PROP_REFPATH) { + str_val = gupnp_didl_lite_item_get_ref_id( + GUPNP_DIDL_LITE_ITEM(object)); + if (str_val != NULL) { + path = dls_path_from_id(root_path, str_val); + prv_add_path_prop(item_vb, DLS_INTERFACE_PROP_REFPATH, + path); + g_free(path); + } + } + + res = prv_get_matching_resource(object, protocol_info); + if (res) { + if (filter_mask & DLS_UPNP_MASK_PROP_URLS) { + str_val = gupnp_didl_lite_resource_get_uri(res); + if (str_val) + prv_add_strv_prop(item_vb, + DLS_INTERFACE_PROP_URLS, + &str_val, 1); + } + prv_parse_resources(item_vb, res, filter_mask); + g_object_unref(res); + } + + if (filter_mask & DLS_UPNP_MASK_PROP_RESOURCES) + prv_add_resources(item_vb, object, filter_mask); +} + +void dls_props_add_resource(GVariantBuilder *item_vb, + GUPnPDIDLLiteObject *object, + dls_upnp_prop_mask filter_mask, + const gchar *protocol_info) +{ + GUPnPDIDLLiteResource *res; + const char *str_val; + + res = prv_get_matching_resource(object, protocol_info); + if (res) { + if (filter_mask & DLS_UPNP_MASK_PROP_URL) { + str_val = gupnp_didl_lite_resource_get_uri(res); + if (str_val) + prv_add_string_prop(item_vb, + DLS_INTERFACE_PROP_URL, + str_val); + } + prv_parse_resources(item_vb, res, filter_mask); + g_object_unref(res); + } +} + + +static GVariant *prv_get_resource_property(const gchar *prop, + GUPnPDIDLLiteResource *res) +{ + int int_val; + gint64 int64_val; + const char *str_val; + GVariant *retval = NULL; + GUPnPProtocolInfo *protocol_info; + + if (!strcmp(prop, DLS_INTERFACE_PROP_DLNA_PROFILE)) { + protocol_info = gupnp_didl_lite_resource_get_protocol_info(res); + if (!protocol_info) + goto on_error; + str_val = gupnp_protocol_info_get_dlna_profile(protocol_info); + if (!str_val) + goto on_error; + retval = g_variant_ref_sink(g_variant_new_string(str_val)); + } else if (!strcmp(prop, DLS_INTERFACE_PROP_MIME_TYPE)) { + protocol_info = gupnp_didl_lite_resource_get_protocol_info(res); + if (!protocol_info) + goto on_error; + str_val = gupnp_protocol_info_get_mime_type(protocol_info); + if (!str_val) + goto on_error; + retval = g_variant_ref_sink(g_variant_new_string(str_val)); + } else if (!strcmp(prop, DLS_INTERFACE_PROP_SIZE)) { + int64_val = gupnp_didl_lite_resource_get_size64(res); + if (int64_val == -1) + goto on_error; + retval = g_variant_ref_sink(g_variant_new_int64(int64_val)); + } else if (!strcmp(prop, DLS_INTERFACE_PROP_DURATION)) { + int_val = (int) gupnp_didl_lite_resource_get_duration(res); + if (int_val == -1) + goto on_error; + retval = g_variant_ref_sink(g_variant_new_int32(int_val)); + } else if (!strcmp(prop, DLS_INTERFACE_PROP_BITRATE)) { + int_val = gupnp_didl_lite_resource_get_bitrate(res); + if (int_val == -1) + goto on_error; + retval = g_variant_ref_sink(g_variant_new_int32(int_val)); + } else if (!strcmp(prop, DLS_INTERFACE_PROP_SAMPLE_RATE)) { + int_val = gupnp_didl_lite_resource_get_sample_freq(res); + if (int_val == -1) + goto on_error; + retval = g_variant_ref_sink(g_variant_new_int32(int_val)); + } else if (!strcmp(prop, DLS_INTERFACE_PROP_BITS_PER_SAMPLE)) { + int_val = gupnp_didl_lite_resource_get_bits_per_sample(res); + if (int_val == -1) + goto on_error; + retval = g_variant_ref_sink(g_variant_new_int32(int_val)); + } else if (!strcmp(prop, DLS_INTERFACE_PROP_WIDTH)) { + int_val = (int) gupnp_didl_lite_resource_get_width(res); + if (int_val == -1) + goto on_error; + retval = g_variant_ref_sink(g_variant_new_int32(int_val)); + } else if (!strcmp(prop, DLS_INTERFACE_PROP_HEIGHT)) { + int_val = (int) gupnp_didl_lite_resource_get_height(res); + if (int_val == -1) + goto on_error; + retval = g_variant_ref_sink(g_variant_new_int32(int_val)); + } else if (!strcmp(prop, DLS_INTERFACE_PROP_COLOR_DEPTH)) { + int_val = (int) gupnp_didl_lite_resource_get_color_depth(res); + if (int_val == -1) + goto on_error; + retval = g_variant_ref_sink(g_variant_new_int32(int_val)); + } else if (!strcmp(prop, DLS_INTERFACE_PROP_URLS)) { + str_val = gupnp_didl_lite_resource_get_uri(res); + if (str_val) + retval = g_variant_new_strv(&str_val, 1); + } + +on_error: + + return retval; +} + +GVariant *dls_props_get_object_prop(const gchar *prop, const gchar *root_path, + GUPnPDIDLLiteObject *object) +{ + const char *id; + gchar *path; + const char *upnp_class; + const char *media_spec_type; + const char *title; + gboolean rest; + GVariant *retval = NULL; + GUPnPOCMFlags dlna_managed; + guint uint_val; + + if (!strcmp(prop, DLS_INTERFACE_PROP_PARENT)) { + id = gupnp_didl_lite_object_get_parent_id(object); + if (!id || !strcmp(id, "-1")) { + DLEYNA_LOG_DEBUG("Prop %s = %s", prop, root_path); + + retval = g_variant_ref_sink(g_variant_new_string( + root_path)); + } else { + path = dls_path_from_id(root_path, id); + + DLEYNA_LOG_DEBUG("Prop %s = %s", prop, path); + + retval = g_variant_ref_sink(g_variant_new_string( + path)); + g_free(path); + } + } else if (!strcmp(prop, DLS_INTERFACE_PROP_PATH)) { + id = gupnp_didl_lite_object_get_id(object); + if (!id) + goto on_error; + + path = dls_path_from_id(root_path, id); + + DLEYNA_LOG_DEBUG("Prop %s = %s", prop, path); + + retval = g_variant_ref_sink(g_variant_new_string(path)); + g_free(path); + } else if (!strcmp(prop, DLS_INTERFACE_PROP_TYPE)) { + upnp_class = gupnp_didl_lite_object_get_upnp_class(object); + media_spec_type = + dls_props_upnp_class_to_media_spec(upnp_class); + if (!media_spec_type) + goto on_error; + + DLEYNA_LOG_DEBUG("Prop %s = %s", prop, media_spec_type); + + retval = g_variant_ref_sink(g_variant_new_string( + media_spec_type)); + } else if (!strcmp(prop, DLS_INTERFACE_PROP_DISPLAY_NAME)) { + title = gupnp_didl_lite_object_get_title(object); + if (!title) + goto on_error; + + DLEYNA_LOG_DEBUG("Prop %s = %s", prop, title); + + retval = g_variant_ref_sink(g_variant_new_string(title)); + } else if (!strcmp(prop, DLS_INTERFACE_PROP_CREATOR)) { + title = gupnp_didl_lite_object_get_creator(object); + if (!title) + goto on_error; + + DLEYNA_LOG_DEBUG("Prop %s = %s", prop, title); + + retval = g_variant_ref_sink(g_variant_new_string(title)); + } else if (!strcmp(prop, DLS_INTERFACE_PROP_RESTRICTED)) { + rest = gupnp_didl_lite_object_get_restricted(object); + + DLEYNA_LOG_DEBUG("Prop %s = %d", prop, rest); + + retval = g_variant_ref_sink(g_variant_new_boolean(rest)); + } else if (!strcmp(prop, DLS_INTERFACE_PROP_DLNA_MANAGED)) { + dlna_managed = gupnp_didl_lite_object_get_dlna_managed(object); + + DLEYNA_LOG_DEBUG("Prop %s = %0x", prop, dlna_managed); + + retval = g_variant_ref_sink( + prv_props_get_dlna_managed_dict(dlna_managed)); + } else if (!strcmp(prop, DLS_INTERFACE_PROP_OBJECT_UPDATE_ID)) { + uint_val = gupnp_didl_lite_object_get_update_id(object); + + DLEYNA_LOG_DEBUG("Prop %s = %u", prop, uint_val); + + retval = g_variant_ref_sink(g_variant_new_uint32(uint_val)); + } + +on_error: + + return retval; +} + +GVariant *dls_props_get_item_prop(const gchar *prop, const gchar *root_path, + GUPnPDIDLLiteObject *object, + const gchar *protocol_info) +{ + const gchar *str; + gchar *path; + gint track_number; + GUPnPDIDLLiteResource *res; + GVariant *retval = NULL; + GList *list; + + if (GUPNP_IS_DIDL_LITE_CONTAINER(object)) + goto on_error; + + if (!strcmp(prop, DLS_INTERFACE_PROP_ARTIST)) { + str = gupnp_didl_lite_object_get_artist(object); + if (!str) + goto on_error; + + DLEYNA_LOG_DEBUG("Prop %s = %s", prop, str); + + retval = g_variant_ref_sink(g_variant_new_string(str)); + } else if (!strcmp(prop, DLS_INTERFACE_PROP_ARTISTS)) { + list = gupnp_didl_lite_object_get_artists(object); + if (!list) + goto on_error; + + retval = g_variant_ref_sink(prv_get_artists_prop(list)); + g_list_free_full(list, g_object_unref); + +#if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_DEBUG + path = g_variant_print(retval, FALSE); + DLEYNA_LOG_DEBUG("Prop %s = %s", prop, path); + g_free(path); +#endif + } else if (!strcmp(prop, DLS_INTERFACE_PROP_ALBUM)) { + str = gupnp_didl_lite_object_get_album(object); + if (!str) + goto on_error; + + DLEYNA_LOG_DEBUG("Prop %s = %s", prop, str); + + retval = g_variant_ref_sink(g_variant_new_string(str)); + } else if (!strcmp(prop, DLS_INTERFACE_PROP_DATE)) { + str = gupnp_didl_lite_object_get_date(object); + if (!str) + goto on_error; + + DLEYNA_LOG_DEBUG("Prop %s = %s", prop, str); + + retval = g_variant_ref_sink(g_variant_new_string(str)); + } else if (!strcmp(prop, DLS_INTERFACE_PROP_GENRE)) { + str = gupnp_didl_lite_object_get_genre(object); + if (!str) + goto on_error; + + DLEYNA_LOG_DEBUG("Prop %s = %s", prop, str); + + retval = g_variant_ref_sink(g_variant_new_string(str)); + } else if (!strcmp(prop, DLS_INTERFACE_PROP_TRACK_NUMBER)) { + track_number = gupnp_didl_lite_object_get_track_number(object); + if (track_number < 0) + goto on_error; + + DLEYNA_LOG_DEBUG("Prop %s = %d", prop, track_number); + + retval = g_variant_ref_sink( + g_variant_new_int32(track_number)); + } else if (!strcmp(prop, DLS_INTERFACE_PROP_ALBUM_ART_URL)) { + str = gupnp_didl_lite_object_get_album_art(object); + if (!str) + goto on_error; + + DLEYNA_LOG_DEBUG("Prop %s = %s", prop, str); + + retval = g_variant_ref_sink(g_variant_new_string(str)); + } else if (!strcmp(prop, DLS_INTERFACE_PROP_REFPATH)) { + str = gupnp_didl_lite_item_get_ref_id( + GUPNP_DIDL_LITE_ITEM(object)); + if (!str) + goto on_error; + + DLEYNA_LOG_DEBUG("Prop %s = %s", prop, str); + + path = dls_path_from_id(root_path, str); + retval = g_variant_ref_sink(g_variant_new_string(path)); + g_free(path); + } else if (!strcmp(prop, DLS_INTERFACE_PROP_RESOURCES)) { + retval = g_variant_ref_sink( + prv_compute_resources(object, DLS_UPNP_MASK_ALL_PROPS)); + } else { + res = prv_get_matching_resource(object, protocol_info); + if (!res) + goto on_error; + + retval = prv_get_resource_property(prop, res); + + g_object_unref(res); + } + +on_error: + + return retval; +} + +GVariant *dls_props_get_container_prop(const gchar *prop, + GUPnPDIDLLiteObject *object) +{ + gint child_count; + gboolean searchable; + GUPnPDIDLLiteContainer *container; + GVariant *retval = NULL; + guint uint_val; +#if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_DEBUG + gchar *create_classes; +#endif + if (!GUPNP_IS_DIDL_LITE_CONTAINER(object)) + goto on_error; + + container = (GUPnPDIDLLiteContainer *)object; + if (!strcmp(prop, DLS_INTERFACE_PROP_CHILD_COUNT)) { + child_count = + gupnp_didl_lite_container_get_child_count(container); + + DLEYNA_LOG_DEBUG("Prop %s = %d", prop, child_count); + + if (child_count >= 0) { + retval = g_variant_new_uint32((guint) child_count); + retval = g_variant_ref_sink(retval); + } + } else if (!strcmp(prop, DLS_INTERFACE_PROP_SEARCHABLE)) { + searchable = + gupnp_didl_lite_container_get_searchable(container); + + DLEYNA_LOG_DEBUG("Prop %s = %d", prop, searchable); + + retval = g_variant_ref_sink( + g_variant_new_boolean(searchable)); + } else if (!strcmp(prop, DLS_INTERFACE_PROP_CREATE_CLASSES)) { + retval = g_variant_ref_sink( + prv_compute_create_classes(container)); +#if DLEYNA_LOG_LEVEL & DLEYNA_LOG_LEVEL_DEBUG + create_classes = g_variant_print(retval, FALSE); + DLEYNA_LOG_DEBUG("Prop %s = %s", prop, create_classes); + g_free(create_classes); +#endif + } else if (!strcmp(prop, DLS_INTERFACE_PROP_CONTAINER_UPDATE_ID)) { + uint_val = gupnp_didl_lite_container_get_container_update_id( + container); + + DLEYNA_LOG_DEBUG("Prop %s = %u", prop, uint_val); + + retval = g_variant_ref_sink(g_variant_new_uint32(uint_val)); + } else if (!strcmp(prop, + DLS_INTERFACE_PROP_TOTAL_DELETED_CHILD_COUNT)) { + uint_val = + gupnp_didl_lite_container_get_total_deleted_child_count( + container); + + DLEYNA_LOG_DEBUG("Prop %s = %u", prop, uint_val); + + retval = g_variant_ref_sink(g_variant_new_uint32(uint_val)); + } + +on_error: + + return retval; +} diff --git a/libdleyna/server/props.h b/libdleyna/server/props.h new file mode 100644 index 0000000..610fcea --- /dev/null +++ b/libdleyna/server/props.h @@ -0,0 +1,134 @@ +/* + * 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 DLS_PROPS_H__ +#define DLS_PROPS_H__ + +#include <libgupnp-av/gupnp-av.h> +#include "async.h" + +#define DLS_UPNP_MASK_PROP_PARENT (1LL << 0) +#define DLS_UPNP_MASK_PROP_TYPE (1LL << 1) +#define DLS_UPNP_MASK_PROP_PATH (1LL << 2) +#define DLS_UPNP_MASK_PROP_DISPLAY_NAME (1LL << 3) +#define DLS_UPNP_MASK_PROP_CHILD_COUNT (1LL << 4) +#define DLS_UPNP_MASK_PROP_SEARCHABLE (1LL << 5) +#define DLS_UPNP_MASK_PROP_URLS (1LL << 6) +#define DLS_UPNP_MASK_PROP_MIME_TYPE (1LL << 7) +#define DLS_UPNP_MASK_PROP_ARTIST (1LL << 8) +#define DLS_UPNP_MASK_PROP_ALBUM (1LL << 9) +#define DLS_UPNP_MASK_PROP_DATE (1LL << 10) +#define DLS_UPNP_MASK_PROP_GENRE (1LL << 11) +#define DLS_UPNP_MASK_PROP_DLNA_PROFILE (1LL << 12) +#define DLS_UPNP_MASK_PROP_TRACK_NUMBER (1LL << 13) +#define DLS_UPNP_MASK_PROP_SIZE (1LL << 14) +#define DLS_UPNP_MASK_PROP_DURATION (1LL << 15) +#define DLS_UPNP_MASK_PROP_BITRATE (1LL << 16) +#define DLS_UPNP_MASK_PROP_SAMPLE_RATE (1LL << 17) +#define DLS_UPNP_MASK_PROP_BITS_PER_SAMPLE (1LL << 18) +#define DLS_UPNP_MASK_PROP_WIDTH (1LL << 19) +#define DLS_UPNP_MASK_PROP_HEIGHT (1LL << 20) +#define DLS_UPNP_MASK_PROP_COLOR_DEPTH (1LL << 21) +#define DLS_UPNP_MASK_PROP_ALBUM_ART_URL (1LL << 22) +#define DLS_UPNP_MASK_PROP_RESOURCES (1LL << 23) +#define DLS_UPNP_MASK_PROP_URL (1LL << 24) +#define DLS_UPNP_MASK_PROP_REFPATH (1LL << 25) +#define DLS_UPNP_MASK_PROP_RESTRICTED (1LL << 26) +#define DLS_UPNP_MASK_PROP_DLNA_MANAGED (1LL << 27) +#define DLS_UPNP_MASK_PROP_CREATOR (1LL << 28) +#define DLS_UPNP_MASK_PROP_ARTISTS (1LL << 29) +#define DLS_UPNP_MASK_PROP_CREATE_CLASSES (1LL << 30) +#define DLS_UPNP_MASK_PROP_OBJECT_UPDATE_ID (1LL << 31) +#define DLS_UPNP_MASK_PROP_UPDATE_COUNT (1LL << 32) +#define DLS_UPNP_MASK_PROP_CONTAINER_UPDATE_ID (1LL << 33) +#define DLS_UPNP_MASK_PROP_TOTAL_DELETED_CHILD_COUNT (1LL << 34) + +#define DLS_UPNP_MASK_ALL_PROPS 0xffffffffffffffff + +typedef struct dls_prop_map_t_ dls_prop_map_t; +struct dls_prop_map_t_ { + const gchar *upnp_prop_name; + dls_upnp_prop_mask type; + gboolean filter; + gboolean searchable; + gboolean updateable; +}; + +void dls_prop_maps_new(GHashTable **property_map, GHashTable **filter_map); + +dls_upnp_prop_mask dls_props_parse_filter(GHashTable *filter_map, + GVariant *filter, + gchar **upnp_filter); + +gboolean dls_props_parse_update_filter(GHashTable *filter_map, + GVariant *to_add_update, + GVariant *to_delete, + dls_upnp_prop_mask *mask, + gchar **upnp_filter); + +void dls_props_add_device(GUPnPDeviceInfo *proxy, + const dls_device_t *device, + GVariantBuilder *vb); + +GVariant *dls_props_get_device_prop(GUPnPDeviceInfo *proxy, + const dls_device_t *device, + const gchar *prop); + +gboolean dls_props_add_object(GVariantBuilder *item_vb, + GUPnPDIDLLiteObject *object, + const char *root_path, + const gchar *parent_path, + dls_upnp_prop_mask filter_mask); + +GVariant *dls_props_get_object_prop(const gchar *prop, const gchar *root_path, + GUPnPDIDLLiteObject *object); + +void dls_props_add_container(GVariantBuilder *item_vb, + GUPnPDIDLLiteContainer *object, + dls_upnp_prop_mask filter_mask, + gboolean *have_child_count); + +void dls_props_add_child_count(GVariantBuilder *item_vb, gint value); + +GVariant *dls_props_get_container_prop(const gchar *prop, + GUPnPDIDLLiteObject *object); + +void dls_props_add_resource(GVariantBuilder *item_vb, + GUPnPDIDLLiteObject *object, + dls_upnp_prop_mask filter_mask, + const gchar *protocol_info); + +void dls_props_add_item(GVariantBuilder *item_vb, + GUPnPDIDLLiteObject *object, + const gchar *root_path, + dls_upnp_prop_mask filter_mask, + const gchar *protocol_info); + +GVariant *dls_props_get_item_prop(const gchar *prop, const gchar *root_path, + GUPnPDIDLLiteObject *object, + const gchar *protocol_info); + +const gchar *dls_props_media_spec_to_upnp_class(const gchar *m2spec_class); + +const gchar *dls_props_upnp_class_to_media_spec(const gchar *upnp_class); + +#endif /* DLS_PROPS_H__ */ diff --git a/libdleyna/server/search.c b/libdleyna/server/search.c new file mode 100644 index 0000000..264654e --- /dev/null +++ b/libdleyna/server/search.c @@ -0,0 +1,148 @@ +/* + * 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 "interface.h" +#include "path.h" +#include "props.h" +#include "search.h" + +gchar *dls_search_translate_search_string(GHashTable *filter_map, + const gchar *search_string) +{ + GRegex *reg; + gchar *retval = NULL; + GMatchInfo *match_info = NULL; + gchar *prop = NULL; + gchar *op = NULL; + gchar *value = NULL; + const gchar *translated_value; + dls_prop_map_t *prop_map; + GString *str; + gint start_pos; + gint end_pos; + gint old_end_pos = 0; + unsigned int skipped; + unsigned int search_string_len = strlen(search_string); + gchar *root_path; + gchar *id; + + reg = g_regex_new("(\\w+)\\s+(=|!=|<|<=|>|>|contains|doesNotContain|"\ + "derivedfrom|exists)\\s+"\ + "(\"[^\"]*\"|true|false)", + 0, 0, NULL); + str = g_string_new(""); + + g_regex_match(reg, search_string, 0, &match_info); + while (g_match_info_matches(match_info)) { + prop = g_match_info_fetch(match_info, 1); + if (!prop) + goto on_error; + + op = g_match_info_fetch(match_info, 2); + if (!op) + goto on_error; + + value = g_match_info_fetch(match_info, 3); + if (!value) + goto on_error; + + /* Handle special cases where we need to translate + value as well as property name */ + + if (!strcmp(prop, DLS_INTERFACE_PROP_TYPE)) { + /* Skip the quotes */ + + value[strlen(value) - 1] = 0; + translated_value = dls_props_media_spec_to_upnp_class( + value + 1); + if (!translated_value) + goto on_error; + g_free(value); + value = g_strdup_printf("\"%s\"", translated_value); + } else if (!strcmp(prop, DLS_INTERFACE_PROP_PARENT) || + !strcmp(prop, DLS_INTERFACE_PROP_PATH)) { + value[strlen(value) - 1] = 0; + if (!dls_path_get_path_and_id(value + 1, &root_path, + &id, NULL)) + goto on_error; + g_free(root_path); + g_free(value); + value = g_strdup_printf("\"%s\"", id); + g_free(id); + } + + prop_map = g_hash_table_lookup(filter_map, prop); + if (!prop_map) + goto on_error; + + if (!prop_map->searchable) + goto on_error; + + if (!g_match_info_fetch_pos(match_info, 0, &start_pos, + &end_pos)) + goto on_error; + + skipped = start_pos - old_end_pos; + if (skipped > 0) + g_string_append_len(str, &search_string[old_end_pos], + skipped); + g_string_append_printf(str, "%s %s %s", + prop_map->upnp_prop_name, op, value); + old_end_pos = end_pos; + + g_free(value); + g_free(prop); + g_free(op); + + value = NULL; + prop = NULL; + op = NULL; + + g_match_info_next(match_info, NULL); + } + + skipped = search_string_len - old_end_pos; + if (skipped > 0) + g_string_append_len(str, &search_string[old_end_pos], + skipped); + + retval = g_string_free(str, FALSE); + str = NULL; + +on_error: + + g_free(value); + g_free(prop); + g_free(op); + + if (match_info) + g_match_info_free(match_info); + + if (str) + g_string_free(str, TRUE); + + g_regex_unref(reg); + + return retval; +} diff --git a/libdleyna/server/search.h b/libdleyna/server/search.h new file mode 100644 index 0000000..150a764 --- /dev/null +++ b/libdleyna/server/search.h @@ -0,0 +1,31 @@ +/* + * 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 DLS_SEARCH_H__ +#define DLS_SEARCH_H__ + +#include <glib.h> + +gchar *dls_search_translate_search_string(GHashTable *filter_map, + const gchar *search_string); + +#endif /* DLS_PROPS_H__ */ diff --git a/libdleyna/server/server.c b/libdleyna/server/server.c new file mode 100644 index 0000000..6cfa036 --- /dev/null +++ b/libdleyna/server/server.c @@ -0,0 +1,1199 @@ +/* + * 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. + * + * Mark Ryan <mark.d.ryan@intel.com> + * Regis Merlino <regis.merlino@intel.com> + * + */ + +#include <glib.h> +#include <string.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 "client.h" +#include "control-point-server.h" +#include "device.h" +#include "interface.h" +#include "path.h" +#include "server.h" +#include "upnp.h" + +#ifdef UA_PREFIX + #define DLS_PRG_NAME UA_PREFIX " dLeyna/" VERSION +#else + #define DLS_PRG_NAME "dLeyna/" VERSION +#endif + + +typedef struct dls_server_context_t_ dls_server_context_t; +struct dls_server_context_t_ { + dleyna_connector_id_t connection; + dleyna_task_processor_t *processor; + const dleyna_connector_t *connector; + dleyna_settings_t *settings; + guint dls_id; + GHashTable *watchers; + dls_upnp_t *upnp; +}; + +static dls_server_context_t g_context; + +static const gchar g_root_introspection[] = + "<node>" + " <interface name='"DLEYNA_SERVER_INTERFACE_MANAGER"'>" + " <method name='"DLS_INTERFACE_GET_VERSION"'>" + " <arg type='s' name='"DLS_INTERFACE_VERSION"'" + " direction='out'/>" + " </method>" + " <method name='"DLS_INTERFACE_RELEASE"'>" + " </method>" + " <method name='"DLS_INTERFACE_GET_SERVERS"'>" + " <arg type='ao' name='"DLS_INTERFACE_SERVERS"'" + " direction='out'/>" + " </method>" + " <method name='"DLS_INTERFACE_SET_PROTOCOL_INFO"'>" + " <arg type='s' name='"DLS_INTERFACE_PROTOCOL_INFO"'" + " direction='in'/>" + " </method>" + " <method name='"DLS_INTERFACE_PREFER_LOCAL_ADDRESSES"'>" + " <arg type='b' name='"DLS_INTERFACE_PREFER"'" + " direction='in'/>" + " </method>" + " <signal name='"DLS_INTERFACE_FOUND_SERVER"'>" + " <arg type='o' name='"DLS_INTERFACE_PATH"'/>" + " </signal>" + " <signal name='"DLS_INTERFACE_LOST_SERVER"'>" + " <arg type='o' name='"DLS_INTERFACE_PATH"'/>" + " </signal>" + " </interface>" + "</node>"; + +static const gchar g_server_introspection[] = + "<node>" + " <interface name='"DLS_INTERFACE_PROPERTIES"'>" + " <method name='"DLS_INTERFACE_GET"'>" + " <arg type='s' name='"DLS_INTERFACE_INTERFACE_NAME"'" + " direction='in'/>" + " <arg type='s' name='"DLS_INTERFACE_PROPERTY_NAME"'" + " direction='in'/>" + " <arg type='v' name='"DLS_INTERFACE_VALUE"'" + " direction='out'/>" + " </method>" + " <method name='"DLS_INTERFACE_GET_ALL"'>" + " <arg type='s' name='"DLS_INTERFACE_INTERFACE_NAME"'" + " direction='in'/>" + " <arg type='a{sv}' name='"DLS_INTERFACE_PROPERTIES_VALUE"'" + " direction='out'/>" + " </method>" + " <signal name='"DLS_INTERFACE_PROPERTIES_CHANGED"'>" + " <arg type='s' name='"DLS_INTERFACE_INTERFACE_NAME"'/>" + " <arg type='a{sv}' name='"DLS_INTERFACE_CHANGED_PROPERTIES"'/>" + " <arg type='as' name='" + DLS_INTERFACE_INVALIDATED_PROPERTIES"'/>" + " </signal>" + " </interface>" + " <interface name='"DLS_INTERFACE_MEDIA_OBJECT"'>" + " <property type='o' name='"DLS_INTERFACE_PROP_PARENT"'" + " access='read'/>" + " <property type='s' name='"DLS_INTERFACE_PROP_TYPE"'" + " access='read'/>" + " <property type='o' name='"DLS_INTERFACE_PROP_PATH"'" + " access='read'/>" + " <property type='s' name='"DLS_INTERFACE_PROP_DISPLAY_NAME"'" + " access='read'/>" + " <property type='s' name='"DLS_INTERFACE_PROP_CREATOR"'" + " access='read'/>" + " <property type='b' name='"DLS_INTERFACE_PROP_RESTRICTED"'" + " access='read'/>" + " <property type='a{sb}' name='"DLS_INTERFACE_PROP_DLNA_MANAGED"'" + " access='read'/>" + " <property type='u' name='"DLS_INTERFACE_PROP_OBJECT_UPDATE_ID"'" + " access='read'/>" + " <method name='"DLS_INTERFACE_DELETE"'>" + " </method>" + " <method name='"DLS_INTERFACE_UPDATE"'>" + " <arg type='a{sv}' name='"DLS_INTERFACE_TO_ADD_UPDATE"'" + " direction='in'/>" + " <arg type='as' name='"DLS_INTERFACE_TO_DELETE"'" + " direction='in'/>" + " </method>" + " </interface>" + " <interface name='"DLS_INTERFACE_MEDIA_CONTAINER"'>" + " <method name='"DLS_INTERFACE_LIST_CHILDREN"'>" + " <arg type='u' name='"DLS_INTERFACE_OFFSET"'" + " direction='in'/>" + " <arg type='u' name='"DLS_INTERFACE_MAX"'" + " direction='in'/>" + " <arg type='as' name='"DLS_INTERFACE_FILTER"'" + " direction='in'/>" + " <arg type='aa{sv}' name='"DLS_INTERFACE_CHILDREN"'" + " direction='out'/>" + " </method>" + " <method name='"DLS_INTERFACE_LIST_CHILDREN_EX"'>" + " <arg type='u' name='"DLS_INTERFACE_OFFSET"'" + " direction='in'/>" + " <arg type='u' name='"DLS_INTERFACE_MAX"'" + " direction='in'/>" + " <arg type='as' name='"DLS_INTERFACE_FILTER"'" + " direction='in'/>" + " <arg type='s' name='"DLS_INTERFACE_SORT_BY"'" + " direction='in'/>" + " <arg type='aa{sv}' name='"DLS_INTERFACE_CHILDREN"'" + " direction='out'/>" + " </method>" + " <method name='"DLS_INTERFACE_LIST_CONTAINERS"'>" + " <arg type='u' name='"DLS_INTERFACE_OFFSET"'" + " direction='in'/>" + " <arg type='u' name='"DLS_INTERFACE_MAX"'" + " direction='in'/>" + " <arg type='as' name='"DLS_INTERFACE_FILTER"'" + " direction='in'/>" + " <arg type='aa{sv}' name='"DLS_INTERFACE_CHILDREN"'" + " direction='out'/>" + " </method>" + " <method name='"DLS_INTERFACE_LIST_CONTAINERS_EX"'>" + " <arg type='u' name='"DLS_INTERFACE_OFFSET"'" + " direction='in'/>" + " <arg type='u' name='"DLS_INTERFACE_MAX"'" + " direction='in'/>" + " <arg type='as' name='"DLS_INTERFACE_FILTER"'" + " direction='in'/>" + " <arg type='s' name='"DLS_INTERFACE_SORT_BY"'" + " direction='in'/>" + " <arg type='aa{sv}' name='"DLS_INTERFACE_CHILDREN"'" + " direction='out'/>" + " </method>" + " <method name='"DLS_INTERFACE_LIST_ITEMS"'>" + " <arg type='u' name='"DLS_INTERFACE_OFFSET"'" + " direction='in'/>" + " <arg type='u' name='"DLS_INTERFACE_MAX"'" + " direction='in'/>" + " <arg type='as' name='"DLS_INTERFACE_FILTER"'" + " direction='in'/>" + " <arg type='aa{sv}' name='"DLS_INTERFACE_CHILDREN"'" + " direction='out'/>" + " </method>" + " <method name='"DLS_INTERFACE_LIST_ITEMS_EX"'>" + " <arg type='u' name='"DLS_INTERFACE_OFFSET"'" + " direction='in'/>" + " <arg type='u' name='"DLS_INTERFACE_MAX"'" + " direction='in'/>" + " <arg type='as' name='"DLS_INTERFACE_FILTER"'" + " direction='in'/>" + " <arg type='s' name='"DLS_INTERFACE_SORT_BY"'" + " direction='in'/>" + " <arg type='aa{sv}' name='"DLS_INTERFACE_CHILDREN"'" + " direction='out'/>" + " </method>" + " <method name='"DLS_INTERFACE_SEARCH_OBJECTS"'>" + " <arg type='s' name='"DLS_INTERFACE_QUERY"'" + " direction='in'/>" + " <arg type='u' name='"DLS_INTERFACE_OFFSET"'" + " direction='in'/>" + " <arg type='u' name='"DLS_INTERFACE_MAX"'" + " direction='in'/>" + " <arg type='as' name='"DLS_INTERFACE_FILTER"'" + " direction='in'/>" + " <arg type='aa{sv}' name='"DLS_INTERFACE_CHILDREN"'" + " direction='out'/>" + " </method>" + " <method name='"DLS_INTERFACE_SEARCH_OBJECTS_EX"'>" + " <arg type='s' name='"DLS_INTERFACE_QUERY"'" + " direction='in'/>" + " <arg type='u' name='"DLS_INTERFACE_OFFSET"'" + " direction='in'/>" + " <arg type='u' name='"DLS_INTERFACE_MAX"'" + " direction='in'/>" + " <arg type='as' name='"DLS_INTERFACE_FILTER"'" + " direction='in'/>" + " <arg type='s' name='"DLS_INTERFACE_SORT_BY"'" + " direction='in'/>" + " <arg type='aa{sv}' name='"DLS_INTERFACE_CHILDREN"'" + " direction='out'/>" + " <arg type='u' name='"DLS_INTERFACE_TOTAL_ITEMS"'" + " direction='out'/>" + " </method>" + " <method name='"DLS_INTERFACE_UPLOAD"'>" + " <arg type='s' name='"DLS_INTERFACE_PROP_DISPLAY_NAME"'" + " direction='in'/>" + " <arg type='s' name='"DLS_INTERFACE_FILE_PATH"'" + " direction='in'/>" + " <arg type='u' name='"DLS_INTERFACE_UPLOAD_ID"'" + " direction='out'/>" + " <arg type='o' name='"DLS_INTERFACE_PATH"'" + " direction='out'/>" + " </method>" + " <method name='"DLS_INTERFACE_CREATE_CONTAINER"'>" + " <arg type='s' name='"DLS_INTERFACE_PROP_DISPLAY_NAME"'" + " direction='in'/>" + " <arg type='s' name='"DLS_INTERFACE_PROP_TYPE"'" + " direction='in'/>" + " <arg type='as' name='"DLS_INTERFACE_CHILD_TYPES"'" + " direction='in'/>" + " <arg type='o' name='"DLS_INTERFACE_PATH"'" + " direction='out'/>" + " </method>" + " <method name='"DLS_INTERFACE_CREATE_PLAYLIST"'>" + " <arg type='s' name='"DLS_INTERFACE_TITLE"'" + " direction='in'/>" + " <arg type='s' name='"DLS_INTERFACE_CREATOR"'" + " direction='in'/>" + " <arg type='s' name='"DLS_INTERFACE_GENRE"'" + " direction='in'/>" + " <arg type='s' name='"DLS_INTERFACE_DESCRIPTION"'" + " direction='in'/>" + " <arg type='ao' name='"DLS_INTERFACE_PLAYLIST_ITEMS"'" + " direction='in'/>" + " <arg type='u' name='"DLS_INTERFACE_UPLOAD_ID"'" + " direction='out'/>" + " <arg type='o' name='"DLS_INTERFACE_PATH"'" + " direction='out'/>" + " </method>" + " <property type='u' name='"DLS_INTERFACE_PROP_CHILD_COUNT"'" + " access='read'/>" + " <property type='b' name='"DLS_INTERFACE_PROP_SEARCHABLE"'" + " access='read'/>" + " <property type='a(sb)' name='" + DLS_INTERFACE_PROP_CREATE_CLASSES"'" + " access='read'/>" + " <property type='u' name='" + DLS_INTERFACE_PROP_CONTAINER_UPDATE_ID"'" + " access='read'/>" + " <property type='u' name='" + DLS_INTERFACE_PROP_TOTAL_DELETED_CHILD_COUNT"'" + " access='read'/>" + " </interface>" + " <interface name='"DLS_INTERFACE_MEDIA_ITEM"'>" + " <method name='"DLS_INTERFACE_GET_COMPATIBLE_RESOURCE"'>" + " <arg type='s' name='"DLS_INTERFACE_PROTOCOL_INFO"'" + " direction='in'/>" + " <arg type='as' name='"DLS_INTERFACE_FILTER"'" + " direction='in'/>" + " <arg type='a{sv}' name='"DLS_INTERFACE_PROPERTIES_VALUE"'" + " direction='out'/>" + " </method>" + " <property type='as' name='"DLS_INTERFACE_PROP_URLS"'" + " access='read'/>" + " <property type='s' name='"DLS_INTERFACE_PROP_MIME_TYPE"'" + " access='read'/>" + " <property type='s' name='"DLS_INTERFACE_PROP_ARTIST"'" + " access='read'/>" + " <property type='as' name='"DLS_INTERFACE_PROP_ARTISTS"'" + " access='read'/>" + " <property type='s' name='"DLS_INTERFACE_PROP_ALBUM"'" + " access='read'/>" + " <property type='s' name='"DLS_INTERFACE_PROP_DATE"'" + " access='read'/>" + " <property type='s' name='"DLS_INTERFACE_PROP_GENRE"'" + " access='read'/>" + " <property type='s' name='"DLS_INTERFACE_PROP_DLNA_PROFILE"'" + " access='read'/>" + " <property type='i' name='"DLS_INTERFACE_PROP_TRACK_NUMBER"'" + " access='read'/>" + " <property type='x' name='"DLS_INTERFACE_PROP_SIZE"'" + " access='read'/>" + " <property type='i' name='"DLS_INTERFACE_PROP_DURATION"'" + " access='read'/>" + " <property type='i' name='"DLS_INTERFACE_PROP_BITRATE"'" + " access='read'/>" + " <property type='i' name='"DLS_INTERFACE_PROP_SAMPLE_RATE"'" + " access='read'/>" + " <property type='i' name='"DLS_INTERFACE_PROP_BITS_PER_SAMPLE"'" + " access='read'/>" + " <property type='i' name='"DLS_INTERFACE_PROP_WIDTH"'" + " access='read'/>" + " <property type='i' name='"DLS_INTERFACE_PROP_HEIGHT"'" + " access='read'/>" + " <property type='i' name='"DLS_INTERFACE_PROP_COLOR_DEPTH"'" + " access='read'/>" + " <property type='s' name='"DLS_INTERFACE_PROP_ALBUM_ART_URL"'" + " access='read'/>" + " <property type='o' name='"DLS_INTERFACE_PROP_REFPATH"'" + " access='read'/>" + " <property type='aa{sv}' name='"DLS_INTERFACE_PROP_RESOURCES"'" + " access='read'/>" + " </interface>" + " <interface name='"DLEYNA_SERVER_INTERFACE_MEDIA_DEVICE"'>" + " <method name='"DLS_INTERFACE_UPLOAD_TO_ANY"'>" + " <arg type='s' name='"DLS_INTERFACE_PROP_DISPLAY_NAME"'" + " direction='in'/>" + " <arg type='s' name='"DLS_INTERFACE_FILE_PATH"'" + " direction='in'/>" + " <arg type='u' name='"DLS_INTERFACE_UPLOAD_ID"'" + " direction='out'/>" + " <arg type='o' name='"DLS_INTERFACE_PATH"'" + " direction='out'/>" + " </method>" + " <method name='"DLS_INTERFACE_GET_UPLOAD_STATUS"'>" + " <arg type='u' name='"DLS_INTERFACE_UPLOAD_ID"'" + " direction='in'/>" + " <arg type='s' name='"DLS_INTERFACE_UPLOAD_STATUS"'" + " direction='out'/>" + " <arg type='t' name='"DLS_INTERFACE_LENGTH"'" + " direction='out'/>" + " <arg type='t' name='"DLS_INTERFACE_TOTAL"'" + " direction='out'/>" + " </method>" + " <method name='"DLS_INTERFACE_GET_UPLOAD_IDS"'>" + " <arg type='au' name='"DLS_INTERFACE_TOTAL"'" + " direction='out'/>" + " </method>" + " <method name='"DLS_INTERFACE_CANCEL_UPLOAD"'>" + " <arg type='u' name='"DLS_INTERFACE_UPLOAD_ID"'" + " direction='in'/>" + " </method>" + " <method name='"DLS_INTERFACE_CREATE_CONTAINER_IN_ANY"'>" + " <arg type='s' name='"DLS_INTERFACE_PROP_DISPLAY_NAME"'" + " direction='in'/>" + " <arg type='s' name='"DLS_INTERFACE_PROP_TYPE"'" + " direction='in'/>" + " <arg type='as' name='"DLS_INTERFACE_CHILD_TYPES"'" + " direction='in'/>" + " <arg type='o' name='"DLS_INTERFACE_PATH"'" + " direction='out'/>" + " </method>" + " <method name='"DLS_INTERFACE_CANCEL"'>" + " </method>" + " <method name='"DLS_INTERFACE_CREATE_PLAYLIST_TO_ANY"'>" + " <arg type='s' name='"DLS_INTERFACE_TITLE"'" + " direction='in'/>" + " <arg type='s' name='"DLS_INTERFACE_CREATOR"'" + " direction='in'/>" + " <arg type='s' name='"DLS_INTERFACE_GENRE"'" + " direction='in'/>" + " <arg type='s' name='"DLS_INTERFACE_DESCRIPTION"'" + " direction='in'/>" + " <arg type='ao' name='"DLS_INTERFACE_PLAYLIST_ITEMS"'" + " direction='in'/>" + " <arg type='u' name='"DLS_INTERFACE_UPLOAD_ID"'" + " direction='out'/>" + " <arg type='o' name='"DLS_INTERFACE_PATH"'" + " direction='out'/>" + " </method>" + " <property type='s' name='"DLS_INTERFACE_PROP_LOCATION"'" + " access='read'/>" + " <property type='s' name='"DLS_INTERFACE_PROP_UDN"'" + " access='read'/>" + " <property type='s' name='"DLS_INTERFACE_PROP_DEVICE_TYPE"'" + " access='read'/>" + " <property type='s' name='"DLS_INTERFACE_PROP_FRIENDLY_NAME"'" + " access='read'/>" + " <property type='s' name='"DLS_INTERFACE_PROP_MANUFACTURER"'" + " access='read'/>" + " <property type='s' name='" + DLS_INTERFACE_PROP_MANUFACTURER_URL"'" + " access='read'/>" + " <property type='s' name='" + DLS_INTERFACE_PROP_MODEL_DESCRIPTION"'" + " access='read'/>" + " <property type='s' name='"DLS_INTERFACE_PROP_MODEL_NAME"'" + " access='read'/>" + " <property type='s' name='"DLS_INTERFACE_PROP_MODEL_NUMBER"'" + " access='read'/>" + " <property type='s' name='"DLS_INTERFACE_PROP_MODEL_URL"'" + " access='read'/>" + " <property type='s' name='"DLS_INTERFACE_PROP_SERIAL_NUMBER"'" + " access='read'/>" + " <property type='s' name='"DLS_INTERFACE_PROP_PRESENTATION_URL"'" + " access='read'/>" + " <property type='s' name='"DLS_INTERFACE_PROP_ICON_URL"'" + " access='read'/>" + " <property type='a{sv}'name='" + DLS_INTERFACE_PROP_SV_DLNA_CAPABILITIES"'" + " access='read'/>" + " <property type='as' name='" + DLS_INTERFACE_PROP_SV_SEARCH_CAPABILITIES"'" + " access='read'/>" + " <property type='as' name='" + DLS_INTERFACE_PROP_SV_SORT_CAPABILITIES"'" + " access='read'/>" + " <property type='as' name='" + DLS_INTERFACE_PROP_SV_SORT_EXT_CAPABILITIES"'" + " access='read'/>" + " <property type='a(ssao)' name='" + DLS_INTERFACE_PROP_SV_FEATURE_LIST"'" + " access='read'/>" + " <property type='u' name='" + DLS_INTERFACE_PROP_ESV_SYSTEM_UPDATE_ID"'" + " access='read'/>" + " <property type='s' name='" + DLS_INTERFACE_PROP_SV_SERVICE_RESET_TOKEN"'" + " access='read'/>" + " <signal name='"DLS_INTERFACE_ESV_CONTAINER_UPDATE_IDS"'>" + " <arg type='a(ou)' name='"DLS_INTERFACE_CONTAINER_PATHS_ID"'/>" + " </signal>" + " <signal name='"DLS_INTERFACE_ESV_LAST_CHANGE"'>" + " <arg type='a(sv)' name='" + DLS_INTERFACE_LAST_CHANGE_STATE_EVENT"'/>" + " </signal>" + " <signal name='"DLS_INTERFACE_UPLOAD_UPDATE"'>" + " <arg type='u' name='"DLS_INTERFACE_UPLOAD_ID"'/>" + " <arg type='s' name='"DLS_INTERFACE_UPLOAD_STATUS"'/>" + " <arg type='t' name='"DLS_INTERFACE_LENGTH"'/>" + " <arg type='t' name='"DLS_INTERFACE_TOTAL"'/>" + " </signal>" + " </interface>" + "</node>"; + +const dleyna_connector_t *dls_server_get_connector(void) +{ + return g_context.connector; +} + +dleyna_task_processor_t *dls_server_get_task_processor(void) +{ + return g_context.processor; +} + +static void prv_sync_task_complete(dls_task_t *task) +{ + dls_task_complete(task); + dleyna_task_queue_task_completed(task->atom.queue_id); +} + +static void prv_process_sync_task(dls_task_t *task) +{ + dls_client_t *client; + const gchar *client_name; + + switch (task->type) { + case DLS_TASK_GET_VERSION: + prv_sync_task_complete(task); + break; + case DLS_TASK_GET_SERVERS: + task->result = dls_upnp_get_server_ids(g_context.upnp); + prv_sync_task_complete(task); + break; + case DLS_TASK_SET_PROTOCOL_INFO: + client_name = dleyna_task_queue_get_source(task->atom.queue_id); + client = g_hash_table_lookup(g_context.watchers, client_name); + if (client) { + g_free(client->protocol_info); + if (task->ut.protocol_info.protocol_info[0]) { + client->protocol_info = + task->ut.protocol_info.protocol_info; + task->ut.protocol_info.protocol_info = NULL; + } else { + client->protocol_info = NULL; + } + } + prv_sync_task_complete(task); + break; + case DLS_TASK_SET_PREFER_LOCAL_ADDRESSES: + client_name = dleyna_task_queue_get_source(task->atom.queue_id); + client = g_hash_table_lookup(g_context.watchers, client_name); + if (client) { + client->prefer_local_addresses = + task->ut.prefer_local_addresses.prefer; + } + prv_sync_task_complete(task); + break; + case DLS_TASK_GET_UPLOAD_STATUS: + dls_upnp_get_upload_status(g_context.upnp, task); + dleyna_task_queue_task_completed(task->atom.queue_id); + break; + case DLS_TASK_GET_UPLOAD_IDS: + dls_upnp_get_upload_ids(g_context.upnp, task); + dleyna_task_queue_task_completed(task->atom.queue_id); + break; + case DLS_TASK_CANCEL_UPLOAD: + dls_upnp_cancel_upload(g_context.upnp, task); + dleyna_task_queue_task_completed(task->atom.queue_id); + break; + default: + break; + } +} + +static void prv_async_task_complete(dls_task_t *task, GError *error) +{ + DLEYNA_LOG_DEBUG("Enter"); + + if (error) { + dls_task_fail(task, error); + g_error_free(error); + } else { + dls_task_complete(task); + } + + dleyna_task_queue_task_completed(task->atom.queue_id); + + DLEYNA_LOG_DEBUG("Exit"); +} + +static void prv_process_async_task(dls_task_t *task) +{ + dls_async_task_t *async_task = (dls_async_task_t *)task; + dls_client_t *client; + const gchar *client_name; + + DLEYNA_LOG_DEBUG("Enter"); + + async_task->cancellable = g_cancellable_new(); + client_name = dleyna_task_queue_get_source(task->atom.queue_id); + client = g_hash_table_lookup(g_context.watchers, client_name); + + switch (task->type) { + case DLS_TASK_GET_CHILDREN: + dls_upnp_get_children(g_context.upnp, client, task, + prv_async_task_complete); + break; + case DLS_TASK_GET_PROP: + dls_upnp_get_prop(g_context.upnp, client, task, + prv_async_task_complete); + break; + case DLS_TASK_GET_ALL_PROPS: + dls_upnp_get_all_props(g_context.upnp, client, task, + prv_async_task_complete); + break; + case DLS_TASK_SEARCH: + dls_upnp_search(g_context.upnp, client, task, + prv_async_task_complete); + break; + case DLS_TASK_GET_RESOURCE: + dls_upnp_get_resource(g_context.upnp, client, task, + prv_async_task_complete); + break; + case DLS_TASK_UPLOAD_TO_ANY: + dls_upnp_upload_to_any(g_context.upnp, client, task, + prv_async_task_complete); + break; + case DLS_TASK_UPLOAD: + dls_upnp_upload(g_context.upnp, client, task, + prv_async_task_complete); + break; + case DLS_TASK_DELETE_OBJECT: + dls_upnp_delete_object(g_context.upnp, client, task, + prv_async_task_complete); + break; + case DLS_TASK_CREATE_CONTAINER: + dls_upnp_create_container(g_context.upnp, client, task, + prv_async_task_complete); + break; + case DLS_TASK_CREATE_CONTAINER_IN_ANY: + dls_upnp_create_container_in_any(g_context.upnp, client, task, + prv_async_task_complete); + break; + case DLS_TASK_UPDATE_OBJECT: + dls_upnp_update_object(g_context.upnp, client, task, + prv_async_task_complete); + break; + case DLS_TASK_CREATE_PLAYLIST: + dls_upnp_create_playlist(g_context.upnp, client, task, + prv_async_task_complete); + break; + case DLS_TASK_CREATE_PLAYLIST_IN_ANY: + dls_upnp_create_playlist_in_any(g_context.upnp, client, 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) +{ + dls_task_t *client_task = (dls_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) +{ + dls_task_cancel((dls_task_t *)task); +} + +static void prv_delete_task(dleyna_task_atom_t *task, gpointer user_data) +{ + dls_task_delete((dls_task_t *)task); +} + +static void prv_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_object_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_item_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_con_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_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_method_call +}; + +static const dleyna_connector_dispatch_cb_t + g_server_vtables[DLS_INTERFACE_INFO_MAX] = { + /* MUST be in the exact same order as g_dls_server_introspection */ + prv_props_method_call, + prv_object_method_call, + prv_con_method_call, + prv_item_method_call, + prv_device_method_call +}; + +static void prv_remove_client(const gchar *name) +{ + dleyna_task_processor_remove_queues_for_source(g_context.processor, + name); + + (void) g_hash_table_remove(g_context.watchers, name); + + if (g_hash_table_size(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_DEBUG("Lost Client %s", name); + + prv_remove_client(name); +} + +static void prv_add_task(dls_task_t *task, const gchar *source, + const gchar *sink) +{ + dls_client_t *client; + const dleyna_task_queue_key_t *queue_id; + + if (!g_hash_table_lookup(g_context.watchers, source)) { + client = g_new0(dls_client_t, 1); + client->prefer_local_addresses = TRUE; + g_context.connector->watch_client(source); + g_hash_table_insert(g_context.watchers, g_strdup(source), + client); + } + + 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_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) +{ + dls_task_t *task; + + if (!strcmp(method, DLS_INTERFACE_RELEASE)) { + prv_remove_client(sender); + g_context.connector->return_response(invocation, NULL); + } else if (!strcmp(method, DLS_INTERFACE_GET_VERSION)) { + task = dls_task_get_version_new(invocation); + prv_add_task(task, sender, DLS_SERVER_SINK); + } else if (!strcmp(method, DLS_INTERFACE_GET_SERVERS)) { + task = dls_task_get_servers_new(invocation); + prv_add_task(task, sender, DLS_SERVER_SINK); + } else if (!strcmp(method, DLS_INTERFACE_SET_PROTOCOL_INFO)) { + task = dls_task_set_protocol_info_new(invocation, + parameters); + prv_add_task(task, sender, DLS_SERVER_SINK); + } else if (!strcmp(method, DLS_INTERFACE_PREFER_LOCAL_ADDRESSES)) { + task = dls_task_prefer_local_addresses_new(invocation, + parameters); + prv_add_task(task, sender, DLS_SERVER_SINK); + } +} + +gboolean dls_server_get_object_info(const gchar *object_path, + gchar **root_path, + gchar **object_id, + dls_device_t **device, + GError **error) +{ + if (!dls_path_get_path_and_id(object_path, root_path, object_id, + error)) { + DLEYNA_LOG_WARNING("Bad object %s", object_path); + + goto on_error; + } + + *device = dls_device_from_path(*root_path, + dls_upnp_get_server_udn_map(g_context.upnp)); + + if (*device == NULL) { + DLEYNA_LOG_WARNING("Cannot locate device for %s", *root_path); + + *error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OBJECT_NOT_FOUND, + "Cannot locate device corresponding to" + " the specified path"); + + g_free(*root_path); + g_free(*object_id); + + goto on_error; + } + + return TRUE; + +on_error: + + return FALSE; +} + +static const gchar *prv_get_device_id(const gchar *object, GError **error) +{ + dls_device_t *device; + gchar *root_path; + gchar *id; + + if (!dls_server_get_object_info(object, &root_path, &id, &device, + error)) + goto on_error; + + g_free(id); + g_free(root_path); + + return device->path; + +on_error: + + return NULL; +} + +static void prv_object_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) +{ + dls_task_t *task; + GError *error = NULL; + + if (!strcmp(method, DLS_INTERFACE_DELETE)) + task = dls_task_delete_new(invocation, object, &error); + else if (!strcmp(method, DLS_INTERFACE_UPDATE)) + task = dls_task_update_new(invocation, object, + parameters, &error); + else + goto finished; + + if (!task) { + g_context.connector->return_error(invocation, error); + g_error_free(error); + + goto finished; + } + + prv_add_task(task, sender, task->target.device->path); + +finished: + + return; +} + +static void prv_item_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) +{ + dls_task_t *task; + GError *error = NULL; + + if (!strcmp(method, DLS_INTERFACE_GET_COMPATIBLE_RESOURCE)) { + task = dls_task_get_resource_new(invocation, object, + parameters, &error); + + if (!task) { + g_context.connector->return_error(invocation, error); + g_error_free(error); + + goto finished; + } + + prv_add_task(task, sender, task->target.device->path); + } + +finished: + + return; +} + + +static void prv_con_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) +{ + dls_task_t *task; + GError *error = NULL; + + if (!strcmp(method, DLS_INTERFACE_LIST_CHILDREN)) + task = dls_task_get_children_new(invocation, object, + parameters, TRUE, + TRUE, &error); + else if (!strcmp(method, DLS_INTERFACE_LIST_CHILDREN_EX)) + task = dls_task_get_children_ex_new(invocation, object, + parameters, TRUE, + TRUE, &error); + else if (!strcmp(method, DLS_INTERFACE_LIST_ITEMS)) + task = dls_task_get_children_new(invocation, object, + parameters, TRUE, + FALSE, &error); + else if (!strcmp(method, DLS_INTERFACE_LIST_ITEMS_EX)) + task = dls_task_get_children_ex_new(invocation, object, + parameters, TRUE, + FALSE, &error); + else if (!strcmp(method, DLS_INTERFACE_LIST_CONTAINERS)) + task = dls_task_get_children_new(invocation, object, + parameters, FALSE, + TRUE, &error); + else if (!strcmp(method, DLS_INTERFACE_LIST_CONTAINERS_EX)) + task = dls_task_get_children_ex_new(invocation, object, + parameters, FALSE, + TRUE, &error); + else if (!strcmp(method, DLS_INTERFACE_SEARCH_OBJECTS)) + task = dls_task_search_new(invocation, object, + parameters, &error); + else if (!strcmp(method, DLS_INTERFACE_SEARCH_OBJECTS_EX)) + task = dls_task_search_ex_new(invocation, object, + parameters, &error); + else if (!strcmp(method, DLS_INTERFACE_UPLOAD)) + task = dls_task_upload_new(invocation, object, + parameters, &error); + else if (!strcmp(method, DLS_INTERFACE_CREATE_CONTAINER)) + task = dls_task_create_container_new_generic(invocation, + DLS_TASK_CREATE_CONTAINER, + object, parameters, &error); + else if (!strcmp(method, DLS_INTERFACE_CREATE_PLAYLIST)) + task = dls_task_create_playlist_new(invocation, + DLS_TASK_CREATE_PLAYLIST, + object, parameters, &error); + else + goto finished; + + if (!task) { + g_context.connector->return_error(invocation, error); + g_error_free(error); + + goto finished; + } + + prv_add_task(task, sender, task->target.device->path); + +finished: + + return; +} + +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) +{ + dls_task_t *task; + GError *error = NULL; + + if (!strcmp(method, DLS_INTERFACE_GET_ALL)) + task = dls_task_get_props_new(invocation, object, + parameters, &error); + else if (!strcmp(method, DLS_INTERFACE_GET)) + task = dls_task_get_prop_new(invocation, object, + parameters, &error); + else + goto finished; + + if (!task) { + g_context.connector->return_error(invocation, error); + g_error_free(error); + + goto finished; + } + + prv_add_task(task, sender, task->target.device->path); + +finished: + + return; +} + +static void prv_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) +{ + dls_task_t *task; + GError *error = NULL; + const gchar *device_id; + const dleyna_task_queue_key_t *queue_id; + + if (!strcmp(method, DLS_INTERFACE_UPLOAD_TO_ANY)) { + task = dls_task_upload_to_any_new(invocation, + object, parameters, &error); + } else if (!strcmp(method, DLS_INTERFACE_CREATE_CONTAINER_IN_ANY)) { + task = dls_task_create_container_new_generic( + invocation, + DLS_TASK_CREATE_CONTAINER_IN_ANY, + object, parameters, &error); + } else if (!strcmp(method, DLS_INTERFACE_GET_UPLOAD_STATUS)) { + task = dls_task_get_upload_status_new(invocation, + object, parameters, + &error); + } else if (!strcmp(method, DLS_INTERFACE_GET_UPLOAD_IDS)) { + task = dls_task_get_upload_ids_new(invocation, object, + &error); + } else if (!strcmp(method, DLS_INTERFACE_CANCEL_UPLOAD)) { + task = dls_task_cancel_upload_new(invocation, object, + parameters, &error); + } else if (!strcmp(method, DLS_INTERFACE_CREATE_PLAYLIST_TO_ANY)) { + task = dls_task_create_playlist_new( + invocation, + DLS_TASK_CREATE_PLAYLIST_IN_ANY, + object, parameters, &error); + } else if (!strcmp(method, DLS_INTERFACE_CANCEL)) { + task = NULL; + + device_id = prv_get_device_id(object, &error); + if (!device_id) + goto on_error; + + 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); + + goto finished; + } else { + goto finished; + } + +on_error: + + if (!task) { + g_context.connector->return_error(invocation, error); + g_error_free(error); + + goto finished; + } + + prv_add_task(task, sender, task->target.device->path); + +finished: + + return; +} + +static void prv_found_media_server(const gchar *path, void *user_data) +{ + (void) g_context.connector->notify(g_context.connection, + DLEYNA_SERVER_OBJECT, + DLEYNA_SERVER_INTERFACE_MANAGER, + DLS_INTERFACE_FOUND_SERVER, + g_variant_new("(o)", path), + NULL); +} + +static void prv_lost_media_server(const gchar *path, void *user_data) +{ + (void) g_context.connector->notify(g_context.connection, + DLEYNA_SERVER_OBJECT, + DLEYNA_SERVER_INTERFACE_MANAGER, + DLS_INTERFACE_LOST_SERVER, + g_variant_new("(o)", path), + NULL); + + dleyna_task_processor_remove_queues_for_sink(g_context.processor, path); +} + +static void prv_unregister_client(gpointer user_data) +{ + dls_client_t *client = user_data; + + if (client) { + g_free(client->protocol_info); + g_free(client); + } +} + +dls_upnp_t *dls_server_get_upnp(void) +{ + return g_context.upnp; +} + +static gboolean prv_control_point_start_service( + dleyna_connector_id_t connection) +{ + gboolean retval = TRUE; + + g_context.connection = connection; + + g_context.dls_id = g_context.connector->publish_object( + connection, + DLEYNA_SERVER_OBJECT, + TRUE, + 0, + g_root_vtables); + + if (!g_context.dls_id) { + retval = FALSE; + goto out; + } else { + g_context.upnp = dls_upnp_new(connection, + g_server_vtables, + prv_found_media_server, + prv_lost_media_server, + NULL); + } + +out: + + return retval; +} + +static void prv_control_point_stop_service(void) +{ + dls_upnp_unsubscribe(g_context.upnp); + + dls_upnp_delete(g_context.upnp); + + if (g_context.connection) { + if (g_context.dls_id) + g_context.connector->unpublish_object( + g_context.connection, + g_context.dls_id); + } +} + +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.connector = connector; + g_context.processor = processor; + g_context.settings = settings; + + g_context.connector->set_client_lost_cb(prv_lost_client); + + g_context.watchers = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, prv_unregister_client); + + g_set_prgname(DLS_PRG_NAME); +} + +static void prv_control_point_free(void) +{ + if (g_context.watchers) + g_hash_table_unref(g_context.watchers); +} + +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_server(void) +{ + return &g_control_point; +} diff --git a/libdleyna/server/server.h b/libdleyna/server/server.h new file mode 100644 index 0000000..01d9356 --- /dev/null +++ b/libdleyna/server/server.h @@ -0,0 +1,47 @@ +/* + * 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 DLS_SERVER_H__ +#define DLS_SERVER_H__ + +#include <libdleyna/core/connector.h> +#include <libdleyna/core/task-processor.h> + +#define DLS_SERVER_SINK "dleyna-server" + +typedef struct dls_device_t_ dls_device_t; +typedef struct dls_device_context_t_ dls_device_context_t; +typedef struct dls_upnp_t_ dls_upnp_t; + +gboolean dls_server_get_object_info(const gchar *object_path, + gchar **root_path, + gchar **object_id, + dls_device_t **device, + GError **error); + +dls_upnp_t *dls_server_get_upnp(void); + +dleyna_task_processor_t *dls_server_get_task_processor(void); + +const dleyna_connector_t *dls_server_get_connector(void); + +#endif /* DLS_SERVER_H__ */ diff --git a/libdleyna/server/sort.c b/libdleyna/server/sort.c new file mode 100644 index 0000000..d1f3c4d --- /dev/null +++ b/libdleyna/server/sort.c @@ -0,0 +1,98 @@ +/* + * 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 "props.h" +#include "sort.h" + +gchar *dls_sort_translate_sort_string(GHashTable *filter_map, + const gchar *sort_string) +{ + GRegex *reg; + gchar *retval = NULL; + GMatchInfo *match_info = NULL; + gchar *prop = NULL; + gchar *op = NULL; + dls_prop_map_t *prop_map; + GString *str; + + if (!g_regex_match_simple( + "^((\\+|\\-)([^,\\+\\-]+))?(,(\\+|\\-)([^,\\+\\-]+))*$", + sort_string, 0, 0)) + goto no_free; + + reg = g_regex_new("(\\+|\\-)(\\w+)", 0, 0, NULL); + str = g_string_new(""); + + g_regex_match(reg, sort_string, 0, &match_info); + while (g_match_info_matches(match_info)) { + op = g_match_info_fetch(match_info, 1); + if (!op) + goto on_error; + + prop = g_match_info_fetch(match_info, 2); + if (!prop) + goto on_error; + + prop_map = g_hash_table_lookup(filter_map, prop); + if (!prop_map) + goto on_error; + + if (!prop_map->searchable) + goto on_error; + + g_string_append_printf(str, "%s%s,", op, + prop_map->upnp_prop_name); + + g_free(prop); + g_free(op); + + prop = NULL; + op = NULL; + + g_match_info_next(match_info, NULL); + } + + if (str->len > 0) + str = g_string_truncate(str, str->len - 1); + retval = g_string_free(str, FALSE); + + str = NULL; + +on_error: + + g_free(prop); + g_free(op); + + if (match_info) + g_match_info_free(match_info); + + if (str) + g_string_free(str, TRUE); + + g_regex_unref(reg); + +no_free: + + return retval; +} diff --git a/libdleyna/server/sort.h b/libdleyna/server/sort.h new file mode 100644 index 0000000..378d163 --- /dev/null +++ b/libdleyna/server/sort.h @@ -0,0 +1,31 @@ +/* + * 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 DLS_SORT_H__ +#define DLS_SORT_H__ + +#include <glib.h> + +gchar *dls_sort_translate_sort_string(GHashTable *filter_map, + const gchar *sort_string); + +#endif /* DLS_SORT_H__ */ diff --git a/libdleyna/server/task.c b/libdleyna/server/task.c new file mode 100644 index 0000000..6a8cf85 --- /dev/null +++ b/libdleyna/server/task.c @@ -0,0 +1,618 @@ +/* + * 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 "async.h" + +dls_task_t *dls_task_get_version_new(dleyna_connector_msg_id_t invocation) +{ + dls_task_t *task = g_new0(dls_task_t, 1); + + task->type = DLS_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; +} + +dls_task_t *dls_task_get_servers_new(dleyna_connector_msg_id_t invocation) +{ + dls_task_t *task = g_new0(dls_task_t, 1); + + task->type = DLS_TASK_GET_SERVERS; + task->invocation = invocation; + task->result_format = "(@ao)"; + task->synchronous = TRUE; + + return task; +} + +static void prv_delete(dls_task_t *task) +{ + if (!task->synchronous) + dls_async_task_delete((dls_async_task_t *)task); + + switch (task->type) { + case DLS_TASK_GET_CHILDREN: + if (task->ut.get_children.filter) + g_variant_unref(task->ut.get_children.filter); + g_free(task->ut.get_children.sort_by); + break; + case DLS_TASK_GET_ALL_PROPS: + g_free(task->ut.get_props.interface_name); + break; + case DLS_TASK_GET_PROP: + g_free(task->ut.get_prop.interface_name); + g_free(task->ut.get_prop.prop_name); + break; + case DLS_TASK_SEARCH: + g_free(task->ut.search.query); + if (task->ut.search.filter) + g_variant_unref(task->ut.search.filter); + g_free(task->ut.search.sort_by); + break; + case DLS_TASK_GET_RESOURCE: + if (task->ut.resource.filter) + g_variant_unref(task->ut.resource.filter); + g_free(task->ut.resource.protocol_info); + break; + case DLS_TASK_SET_PROTOCOL_INFO: + if (task->ut.protocol_info.protocol_info) + g_free(task->ut.protocol_info.protocol_info); + break; + case DLS_TASK_UPLOAD_TO_ANY: + case DLS_TASK_UPLOAD: + g_free(task->ut.upload.display_name); + g_free(task->ut.upload.file_path); + break; + case DLS_TASK_CREATE_CONTAINER: + case DLS_TASK_CREATE_CONTAINER_IN_ANY: + g_free(task->ut.create_container.display_name); + g_free(task->ut.create_container.type); + g_variant_unref(task->ut.create_container.child_types); + break; + case DLS_TASK_UPDATE_OBJECT: + if (task->ut.update.to_add_update) + g_variant_unref(task->ut.update.to_add_update); + if (task->ut.update.to_delete) + g_variant_unref(task->ut.update.to_delete); + break; + case DLS_TASK_CREATE_PLAYLIST: + case DLS_TASK_CREATE_PLAYLIST_IN_ANY: + g_free(task->ut.playlist.title); + g_free(task->ut.playlist.creator); + g_free(task->ut.playlist.genre); + g_free(task->ut.playlist.desc); + if (task->ut.playlist.item_path) + g_variant_unref(task->ut.playlist.item_path); + break; + default: + break; + } + + g_free(task->target.path); + g_free(task->target.root_path); + g_free(task->target.id); + + if (task->result) + g_variant_unref(task->result); + + g_free(task); +} + +static gboolean prv_set_task_target_info(dls_task_t *task, const gchar *path, + GError **error) +{ + task->target.path = g_strdup(path); + g_strstrip(task->target.path); + + return dls_server_get_object_info(path, &task->target.root_path, + &task->target.id, + &task->target.device, error); +} + +static dls_task_t *prv_m2spec_task_new(dls_task_type_t type, + dleyna_connector_msg_id_t invocation, + const gchar *path, + const gchar *result_format, + GError **error, + gboolean synchronous) +{ + dls_task_t *task; + + if (synchronous) { + task = g_new0(dls_task_t, 1); + task->synchronous = TRUE; + } else { + task = (dls_task_t *)g_new0(dls_async_task_t, 1); + } + + if (!prv_set_task_target_info(task, path, error)) { + prv_delete(task); + task = NULL; + + goto finished; + } + + task->type = type; + task->invocation = invocation; + task->result_format = result_format; + +finished: + + return task; +} + +dls_task_t *dls_task_get_children_new(dleyna_connector_msg_id_t invocation, + const gchar *path, GVariant *parameters, + gboolean items, gboolean containers, + GError **error) +{ + dls_task_t *task; + + task = prv_m2spec_task_new(DLS_TASK_GET_CHILDREN, invocation, path, + "(@aa{sv})", error, FALSE); + if (!task) + goto finished; + + task->ut.get_children.containers = containers; + task->ut.get_children.items = items; + + g_variant_get(parameters, "(uu@as)", + &task->ut.get_children.start, + &task->ut.get_children.count, + &task->ut.get_children.filter); + + task->ut.get_children.sort_by = g_strdup(""); + +finished: + + return task; +} + +dls_task_t *dls_task_get_children_ex_new(dleyna_connector_msg_id_t invocation, + const gchar *path, + GVariant *parameters, gboolean items, + gboolean containers, + GError **error) +{ + dls_task_t *task; + + task = prv_m2spec_task_new(DLS_TASK_GET_CHILDREN, invocation, path, + "(@aa{sv})", error, FALSE); + if (!task) + goto finished; + + task->ut.get_children.containers = containers; + task->ut.get_children.items = items; + + g_variant_get(parameters, "(uu@ass)", + &task->ut.get_children.start, + &task->ut.get_children.count, + &task->ut.get_children.filter, + &task->ut.get_children.sort_by); + +finished: + + return task; +} + +dls_task_t *dls_task_get_prop_new(dleyna_connector_msg_id_t invocation, + const gchar *path, GVariant *parameters, + GError **error) +{ + dls_task_t *task; + + task = prv_m2spec_task_new(DLS_TASK_GET_PROP, invocation, path, "(v)", + error, FALSE); + if (!task) + goto finished; + + 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); + +finished: + + return task; +} + +dls_task_t *dls_task_get_props_new(dleyna_connector_msg_id_t invocation, + const gchar *path, GVariant *parameters, + GError **error) +{ + dls_task_t *task; + + task = prv_m2spec_task_new(DLS_TASK_GET_ALL_PROPS, invocation, path, + "(@a{sv})", error, FALSE); + if (!task) + goto finished; + + g_variant_get(parameters, "(s)", &task->ut.get_props.interface_name); + g_strstrip(task->ut.get_props.interface_name); + +finished: + + return task; +} + +dls_task_t *dls_task_search_new(dleyna_connector_msg_id_t invocation, + const gchar *path, GVariant *parameters, + GError **error) +{ + dls_task_t *task; + + task = prv_m2spec_task_new(DLS_TASK_SEARCH, invocation, path, + "(@aa{sv})", error, FALSE); + if (!task) + goto finished; + + g_variant_get(parameters, "(suu@as)", &task->ut.search.query, + &task->ut.search.start, &task->ut.search.count, + &task->ut.search.filter); + + task->ut.search.sort_by = g_strdup(""); + +finished: + return task; +} + +dls_task_t *dls_task_search_ex_new(dleyna_connector_msg_id_t invocation, + const gchar *path, GVariant *parameters, + GError **error) +{ + dls_task_t *task; + + task = prv_m2spec_task_new(DLS_TASK_SEARCH, invocation, path, + "(@aa{sv}u)", error, FALSE); + if (!task) + goto finished; + + g_variant_get(parameters, "(suu@ass)", &task->ut.search.query, + &task->ut.search.start, &task->ut.search.count, + &task->ut.search.filter, &task->ut.search.sort_by); + + task->multiple_retvals = TRUE; + +finished: + + return task; +} + +dls_task_t *dls_task_get_resource_new(dleyna_connector_msg_id_t invocation, + const gchar *path, GVariant *parameters, + GError **error) +{ + dls_task_t *task; + + task = prv_m2spec_task_new(DLS_TASK_GET_RESOURCE, invocation, path, + "(@a{sv})", error, FALSE); + if (!task) + goto finished; + + g_variant_get(parameters, "(s@as)", + &task->ut.resource.protocol_info, + &task->ut.resource.filter); + +finished: + + return task; +} + +dls_task_t *dls_task_set_protocol_info_new(dleyna_connector_msg_id_t invocation, + GVariant *parameters) +{ + dls_task_t *task = g_new0(dls_task_t, 1); + + task->type = DLS_TASK_SET_PROTOCOL_INFO; + task->invocation = invocation; + task->synchronous = TRUE; + g_variant_get(parameters, "(s)", &task->ut.protocol_info.protocol_info); + + return task; +} + +static dls_task_t *prv_upload_new_generic(dls_task_type_t type, + dleyna_connector_msg_id_t invocation, + const gchar *path, + GVariant *parameters, + GError **error) +{ + dls_task_t *task; + + task = prv_m2spec_task_new(type, invocation, path, + "(uo)", error, FALSE); + if (!task) + goto finished; + + g_variant_get(parameters, "(ss)", &task->ut.upload.display_name, + &task->ut.upload.file_path); + g_strstrip(task->ut.upload.file_path); + task->multiple_retvals = TRUE; + +finished: + + return task; +} + +dls_task_t *dls_task_prefer_local_addresses_new( + dleyna_connector_msg_id_t invocation, + GVariant *parameters) +{ + dls_task_t *task = g_new0(dls_task_t, 1); + + task->type = DLS_TASK_SET_PREFER_LOCAL_ADDRESSES; + task->invocation = invocation; + task->synchronous = TRUE; + g_variant_get(parameters, "(b)", + &task->ut.prefer_local_addresses.prefer); + + return task; +} + +dls_task_t *dls_task_upload_to_any_new(dleyna_connector_msg_id_t invocation, + const gchar *path, GVariant *parameters, + GError **error) +{ + return prv_upload_new_generic(DLS_TASK_UPLOAD_TO_ANY, invocation, + path, parameters, error); +} + +dls_task_t *dls_task_upload_new(dleyna_connector_msg_id_t invocation, + const gchar *path, GVariant *parameters, + GError **error) +{ + return prv_upload_new_generic(DLS_TASK_UPLOAD, invocation, + path, parameters, error); +} + +dls_task_t *dls_task_get_upload_status_new(dleyna_connector_msg_id_t invocation, + const gchar *path, + GVariant *parameters, + GError **error) +{ + dls_task_t *task; + + task = prv_m2spec_task_new(DLS_TASK_GET_UPLOAD_STATUS, invocation, path, + "(stt)", error, TRUE); + if (!task) + goto finished; + + g_variant_get(parameters, "(u)", + &task->ut.upload_action.upload_id); + task->multiple_retvals = TRUE; + +finished: + + return task; +} + +dls_task_t *dls_task_get_upload_ids_new(dleyna_connector_msg_id_t invocation, + const gchar *path, + GError **error) +{ + dls_task_t *task; + + task = prv_m2spec_task_new(DLS_TASK_GET_UPLOAD_IDS, invocation, path, + "(@au)", error, TRUE); + if (!task) + goto finished; + +finished: + + return task; +} + +dls_task_t *dls_task_cancel_upload_new(dleyna_connector_msg_id_t invocation, + const gchar *path, + GVariant *parameters, + GError **error) +{ + dls_task_t *task; + + task = prv_m2spec_task_new(DLS_TASK_CANCEL_UPLOAD, invocation, path, + NULL, error, TRUE); + if (!task) + goto finished; + + g_variant_get(parameters, "(u)", + &task->ut.upload_action.upload_id); + +finished: + + return task; +} + +dls_task_t *dls_task_delete_new(dleyna_connector_msg_id_t invocation, + const gchar *path, + GError **error) +{ + dls_task_t *task; + + task = prv_m2spec_task_new(DLS_TASK_DELETE_OBJECT, invocation, + path, NULL, error, FALSE); + return task; +} + +dls_task_t *dls_task_create_container_new_generic( + dleyna_connector_msg_id_t invocation, + dls_task_type_t type, + const gchar *path, + GVariant *parameters, + GError **error) +{ + dls_task_t *task; + + task = prv_m2spec_task_new(type, invocation, path, + "(@o)", error, FALSE); + if (!task) + goto finished; + + g_variant_get(parameters, "(ss@as)", + &task->ut.create_container.display_name, + &task->ut.create_container.type, + &task->ut.create_container.child_types); + +finished: + + return task; +} + +dls_task_t *dls_task_create_playlist_new(dleyna_connector_msg_id_t invocation, + dls_task_type_t type, + const gchar *path, + GVariant *parameters, + GError **error) +{ + dls_task_t *task; + + task = prv_m2spec_task_new(type, invocation, path, + "(uo)", error, FALSE); + if (!task) + goto finished; + + g_variant_get(parameters, "(ssss@ao)", + &task->ut.playlist.title, + &task->ut.playlist.creator, + &task->ut.playlist.genre, + &task->ut.playlist.desc, + &task->ut.playlist.item_path); + + task->multiple_retvals = TRUE; + +finished: + + return task; + +} + +dls_task_t *dls_task_update_new(dleyna_connector_msg_id_t invocation, + const gchar *path, GVariant *parameters, + GError **error) +{ + dls_task_t *task; + + task = prv_m2spec_task_new(DLS_TASK_UPDATE_OBJECT, invocation, path, + NULL, error, FALSE); + if (!task) + goto finished; + + g_variant_get(parameters, "(@a{sv}@as)", + &task->ut.update.to_add_update, + &task->ut.update.to_delete); + +finished: + + return task; +} + +void dls_task_complete(dls_task_t *task) +{ + GVariant *variant = NULL; + + if (!task) + goto finished; + + if (task->invocation) { + if (task->result_format) { + if (task->multiple_retvals) + variant = task->result; + else + variant = g_variant_new(task->result_format, + task->result); + } + dls_server_get_connector()->return_response(task->invocation, + variant); + task->invocation = NULL; + } + +finished: + + return; +} + +void dls_task_fail(dls_task_t *task, GError *error) +{ + if (!task) + goto finished; + + if (task->invocation) { + dls_server_get_connector()->return_error(task->invocation, + error); + task->invocation = NULL; + } + +finished: + + return; +} + +void dls_task_cancel(dls_task_t *task) +{ + GError *error; + + if (!task) + goto finished; + + if (task->invocation) { + error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_CANCELLED, + "Operation cancelled."); + dls_server_get_connector()->return_error(task->invocation, + error); + task->invocation = NULL; + g_error_free(error); + } + + if (!task->synchronous) + dls_async_task_cancel((dls_async_task_t *)task); + +finished: + + return; +} + +void dls_task_delete(dls_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."); + dls_server_get_connector()->return_error(task->invocation, + error); + g_error_free(error); + } + + prv_delete(task); + +finished: + + return; +} diff --git a/libdleyna/server/task.h b/libdleyna/server/task.h new file mode 100644 index 0000000..0257a9f --- /dev/null +++ b/libdleyna/server/task.h @@ -0,0 +1,267 @@ +/* + * 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 DLS_TASK_H__ +#define DLS_TASK_H__ + +#include <gio/gio.h> +#include <glib.h> + +#include <libdleyna/core/connector.h> +#include <libdleyna/core/task-atom.h> + +#include "server.h" + +enum dls_task_type_t_ { + DLS_TASK_GET_VERSION, + DLS_TASK_GET_SERVERS, + DLS_TASK_GET_CHILDREN, + DLS_TASK_GET_ALL_PROPS, + DLS_TASK_GET_PROP, + DLS_TASK_SEARCH, + DLS_TASK_GET_RESOURCE, + DLS_TASK_SET_PREFER_LOCAL_ADDRESSES, + DLS_TASK_SET_PROTOCOL_INFO, + DLS_TASK_UPLOAD_TO_ANY, + DLS_TASK_UPLOAD, + DLS_TASK_GET_UPLOAD_STATUS, + DLS_TASK_GET_UPLOAD_IDS, + DLS_TASK_CANCEL_UPLOAD, + DLS_TASK_DELETE_OBJECT, + DLS_TASK_CREATE_CONTAINER, + DLS_TASK_CREATE_CONTAINER_IN_ANY, + DLS_TASK_UPDATE_OBJECT, + DLS_TASK_CREATE_PLAYLIST, + DLS_TASK_CREATE_PLAYLIST_IN_ANY +}; +typedef enum dls_task_type_t_ dls_task_type_t; + +typedef void (*dls_cancel_task_t)(void *handle); + +typedef struct dls_task_get_children_t_ dls_task_get_children_t; +struct dls_task_get_children_t_ { + gboolean containers; + gboolean items; + guint start; + guint count; + GVariant *filter; + gchar *sort_by; +}; + +typedef struct dls_task_get_props_t_ dls_task_get_props_t; +struct dls_task_get_props_t_ { + gchar *interface_name; +}; + +typedef struct dls_task_get_prop_t_ dls_task_get_prop_t; +struct dls_task_get_prop_t_ { + gchar *prop_name; + gchar *interface_name; +}; + +typedef struct dls_task_search_t_ dls_task_search_t; +struct dls_task_search_t_ { + gchar *query; + guint start; + guint count; + gchar *sort_by; + GVariant *filter; +}; + +typedef struct dls_task_get_resource_t_ dls_task_get_resource_t; +struct dls_task_get_resource_t_ { + gchar *protocol_info; + GVariant *filter; +}; + +typedef struct dls_task_set_prefer_local_addresses_t_ + dls_task_set_prefer_local_addresses_t; +struct dls_task_set_prefer_local_addresses_t_ { + gboolean prefer; +}; + +typedef struct dls_task_set_protocol_info_t_ dls_task_set_protocol_info_t; +struct dls_task_set_protocol_info_t_ { + gchar *protocol_info; +}; + +typedef struct dls_task_upload_t_ dls_task_upload_t; +struct dls_task_upload_t_ { + gchar *display_name; + gchar *file_path; +}; + +typedef struct dls_task_upload_action_t_ dls_task_upload_action_t; +struct dls_task_upload_action_t_ { + guint upload_id; +}; + +typedef struct dls_task_create_container_t_ dls_task_create_container_t; +struct dls_task_create_container_t_ { + gchar *display_name; + gchar *type; + GVariant *child_types; +}; + +typedef struct dls_task_update_t_ dls_task_update_t; +struct dls_task_update_t_ { + GVariant *to_add_update; + GVariant *to_delete; +}; + +typedef struct dls_task_create_playlist_t_ dls_task_create_playlist_t; +struct dls_task_create_playlist_t_ { + gchar *title; + gchar *creator; + gchar *genre; + gchar *desc; + GVariant *item_path; +}; + +typedef struct dls_task_target_info_t_ dls_task_target_info_t; +struct dls_task_target_info_t_ { + gchar *path; + gchar *root_path; + gchar *id; + dls_device_t *device; +}; + +typedef struct dls_task_t_ dls_task_t; +struct dls_task_t_ { + dleyna_task_atom_t atom; /* pseudo inheritance - MUST be first field */ + dls_task_type_t type; + dls_task_target_info_t target; + const gchar *result_format; + GVariant *result; + dleyna_connector_msg_id_t invocation; + gboolean synchronous; + gboolean multiple_retvals; + union { + dls_task_get_children_t get_children; + dls_task_get_props_t get_props; + dls_task_get_prop_t get_prop; + dls_task_search_t search; + dls_task_get_resource_t resource; + dls_task_set_prefer_local_addresses_t prefer_local_addresses; + dls_task_set_protocol_info_t protocol_info; + dls_task_upload_t upload; + dls_task_upload_action_t upload_action; + dls_task_create_container_t create_container; + dls_task_update_t update; + dls_task_create_playlist_t playlist; + } ut; +}; + +dls_task_t *dls_task_get_version_new(dleyna_connector_msg_id_t invocation); + +dls_task_t *dls_task_get_servers_new(dleyna_connector_msg_id_t invocation); + +dls_task_t *dls_task_get_children_new(dleyna_connector_msg_id_t invocation, + const gchar *path, GVariant *parameters, + gboolean items, gboolean containers, + GError **error); + +dls_task_t *dls_task_get_children_ex_new(dleyna_connector_msg_id_t invocation, + const gchar *path, + GVariant *parameters, gboolean items, + gboolean containers, + GError **error); + +dls_task_t *dls_task_get_prop_new(dleyna_connector_msg_id_t invocation, + const gchar *path, GVariant *parameters, + GError **error); + +dls_task_t *dls_task_get_props_new(dleyna_connector_msg_id_t invocation, + const gchar *path, GVariant *parameters, + GError **error); + +dls_task_t *dls_task_search_new(dleyna_connector_msg_id_t invocation, + const gchar *path, GVariant *parameters, + GError **error); + +dls_task_t *dls_task_search_ex_new(dleyna_connector_msg_id_t invocation, + const gchar *path, GVariant *parameters, + GError **error); + +dls_task_t *dls_task_get_resource_new(dleyna_connector_msg_id_t invocation, + const gchar *path, GVariant *parameters, + GError **error); + +dls_task_t *dls_task_set_protocol_info_new(dleyna_connector_msg_id_t invocation, + GVariant *parameters); + +dls_task_t *dls_task_prefer_local_addresses_new( + dleyna_connector_msg_id_t invocation, + GVariant *parameters); + +dls_task_t *dls_task_upload_to_any_new(dleyna_connector_msg_id_t invocation, + const gchar *path, GVariant *parameters, + GError **error); + +dls_task_t *dls_task_upload_new(dleyna_connector_msg_id_t invocation, + const gchar *path, GVariant *parameters, + GError **error); + +dls_task_t *dls_task_get_upload_status_new(dleyna_connector_msg_id_t invocation, + const gchar *path, + GVariant *parameters, + GError **error); + +dls_task_t *dls_task_get_upload_ids_new(dleyna_connector_msg_id_t invocation, + const gchar *path, + GError **error); + +dls_task_t *dls_task_cancel_upload_new(dleyna_connector_msg_id_t invocation, + const gchar *path, + GVariant *parameters, + GError **error); + +dls_task_t *dls_task_delete_new(dleyna_connector_msg_id_t invocation, + const gchar *path, + GError **error); + +dls_task_t *dls_task_create_container_new_generic( + dleyna_connector_msg_id_t invocation, + dls_task_type_t type, + const gchar *path, + GVariant *parameters, + GError **error); + +dls_task_t *dls_task_create_playlist_new(dleyna_connector_msg_id_t invocation, + dls_task_type_t type, + const gchar *path, + GVariant *parameters, + GError **error); + +dls_task_t *dls_task_update_new(dleyna_connector_msg_id_t invocation, + const gchar *path, GVariant *parameters, + GError **error); + +void dls_task_cancel(dls_task_t *task); + +void dls_task_complete(dls_task_t *task); + +void dls_task_fail(dls_task_t *task, GError *error); + +void dls_task_delete(dls_task_t *task); + +#endif /* DLS_TASK_H__ */ diff --git a/libdleyna/server/upnp.c b/libdleyna/server/upnp.c new file mode 100644 index 0000000..dcb9876 --- /dev/null +++ b/libdleyna/server/upnp.c @@ -0,0 +1,1110 @@ +/* + * 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-control-point.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 "interface.h" +#include "path.h" +#include "search.h" +#include "sort.h" +#include "upnp.h" + +struct dls_upnp_t_ { + dleyna_connector_id_t connection; + const dleyna_connector_dispatch_cb_t *interface_info; + GHashTable *filter_map; + GHashTable *property_map; + dls_upnp_callback_t found_server; + dls_upnp_callback_t lost_server; + GUPnPContextManager *context_manager; + void *user_data; + GHashTable *server_udn_map; + GHashTable *server_uc_map; + guint counter; +}; + +/* Private structure used in service task */ +typedef struct prv_device_new_ct_t_ prv_device_new_ct_t; +struct prv_device_new_ct_t_ { + dls_upnp_t *upnp; + char *udn; + dls_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) +{ + dls_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, priv_t->upnp->user_data); + +on_clear: + + g_hash_table_remove(priv_t->upnp->server_uc_map, priv_t->udn); + prv_device_new_free(priv_t); + + if (cancelled) + dls_device_delete(device); + + DLEYNA_LOG_DEBUG_NL(); +} + +static void prv_server_available_cb(GUPnPControlPoint *cp, + GUPnPDeviceProxy *proxy, + gpointer user_data) +{ + dls_upnp_t *upnp = user_data; + const char *udn; + dls_device_t *device; + const gchar *ip_address; + dls_device_context_t *context; + const dleyna_task_queue_key_t *queue_id; + unsigned int i; + prv_device_new_ct_t *priv_t; + + 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"); + DLEYNA_LOG_DEBUG_NL(); + + priv_t = g_new0(prv_device_new_ct_t, 1); + + queue_id = dleyna_task_processor_add_queue( + dls_server_get_task_processor(), + dleyna_service_task_create_source(), + DLS_SERVER_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 = dls_device_new(upnp->connection, proxy, ip_address, + upnp->interface_info, + upnp->property_map, upnp->counter, + 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"); + (void) dls_device_append_new_context(device, ip_address, + proxy); + } + + DLEYNA_LOG_DEBUG_NL(); + } + +on_error: + + return; +} + +static gboolean prv_subscribe_to_contents_change(gpointer user_data) +{ + dls_device_t *device = user_data; + + device->timeout_id = 0; + dls_device_subscribe_to_contents_change(device); + + return FALSE; +} + +static void prv_server_unavailable_cb(GUPnPControlPoint *cp, + GUPnPDeviceProxy *proxy, + gpointer user_data) +{ + dls_upnp_t *upnp = user_data; + const char *udn; + dls_device_t *device; + const gchar *ip_address; + unsigned int i; + dls_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) + goto on_error; + + subscribed = context->subscribed; + + (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, upnp->user_data); + 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_contents_change, + device); + } + +on_error: + + DLEYNA_LOG_DEBUG("Exit"); + DLEYNA_LOG_DEBUG_NL(); + + return; +} + +static void prv_on_context_available(GUPnPContextManager *context_manager, + GUPnPContext *context, + gpointer user_data) +{ + dls_upnp_t *upnp = user_data; + GUPnPControlPoint *cp; + + cp = gupnp_control_point_new( + context, + "urn:schemas-upnp-org:device:MediaServer: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); +} + +dls_upnp_t *dls_upnp_new(dleyna_connector_id_t connection, + const dleyna_connector_dispatch_cb_t *dispatch_table, + dls_upnp_callback_t found_server, + dls_upnp_callback_t lost_server, + void *user_data) +{ + dls_upnp_t *upnp = g_new0(dls_upnp_t, 1); + + upnp->connection = connection; + upnp->interface_info = dispatch_table; + upnp->user_data = user_data; + 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, + dls_device_delete); + + upnp->server_uc_map = g_hash_table_new_full(g_str_hash, g_str_equal, + g_free, NULL); + + dls_prop_maps_new(&upnp->property_map, &upnp->filter_map); + + upnp->context_manager = gupnp_context_manager_create(0); + + g_signal_connect(upnp->context_manager, "context-available", + G_CALLBACK(prv_on_context_available), + upnp); + + return upnp; +} + +void dls_upnp_delete(dls_upnp_t *upnp) +{ + if (upnp) { + g_object_unref(upnp->context_manager); + g_hash_table_unref(upnp->property_map); + g_hash_table_unref(upnp->filter_map); + g_hash_table_unref(upnp->server_udn_map); + g_hash_table_unref(upnp->server_uc_map); + g_free(upnp); + } +} + +GVariant *dls_upnp_get_server_ids(dls_upnp_t *upnp) +{ + GVariantBuilder vb; + GHashTableIter iter; + gpointer value; + dls_device_t *device; + GVariant *retval; + + DLEYNA_LOG_DEBUG("Enter"); + + g_variant_builder_init(&vb, G_VARIANT_TYPE("ao")); + + g_hash_table_iter_init(&iter, upnp->server_udn_map); + while (g_hash_table_iter_next(&iter, NULL, &value)) { + device = value; + DLEYNA_LOG_DEBUG("Have device %s", device->path); + g_variant_builder_add(&vb, "o", device->path); + } + + retval = g_variant_ref_sink(g_variant_builder_end(&vb)); + + DLEYNA_LOG_DEBUG("Exit"); + + return retval; +} + +GHashTable *dls_upnp_get_server_udn_map(dls_upnp_t *upnp) +{ + return upnp->server_udn_map; +} + +void dls_upnp_get_children(dls_upnp_t *upnp, dls_client_t *client, + dls_task_t *task, + dls_upnp_task_complete_t cb) +{ + dls_async_task_t *cb_data = (dls_async_task_t *)task; + dls_async_bas_t *cb_task_data; + gchar *upnp_filter = NULL; + gchar *sort_by = NULL; + + DLEYNA_LOG_DEBUG("Enter"); + + DLEYNA_LOG_DEBUG("Path: %s", task->target.path); + DLEYNA_LOG_DEBUG("Start: %u", task->ut.get_children.start); + DLEYNA_LOG_DEBUG("Count: %u", task->ut.get_children.count); + + cb_data->cb = cb; + cb_task_data = &cb_data->ut.bas; + + cb_task_data->filter_mask = + dls_props_parse_filter(upnp->filter_map, + task->ut.get_children.filter, + &upnp_filter); + + DLEYNA_LOG_DEBUG("Filter Mask 0x%"G_GUINT64_FORMAT"x", + cb_task_data->filter_mask); + + sort_by = dls_sort_translate_sort_string(upnp->filter_map, + task->ut.get_children.sort_by); + if (!sort_by) { + DLEYNA_LOG_WARNING("Invalid Sort Criteria"); + + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_BAD_QUERY, + "Sort Criteria are not valid"); + goto on_error; + } + + DLEYNA_LOG_DEBUG("Sort By %s", sort_by); + + cb_task_data->protocol_info = client->protocol_info; + + dls_device_get_children(client, task, upnp_filter, sort_by); + +on_error: + + if (!cb_data->action) + (void) g_idle_add(dls_async_task_complete, cb_data); + + g_free(sort_by); + g_free(upnp_filter); + + DLEYNA_LOG_DEBUG("Exit with %s", !cb_data->action ? "FAIL" : "SUCCESS"); +} + +void dls_upnp_get_all_props(dls_upnp_t *upnp, dls_client_t *client, + dls_task_t *task, + dls_upnp_task_complete_t cb) +{ + gboolean root_object; + dls_async_task_t *cb_data = (dls_async_task_t *)task; + dls_async_get_all_t *cb_task_data; + + DLEYNA_LOG_DEBUG("Enter"); + + DLEYNA_LOG_DEBUG("Path: %s", task->target.path); + DLEYNA_LOG_DEBUG("Interface %s", task->ut.get_prop.interface_name); + + cb_data->cb = cb; + cb_task_data = &cb_data->ut.get_all; + + root_object = task->target.id[0] == '0' && task->target.id[1] == 0; + + DLEYNA_LOG_DEBUG("Root Object = %d", root_object); + + cb_task_data->protocol_info = client->protocol_info; + + dls_device_get_all_props(client, task, root_object); + + DLEYNA_LOG_DEBUG("Exit with SUCCESS"); +} + +void dls_upnp_get_prop(dls_upnp_t *upnp, dls_client_t *client, + dls_task_t *task, + dls_upnp_task_complete_t cb) +{ + gboolean root_object; + dls_async_task_t *cb_data = (dls_async_task_t *)task; + dls_async_get_prop_t *cb_task_data; + dls_prop_map_t *prop_map; + dls_task_get_prop_t *task_data; + + DLEYNA_LOG_DEBUG("Enter"); + + DLEYNA_LOG_DEBUG("Path: %s", task->target.path); + DLEYNA_LOG_DEBUG("Interface %s", task->ut.get_prop.interface_name); + DLEYNA_LOG_DEBUG("Prop.%s", task->ut.get_prop.prop_name); + + task_data = &task->ut.get_prop; + cb_data->cb = cb; + cb_task_data = &cb_data->ut.get_prop; + + root_object = task->target.id[0] == '0' && task->target.id[1] == 0; + + DLEYNA_LOG_DEBUG("Root Object = %d", root_object); + + cb_task_data->protocol_info = client->protocol_info; + prop_map = g_hash_table_lookup(upnp->filter_map, task_data->prop_name); + + dls_device_get_prop(client, task, prop_map, root_object); + + DLEYNA_LOG_DEBUG("Exit with SUCCESS"); +} + +void dls_upnp_search(dls_upnp_t *upnp, dls_client_t *client, + dls_task_t *task, + dls_upnp_task_complete_t cb) +{ + gchar *upnp_filter = NULL; + gchar *upnp_query = NULL; + gchar *sort_by = NULL; + dls_async_task_t *cb_data = (dls_async_task_t *)task; + dls_async_bas_t *cb_task_data; + + DLEYNA_LOG_DEBUG("Enter"); + + DLEYNA_LOG_DEBUG("Path: %s", task->target.path); + DLEYNA_LOG_DEBUG("Query: %s", task->ut.search.query); + DLEYNA_LOG_DEBUG("Start: %u", task->ut.search.start); + DLEYNA_LOG_DEBUG("Count: %u", task->ut.search.count); + + cb_data->cb = cb; + cb_task_data = &cb_data->ut.bas; + + cb_task_data->filter_mask = + dls_props_parse_filter(upnp->filter_map, + task->ut.search.filter, &upnp_filter); + + DLEYNA_LOG_DEBUG("Filter Mask 0x%"G_GUINT64_FORMAT"x", + cb_task_data->filter_mask); + + upnp_query = dls_search_translate_search_string(upnp->filter_map, + task->ut.search.query); + if (!upnp_query) { + DLEYNA_LOG_WARNING("Query string is not valid:%s", + task->ut.search.query); + + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_BAD_QUERY, + "Query string is not valid."); + goto on_error; + } + + DLEYNA_LOG_DEBUG("UPnP Query %s", upnp_query); + + sort_by = dls_sort_translate_sort_string(upnp->filter_map, + task->ut.search.sort_by); + if (!sort_by) { + DLEYNA_LOG_WARNING("Invalid Sort Criteria"); + + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_BAD_QUERY, + "Sort Criteria are not valid"); + goto on_error; + } + + DLEYNA_LOG_DEBUG("Sort By %s", sort_by); + + cb_task_data->protocol_info = client->protocol_info; + + dls_device_search(client, task, upnp_filter, upnp_query, sort_by); +on_error: + + if (!cb_data->action) + (void) g_idle_add(dls_async_task_complete, cb_data); + + g_free(sort_by); + g_free(upnp_query); + g_free(upnp_filter); + + DLEYNA_LOG_DEBUG("Exit with %s", !cb_data->action ? "FAIL" : "SUCCESS"); +} + +void dls_upnp_get_resource(dls_upnp_t *upnp, dls_client_t *client, + dls_task_t *task, + dls_upnp_task_complete_t cb) +{ + dls_async_task_t *cb_data = (dls_async_task_t *)task; + dls_async_get_all_t *cb_task_data; + gchar *upnp_filter = NULL; + + DLEYNA_LOG_DEBUG("Enter"); + + DLEYNA_LOG_DEBUG("Protocol Info: %s ", task->ut.resource.protocol_info); + + cb_data->cb = cb; + cb_task_data = &cb_data->ut.get_all; + + DLEYNA_LOG_DEBUG("Root Path %s Id %s", task->target.root_path, + task->target.id); + + cb_task_data->filter_mask = + dls_props_parse_filter(upnp->filter_map, + task->ut.resource.filter, &upnp_filter); + + DLEYNA_LOG_DEBUG("Filter Mask 0x%"G_GUINT64_FORMAT"x", + cb_task_data->filter_mask); + + dls_device_get_resource(client, task, upnp_filter); + + DLEYNA_LOG_DEBUG("Exit"); +} + +static gboolean prv_compute_mime_and_class(dls_task_t *task, + dls_async_upload_t *cb_task_data, + GError **error) +{ + gchar *content_type = NULL; + + if (!g_file_test(task->ut.upload.file_path, + G_FILE_TEST_IS_REGULAR | G_FILE_TEST_EXISTS)) { + + DLEYNA_LOG_WARNING( + "File %s does not exist or is not a regular file", + task->ut.upload.file_path); + + *error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OBJECT_NOT_FOUND, + "File %s does not exist or is not a regular file", + task->ut.upload.file_path); + goto on_error; + } + + content_type = g_content_type_guess(task->ut.upload.file_path, NULL, 0, + NULL); + + if (!content_type) { + + DLEYNA_LOG_WARNING("Unable to determine Content Type for %s", + task->ut.upload.file_path); + + *error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_BAD_MIME, + "Unable to determine Content Type for %s", + task->ut.upload.file_path); + goto on_error; + } + + cb_task_data->mime_type = g_content_type_get_mime_type(content_type); + g_free(content_type); + + if (!cb_task_data->mime_type) { + + DLEYNA_LOG_WARNING("Unable to determine MIME Type for %s", + task->ut.upload.file_path); + + *error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_BAD_MIME, + "Unable to determine MIME Type for %s", + task->ut.upload.file_path); + goto on_error; + } + + if (g_content_type_is_a(cb_task_data->mime_type, "image/*")) { + cb_task_data->object_class = "object.item.imageItem"; + } else if (g_content_type_is_a(cb_task_data->mime_type, "audio/*")) { + cb_task_data->object_class = "object.item.audioItem"; + } else if (g_content_type_is_a(cb_task_data->mime_type, "video/*")) { + cb_task_data->object_class = "object.item.videoItem"; + } else { + + DLEYNA_LOG_WARNING("Unsupported MIME Type %s", + cb_task_data->mime_type); + + *error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_BAD_MIME, + "Unsupported MIME Type %s", + cb_task_data->mime_type); + goto on_error; + } + + return TRUE; + +on_error: + + return FALSE; +} + +void dls_upnp_upload_to_any(dls_upnp_t *upnp, dls_client_t *client, + dls_task_t *task, + dls_upnp_task_complete_t cb) +{ + dls_async_task_t *cb_data = (dls_async_task_t *)task; + dls_async_upload_t *cb_task_data; + + DLEYNA_LOG_DEBUG("Enter"); + + cb_data->cb = cb; + cb_task_data = &cb_data->ut.upload; + + DLEYNA_LOG_DEBUG("Root Path %s Id %s", task->target.root_path, + task->target.id); + + if (strcmp(task->target.id, "0")) { + DLEYNA_LOG_WARNING("Bad path %s", task->target.path); + + cb_data->error = + g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_BAD_PATH, + "UploadToAnyContainer must be executed on a root path"); + goto on_error; + } + + if (!prv_compute_mime_and_class(task, cb_task_data, &cb_data->error)) + goto on_error; + + DLEYNA_LOG_DEBUG("MIME Type %s", cb_task_data->mime_type); + DLEYNA_LOG_DEBUG("Object class %s", cb_task_data->object_class); + + dls_device_upload(client, task, "DLNA.ORG_AnyContainer"); + +on_error: + + if (!cb_data->action) + (void) g_idle_add(dls_async_task_complete, cb_data); + + DLEYNA_LOG_DEBUG("Exit"); +} + +void dls_upnp_upload(dls_upnp_t *upnp, dls_client_t *client, dls_task_t *task, + dls_upnp_task_complete_t cb) +{ + dls_async_task_t *cb_data = (dls_async_task_t *)task; + dls_async_upload_t *cb_task_data; + + DLEYNA_LOG_DEBUG("Enter"); + + cb_data->cb = cb; + cb_task_data = &cb_data->ut.upload; + + if (!prv_compute_mime_and_class(task, cb_task_data, &cb_data->error)) + goto on_error; + + DLEYNA_LOG_DEBUG("MIME Type %s", cb_task_data->mime_type); + DLEYNA_LOG_DEBUG("Object class %s", cb_task_data->object_class); + + dls_device_upload(client, task, task->target.id); + +on_error: + + if (!cb_data->action) + (void) g_idle_add(dls_async_task_complete, cb_data); + + DLEYNA_LOG_DEBUG("Exit"); +} + +void dls_upnp_get_upload_status(dls_upnp_t *upnp, dls_task_t *task) +{ + GError *error = NULL; + + DLEYNA_LOG_DEBUG("Enter"); + + DLEYNA_LOG_DEBUG("Root Path %s Id %s", task->target.root_path, + task->target.id); + + if (strcmp(task->target.id, "0")) { + DLEYNA_LOG_WARNING("Bad path %s", task->target.path); + + error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_BAD_PATH, + "GetUploadStatus must be executed on a root path"); + goto on_error; + } + + (void) dls_device_get_upload_status(task, &error); + +on_error: + + if (error) { + dls_task_fail(task, error); + g_error_free(error); + } else { + dls_task_complete(task); + } + + DLEYNA_LOG_DEBUG("Exit"); +} + +void dls_upnp_get_upload_ids(dls_upnp_t *upnp, dls_task_t *task) +{ + GError *error = NULL; + + DLEYNA_LOG_DEBUG("Enter"); + + DLEYNA_LOG_DEBUG("Root Path %s Id %s", task->target.root_path, + task->target.id); + + if (strcmp(task->target.id, "0")) { + DLEYNA_LOG_WARNING("Bad path %s", task->target.path); + + error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_BAD_PATH, + "GetUploadIDs must be executed on a root path"); + goto on_error; + } + + dls_device_get_upload_ids(task); + +on_error: + + if (error) { + dls_task_fail(task, error); + g_error_free(error); + } else { + dls_task_complete(task); + } + + DLEYNA_LOG_DEBUG("Exit"); +} + +void dls_upnp_cancel_upload(dls_upnp_t *upnp, dls_task_t *task) +{ + GError *error = NULL; + + DLEYNA_LOG_DEBUG("Enter"); + + DLEYNA_LOG_DEBUG("Root Path %s Id %s", task->target.root_path, + task->target.id); + + if (strcmp(task->target.id, "0")) { + DLEYNA_LOG_WARNING("Bad path %s", task->target.path); + + error = g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_BAD_PATH, + "CancelUpload must be executed on a root path"); + goto on_error; + } + + (void) dls_device_cancel_upload(task, &error); + +on_error: + + if (error) { + dls_task_fail(task, error); + g_error_free(error); + } else { + dls_task_complete(task); + } + + DLEYNA_LOG_DEBUG("Exit"); +} + +void dls_upnp_delete_object(dls_upnp_t *upnp, dls_client_t *client, + dls_task_t *task, + dls_upnp_task_complete_t cb) +{ + dls_async_task_t *cb_data = (dls_async_task_t *)task; + + DLEYNA_LOG_DEBUG("Enter"); + + cb_data->cb = cb; + + DLEYNA_LOG_DEBUG("Root Path %s Id %s", task->target.root_path, + task->target.id); + + dls_device_delete_object(client, task); + + DLEYNA_LOG_DEBUG("Exit"); +} + +void dls_upnp_create_container(dls_upnp_t *upnp, dls_client_t *client, + dls_task_t *task, + dls_upnp_task_complete_t cb) +{ + dls_async_task_t *cb_data = (dls_async_task_t *)task; + + DLEYNA_LOG_DEBUG("Enter"); + + cb_data->cb = cb; + + DLEYNA_LOG_DEBUG("Root Path %s Id %s", task->target.root_path, + task->target.id); + + dls_device_create_container(client, task, task->target.id); + + DLEYNA_LOG_DEBUG("Exit"); +} + +void dls_upnp_create_container_in_any(dls_upnp_t *upnp, dls_client_t *client, + dls_task_t *task, + dls_upnp_task_complete_t cb) +{ + dls_async_task_t *cb_data = (dls_async_task_t *)task; + + DLEYNA_LOG_DEBUG("Enter"); + + cb_data->cb = cb; + + DLEYNA_LOG_DEBUG("Root Path %s Id %s", task->target.root_path, + task->target.id); + + if (strcmp(task->target.id, "0")) { + DLEYNA_LOG_WARNING("Bad path %s", task->target.path); + + cb_data->error = + g_error_new(DLEYNA_SERVER_ERROR, DLEYNA_ERROR_BAD_PATH, + "CreateContainerInAnyContainer must be executed on a root path"); + goto on_error; + } + + dls_device_create_container(client, task, "DLNA.ORG_AnyContainer"); + +on_error: + + if (!cb_data->action) + (void) g_idle_add(dls_async_task_complete, cb_data); + + DLEYNA_LOG_DEBUG("Exit"); +} + +void dls_upnp_update_object(dls_upnp_t *upnp, dls_client_t *client, + dls_task_t *task, + dls_upnp_task_complete_t cb) +{ + dls_async_task_t *cb_data = (dls_async_task_t *)task; + dls_async_update_t *cb_task_data; + dls_upnp_prop_mask mask; + gchar *upnp_filter = NULL; + dls_task_update_t *task_data; + + DLEYNA_LOG_DEBUG("Enter"); + + cb_data->cb = cb; + cb_task_data = &cb_data->ut.update; + task_data = &task->ut.update; + + DLEYNA_LOG_DEBUG("Root Path %s Id %s", task->target.root_path, + task->target.id); + + if (!dls_props_parse_update_filter(upnp->filter_map, + task_data->to_add_update, + task_data->to_delete, + &mask, &upnp_filter)) { + DLEYNA_LOG_WARNING("Invalid Parameter"); + + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OPERATION_FAILED, + "Invalid Parameter"); + goto on_error; + } + + cb_task_data->map = upnp->filter_map; + + DLEYNA_LOG_DEBUG("Filter = %s", upnp_filter); + DLEYNA_LOG_DEBUG("Mask = 0x%"G_GUINT64_FORMAT"x", mask); + + if (mask == 0) { + DLEYNA_LOG_WARNING("Empty Parameters"); + + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OPERATION_FAILED, + "Empty Parameters"); + + goto on_error; + } + + dls_device_update_object(client, task, upnp_filter); + +on_error: + + g_free(upnp_filter); + + if (!cb_data->action) + (void) g_idle_add(dls_async_task_complete, cb_data); + + DLEYNA_LOG_DEBUG("Exit"); +} + +void dls_upnp_create_playlist(dls_upnp_t *upnp, dls_client_t *client, + dls_task_t *task, + dls_upnp_task_complete_t cb) +{ + dls_async_task_t *cb_data = (dls_async_task_t *)task; + dls_task_create_playlist_t *task_data; + + DLEYNA_LOG_DEBUG("Enter"); + + cb_data->cb = cb; + task_data = &task->ut.playlist; + + DLEYNA_LOG_DEBUG("Root Path: %s - Id: %s", task->target.root_path, + task->target.id); + + if (!task_data->title || !*task_data->title) + goto on_param_error; + + if (!g_variant_n_children(task_data->item_path)) + goto on_param_error; + + DLEYNA_LOG_DEBUG_NL(); + DLEYNA_LOG_DEBUG("Title = %s", task_data->title); + DLEYNA_LOG_DEBUG("Creator = %s", task_data->creator); + DLEYNA_LOG_DEBUG("Genre = %s", task_data->genre); + DLEYNA_LOG_DEBUG("Desc = %s", task_data->desc); + DLEYNA_LOG_DEBUG_NL(); + + dls_device_playlist_upload(client, task, task->target.id); + + DLEYNA_LOG_DEBUG("Exit"); + + return; + +on_param_error: + + DLEYNA_LOG_WARNING("Invalid Parameter"); + + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OPERATION_FAILED, + "Invalid Parameter"); + + (void) g_idle_add(dls_async_task_complete, cb_data); + + DLEYNA_LOG_DEBUG("Exit failure"); +} + +void dls_upnp_create_playlist_in_any(dls_upnp_t *upnp, dls_client_t *client, + dls_task_t *task, + dls_upnp_task_complete_t cb) +{ + dls_async_task_t *cb_data = (dls_async_task_t *)task; + dls_task_create_playlist_t *task_data; + + DLEYNA_LOG_DEBUG("Enter"); + + cb_data->cb = cb; + task_data = &task->ut.playlist; + + DLEYNA_LOG_DEBUG("Root Path: %s - Id: %s", task->target.root_path, + task->target.id); + + if (strcmp(task->target.id, "0")) { + DLEYNA_LOG_WARNING("Bad path %s", task->target.path); + + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_BAD_PATH, + "CreatePlayListInAny must be executed on a root path"); + + goto on_error; + } + + if (!task_data->title || !*task_data->title) + goto on_param_error; + + if (!g_variant_n_children(task_data->item_path)) + goto on_param_error; + + DLEYNA_LOG_DEBUG_NL(); + DLEYNA_LOG_DEBUG("Title = %s", task_data->title); + DLEYNA_LOG_DEBUG("Creator = %s", task_data->creator); + DLEYNA_LOG_DEBUG("Genre = %s", task_data->genre); + DLEYNA_LOG_DEBUG("Desc = %s", task_data->desc); + DLEYNA_LOG_DEBUG_NL(); + + dls_device_playlist_upload(client, task, "DLNA.ORG_AnyContainer"); + + DLEYNA_LOG_DEBUG("Exit"); + + return; + +on_param_error: + + DLEYNA_LOG_WARNING("Invalid Parameter"); + + cb_data->error = g_error_new(DLEYNA_SERVER_ERROR, + DLEYNA_ERROR_OPERATION_FAILED, + "Invalid Parameter"); +on_error: + + (void) g_idle_add(dls_async_task_complete, cb_data); + + DLEYNA_LOG_DEBUG("Exit failure"); +} + +void dls_upnp_unsubscribe(dls_upnp_t *upnp) +{ + GHashTableIter iter; + gpointer value; + dls_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, &value)) { + device = value; + dls_device_unsubscribe(device); + } + + DLEYNA_LOG_DEBUG("Exit"); +} + +static gboolean prv_device_uc_find(gpointer key, gpointer value, + gpointer user_data) +{ + prv_device_new_ct_t *priv_t = (prv_device_new_ct_t *)value; + + return (priv_t->device == user_data) ? TRUE : FALSE; +} + +static gboolean prv_device_find(gpointer key, gpointer value, + gpointer user_data) +{ + return (value == user_data) ? TRUE : FALSE; +} + +gboolean dls_upnp_device_context_exist(dls_device_t *device, + dls_device_context_t *context) +{ + gpointer result; + guint i; + gboolean found = FALSE; + dls_upnp_t *upnp = dls_server_get_upnp(); + + if (upnp == NULL) + goto on_exit; + + /* Check if the device still exist */ + result = g_hash_table_find(upnp->server_udn_map, prv_device_find, + device); + + if (result == NULL) + if (g_hash_table_find(upnp->server_uc_map, prv_device_uc_find, + device) == NULL) + goto on_exit; + + /* Search if the context still exist in the device */ + for (i = 0; i < device->contexts->len; ++i) { + if (g_ptr_array_index(device->contexts, i) == context) { + found = TRUE; + break; + } + } + +on_exit: + return found; +} diff --git a/libdleyna/server/upnp.h b/libdleyna/server/upnp.h new file mode 100644 index 0000000..dfe2d1b --- /dev/null +++ b/libdleyna/server/upnp.h @@ -0,0 +1,109 @@ +/* + * 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 DLS_UPNP_H__ +#define DLS_UPNP_H__ + +#include <libdleyna/core/connector.h> + +#include "client.h" +#include "async.h" + +typedef void (*dls_upnp_callback_t)(const gchar *path, void *user_data); +typedef void (*dls_upnp_task_complete_t)(dls_task_t *task, GError *error); + +dls_upnp_t *dls_upnp_new(dleyna_connector_id_t connection, + const dleyna_connector_dispatch_cb_t *dispatch_table, + dls_upnp_callback_t found_server, + dls_upnp_callback_t lost_server, + void *user_data); + +void dls_upnp_delete(dls_upnp_t *upnp); + +GVariant *dls_upnp_get_server_ids(dls_upnp_t *upnp); + +GHashTable *dls_upnp_get_server_udn_map(dls_upnp_t *upnp); + +void dls_upnp_get_children(dls_upnp_t *upnp, dls_client_t *client, + dls_task_t *task, + dls_upnp_task_complete_t cb); + +void dls_upnp_get_all_props(dls_upnp_t *upnp, dls_client_t *client, + dls_task_t *task, + dls_upnp_task_complete_t cb); + +void dls_upnp_get_prop(dls_upnp_t *upnp, dls_client_t *client, + dls_task_t *task, + dls_upnp_task_complete_t cb); + +void dls_upnp_search(dls_upnp_t *upnp, dls_client_t *client, + dls_task_t *task, + dls_upnp_task_complete_t cb); + +void dls_upnp_get_resource(dls_upnp_t *upnp, dls_client_t *client, + dls_task_t *task, + dls_upnp_task_complete_t cb); + +void dls_upnp_upload_to_any(dls_upnp_t *upnp, dls_client_t *client, + dls_task_t *task, + dls_upnp_task_complete_t cb); + +void dls_upnp_upload(dls_upnp_t *upnp, dls_client_t *client, + dls_task_t *task, + dls_upnp_task_complete_t cb); + +void dls_upnp_get_upload_status(dls_upnp_t *upnp, dls_task_t *task); + +void dls_upnp_get_upload_ids(dls_upnp_t *upnp, dls_task_t *task); + +void dls_upnp_cancel_upload(dls_upnp_t *upnp, dls_task_t *task); + +void dls_upnp_delete_object(dls_upnp_t *upnp, dls_client_t *client, + dls_task_t *task, + dls_upnp_task_complete_t cb); + +void dls_upnp_create_container(dls_upnp_t *upnp, dls_client_t *client, + dls_task_t *task, + dls_upnp_task_complete_t cb); + +void dls_upnp_create_container_in_any(dls_upnp_t *upnp, dls_client_t *client, + dls_task_t *task, + dls_upnp_task_complete_t cb); + +void dls_upnp_update_object(dls_upnp_t *upnp, dls_client_t *client, + dls_task_t *task, + dls_upnp_task_complete_t cb); + +void dls_upnp_create_playlist(dls_upnp_t *upnp, dls_client_t *client, + dls_task_t *task, + dls_upnp_task_complete_t cb); + +void dls_upnp_create_playlist_in_any(dls_upnp_t *upnp, dls_client_t *client, + dls_task_t *task, + dls_upnp_task_complete_t cb); + +void dls_upnp_unsubscribe(dls_upnp_t *upnp); + +gboolean dls_upnp_device_context_exist(dls_device_t *device, + dls_device_context_t *context); + +#endif /* DLS_UPNP_H__ */ |