From c9a33351febfbafb58f477b1bbc9bf14a4e35d05 Mon Sep 17 00:00:00 2001 From: Manuel Bachmann Date: Fri, 22 Jan 2016 12:25:51 +0100 Subject: Implement Media Plugin upload API, update README.md Media Plugin now supports a "upload?value=" API. Add Media Plugin requirements to README.md. Signed-off-by: Manuel Bachmann --- CMakeLists.txt | 2 +- README.md | 3 +- plugins/media/media-api.c | 24 +++++- plugins/media/media-rygel.c | 206 ++++++++++++++++++++++++++++++++++++++------ plugins/media/media-rygel.h | 7 ++ 5 files changed, 215 insertions(+), 27 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 920899a3..6049f875 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,7 +42,7 @@ PKG_CHECK_MODULES(dbus REQUIRED dbus-1) PKG_CHECK_MODULES(alsa alsa) PKG_CHECK_MODULES(pulseaudio libpulse libpulse-simple) PKG_CHECK_MODULES(librtlsdr librtlsdr>=0.5.0) -PKG_CHECK_MODULES(gupnp gupnp-1.0 gssdp-1.0 gobject-2.0) +PKG_CHECK_MODULES(gupnp gupnp-1.0 gupnp-av-1.0 gssdp-1.0 gobject-2.0 gio-2.0) IF(alsa_FOUND) MESSAGE(STATUS "ALSA found ; will compile Audio plugin... (PLUGIN)") diff --git a/README.md b/README.md index 5ba5ceb8..0cfc8da3 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ optionally, for plugins : * alsa ("libasound2-dev/alsa-devel"); * pulseaudio ("libpulse-dev/libpulse-devel"); * rtl-sdr >= 0.5.0 (fetch and build from "git://git.osmocom.org/rtl-sdr"); + * GUPnP ("libglib2.0-dev libgupnp-av-1.0-dev/glib2-devel libgupnp-av-devel"); and the following tools: * pkg-config; @@ -27,7 +28,7 @@ and the following tools: To install all dependencies under OpenSUSE (excepting rtl-sdr), please type: ``` -$ zypper in file-devel libmicrohttpd-devel libjson-c-devel libuuid-devel dbus-1-devel alsa-devel libpulse-devel pkg-config cmake +$ zypper in file-devel libmicrohttpd-devel libjson-c-devel libuuid-devel dbus-1-devel alsa-devel libpulse-devel glib2-devel libgupnp-av-devel pkg-config cmake ``` To build, move to the root directory and type: diff --git a/plugins/media/media-api.c b/plugins/media/media-api.c index dc73136f..05237d39 100644 --- a/plugins/media/media-api.c +++ b/plugins/media/media-api.c @@ -136,6 +136,27 @@ STATIC json_object* paused (AFB_request *request) { /* AFB_SESSION_CHECK */ return jsonNewMessage(AFB_SUCCESS, "Paused media"); } +STATIC json_object* upload (AFB_request *request) { /* AFB_SESSION_CHECK */ + + mediaCtxHandleT *ctx = (mediaCtxHandleT*)request->context; + const char *value = getQueryValue (request, "value"); + json_object *jresp; + char path[256]; + + /* no "?value=" parameter : return error */ + if (!value) + return jsonNewMessage(AFB_FAIL, "You must provide a file name"); + + snprintf (path, sizeof(path), "/tmp/%s", value); + if (access (path, R_OK) == -1) + return jsonNewMessage(AFB_FAIL, "File not found"); + + if (!_rygel_upload (ctx, path)) + return jsonNewMessage(AFB_FAIL, "Error when uploading file... could not complete"); + + return jsonNewMessage(AFB_SUCCESS, "File successfully uploaded"); +} + STATIC json_object* ping (AFB_request *request) { /* AFB_SESSION_NONE */ return jsonNewMessage(AFB_SUCCESS, "Ping Binder Daemon - Media API"); } @@ -147,7 +168,8 @@ STATIC AFB_restapi pluginApis[]= { {"choose" , AFB_SESSION_CHECK, (AFB_apiCB)choose , "Media API - choose" }, {"play" , AFB_SESSION_CHECK, (AFB_apiCB)play , "Media API - play" }, {"stop" , AFB_SESSION_CHECK, (AFB_apiCB)stop , "Media API - stop" }, - {"paused" , AFB_SESSION_CHECK, (AFB_apiCB)paused , "Media API - paused" }, + {"pause" , AFB_SESSION_CHECK, (AFB_apiCB)paused , "Media API - pause" }, + {"upload" , AFB_SESSION_CHECK, (AFB_apiCB)upload , "Media API - upload" }, {"ping" , AFB_SESSION_NONE, (AFB_apiCB)ping , "Media API - ping" }, {NULL} }; diff --git a/plugins/media/media-rygel.c b/plugins/media/media-rygel.c index 3ad008ef..539b6189 100644 --- a/plugins/media/media-rygel.c +++ b/plugins/media/media-rygel.c @@ -65,6 +65,7 @@ PUBLIC unsigned char _rygel_init (mediaCtxHandleT *ctx) { dev_ctx[client_count]->av_transport = NULL; dev_ctx[client_count]->state = STOP; dev_ctx[client_count]->target_state = STOP; + dev_ctx[client_count]->transfer_started = 0; client_count++; @@ -96,27 +97,28 @@ PUBLIC char* _rygel_list (mediaCtxHandleT *ctx) { return NULL; raw = _rygel_list_raw (dev_ctx_c, NULL); + if (!raw) + return NULL; - if (raw) { - start = strstr (raw, ""); - if (!start) return NULL; - - result = strdup(""); + start = strstr (raw, ""); + if (!start) + return NULL; - while (start) { - start = strstr (start, ""); - if (!start) break; - end = strstr (start, ""); - start += 10; length = end - start; + result = strdup(""); + while (start) { + start = strstr (start, ""); + if (!start) break; + end = strstr (start, ""); + start += 10; + length = end - start; - title = (char*) malloc (length+1); - strncpy (title, start, length); - title[length] = '\0'; + title = (char*) malloc (length+1); + strncpy (title, start, length); + title[length] = '\0'; - asprintf (&result, "%s%02d:%s::", result, i, title); + asprintf (&result, "%s%02d:%s::", result, i, title); - free (title); i++; - } + free (title); i++; } return result; @@ -140,6 +142,24 @@ PUBLIC unsigned char _rygel_choose (mediaCtxHandleT *ctx, unsigned int index) { return 1; } +PUBLIC unsigned char _rygel_upload (mediaCtxHandleT *ctx, char *path) { + + dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server; + char *raw, *upload_id; + + if (!dev_ctx_c) + return 0; + + raw = _rygel_list_raw (dev_ctx_c, NULL); + if (!raw) + return 0; + + /* for now, we always use the same upload container id */ + upload_id = _rygel_find_upload_id (dev_ctx_c, raw); + + return _rygel_start_uploading (dev_ctx_c, path, upload_id); +} + PUBLIC unsigned char _rygel_do (mediaCtxHandleT *ctx, State state) { dev_ctx_T *dev_ctx_c = (dev_ctx_T*)ctx->media_server; @@ -170,7 +190,6 @@ STATIC char* _rygel_list_raw (dev_ctx_T* dev_ctx_c, unsigned int *count) { dev_ctx_c->content_res = NULL; dev_ctx_c->content_num = 0; - content_dir_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->content_dir); gupnp_service_proxy_begin_action (content_dir_proxy, "Browse", _rygel_content_cb, dev_ctx_c, @@ -197,6 +216,21 @@ STATIC char* _rygel_list_raw (dev_ctx_T* dev_ctx_c, unsigned int *count) { return dev_ctx_c->content_res; } +STATIC char* _rygel_find_upload_id (dev_ctx_T* dev_ctx_c, char *raw) { + + char *found; + char id[33]; + + found = strstr (raw, "parentID=\""); + found += 10; + + /* IDs are 32-bit strings */ + strncpy (id, found, 32); + id[32] = '\0'; + + return strdup (id); +} + STATIC char* _rygel_find_id_for_index (dev_ctx_T* dev_ctx_c, char *raw, unsigned int index) { char *found = raw; @@ -208,13 +242,13 @@ STATIC char* _rygel_find_id_for_index (dev_ctx_T* dev_ctx_c, char *raw, unsigned found += 9; if (i == index) { - /* IDs are 32-bit numbers */ + /* IDs are 32-bit strings */ strncpy (id, found, 32); id[32] = '\0'; } } - return strdup(id); + return strdup (id); } STATIC char* _rygel_find_metadata_for_id (dev_ctx_T* dev_ctx_c, char *id) { @@ -277,6 +311,61 @@ STATIC char* _rygel_find_uri_for_metadata (dev_ctx_T* dev_ctx_c, char *metadata) return uri; } +STATIC unsigned char _rygel_start_uploading (dev_ctx_T* dev_ctx_c, char *path, char *upload_id) { + + GUPnPServiceProxy *content_dir_proxy; + GUPnPDIDLLiteWriter *didl_writer; + GUPnPDIDLLiteObject *didl_object; + char *didl, *content_type, *mime_type, *upnp_class; + struct timeval tv_start, tv_now; + + didl_writer = gupnp_didl_lite_writer_new (NULL); + didl_object = GUPNP_DIDL_LITE_OBJECT (gupnp_didl_lite_writer_add_item (didl_writer)); + + /* create the metadata for the file */ + gupnp_didl_lite_object_set_parent_id (didl_object, upload_id); + gupnp_didl_lite_object_set_id (didl_object, ""); + gupnp_didl_lite_object_set_restricted (didl_object, FALSE); + gupnp_didl_lite_object_set_title (didl_object, g_path_get_basename (path)); + /* deduce the UPnP class from the MIME type ("audio/ogg" e.g.) */ + content_type = g_content_type_guess (path, NULL, 0, NULL); + mime_type = g_content_type_get_mime_type (content_type); + if (strstr (mime_type, "audio/")) + upnp_class = strdup ("object.item.audioItem.musicTrack"); + else if (strstr (mime_type, "video/")) + upnp_class = strdup ("object.item.videoItem"); + else if (strstr (mime_type, "image/")) + upnp_class = strdup ("object.item.imageItem"); + else + upnp_class = strdup ("object.item"); + gupnp_didl_lite_object_set_upnp_class (didl_object, upnp_class); + didl = gupnp_didl_lite_writer_get_string (didl_writer); + + dev_ctx_c->transfer_path = path; + dev_ctx_c->transfer_started = 0; + content_dir_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->content_dir); + + gupnp_service_proxy_begin_action (content_dir_proxy, "CreateObject", _rygel_upload_cb, dev_ctx_c, + "ContainerID", G_TYPE_STRING, upload_id, + "Elements", G_TYPE_STRING, didl, + NULL); + + gettimeofday (&tv_start, NULL); + gettimeofday (&tv_now, NULL); + while (tv_now.tv_sec - tv_start.tv_sec <= 5) { + + g_main_context_iteration (dev_ctx_c->loop, FALSE); + + if (dev_ctx_c->transfer_started) + break; + gettimeofday (&tv_now, NULL); + } + if (!dev_ctx_c->transfer_started) + return 0; + + return 1; +} + STATIC unsigned char _rygel_start_doing (dev_ctx_T* dev_ctx_c, char *uri, char *metadata, State state) { GUPnPServiceProxy *av_transport_proxy; @@ -287,14 +376,13 @@ STATIC unsigned char _rygel_start_doing (dev_ctx_T* dev_ctx_c, char *uri, char * return 0; } dev_ctx_c->target_state = state; - av_transport_proxy = GUPNP_SERVICE_PROXY (dev_ctx_c->av_transport); gupnp_service_proxy_begin_action (av_transport_proxy, "SetAVTransportURI", _rygel_select_cb, dev_ctx_c, "InstanceID", G_TYPE_UINT, 0, "CurrentURI", G_TYPE_STRING, uri, "CurrentURIMetaData", G_TYPE_STRING, metadata, - NULL); + NULL); gettimeofday (&tv_start, NULL); gettimeofday (&tv_now, NULL); @@ -449,7 +537,7 @@ STATIC void _rygel_metadata_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProx } STATIC void _rygel_select_cb (GUPnPServiceProxy *av_transport, GUPnPServiceProxyAction *action, - gpointer data) + gpointer data) { dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data; @@ -494,14 +582,84 @@ STATIC void _rygel_select_cb (GUPnPServiceProxy *av_transport, GUPnPServiceProxy } } +STATIC void _rygel_upload_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProxyAction *action, + gpointer data) +{ + dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data; + GUPnPServiceProxy *content_dir_proxy; + GError *error; + char *result, *start, *end, *dst_uri, *src_uri; + int length; + struct timeval tv_start, tv_now; + + content_dir_proxy = GUPNP_SERVICE_PROXY (content_dir); + + if (!gupnp_service_proxy_end_action (content_dir, action, &error, + "Result", G_TYPE_STRING, &result, + NULL)) + return; + + start = strstr (result, "context), + gupnp_context_get_port (dev_ctx_c->context), + dev_ctx_c->transfer_path); + + /* host the file */ + gupnp_context_host_path (dev_ctx_c->context, dev_ctx_c->transfer_path, + dev_ctx_c->transfer_path); + + gupnp_service_proxy_begin_action (content_dir_proxy, "ImportResource", _rygel_transfer_cb, dev_ctx_c, + "SourceURI", G_TYPE_STRING, src_uri, + "DestinationURI", G_TYPE_STRING, dst_uri, + NULL); + + gettimeofday (&tv_start, NULL); + gettimeofday (&tv_now, NULL); + while (tv_now.tv_sec - tv_start.tv_sec <= 5) { + + g_main_context_iteration (dev_ctx_c->loop, FALSE); + + if (dev_ctx_c->transfer_started) + break; + gettimeofday (&tv_now, NULL); + } +} + +STATIC void _rygel_transfer_cb (GUPnPServiceProxy *content_dir, GUPnPServiceProxyAction *action, + gpointer data) +{ + dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data; + GError *error; + guint transfer_id; + + if (!gupnp_service_proxy_end_action (content_dir, action, &error, + "TransferID", G_TYPE_UINT, &transfer_id, + NULL)) + return; + + dev_ctx_c->transfer_started = 1; +} + STATIC void _rygel_do_cb (GUPnPServiceProxy *av_transport, GUPnPServiceProxyAction *action, gpointer data) { dev_ctx_T *dev_ctx_c = (dev_ctx_T*)data; GError *error; - gupnp_service_proxy_end_action (av_transport, action, &error, - NULL); + if (!gupnp_service_proxy_end_action (av_transport, action, &error, + NULL)) + return; dev_ctx_c->state = dev_ctx_c->target_state; } diff --git a/plugins/media/media-rygel.h b/plugins/media/media-rygel.h index da028c13..c70ff5c3 100644 --- a/plugins/media/media-rygel.h +++ b/plugins/media/media-rygel.h @@ -23,6 +23,7 @@ #include #include +#include #include "local-def.h" @@ -44,12 +45,16 @@ struct dev_ctx { int content_num; State state; State target_state; + char *transfer_path; + unsigned char transfer_started; }; STATIC char* _rygel_list_raw (dev_ctx_T *, unsigned int *); +STATIC char* _rygel_find_upload_id (dev_ctx_T *, char *); STATIC char* _rygel_find_id_for_index (dev_ctx_T *, char *, unsigned int); STATIC char* _rygel_find_metadata_for_id (dev_ctx_T *, char *); STATIC char* _rygel_find_uri_for_metadata (dev_ctx_T *, char *); +STATIC unsigned char _rygel_start_uploading (dev_ctx_T *, char *, char *); STATIC unsigned char _rygel_start_doing (dev_ctx_T *, char *, char *, State); STATIC unsigned char _rygel_find_av_transport (dev_ctx_T *); STATIC void _rygel_device_cb (GUPnPControlPoint *, GUPnPDeviceProxy *, gpointer); @@ -57,6 +62,8 @@ STATIC void _rygel_av_transport_cb (GUPnPControlPoint *, GUPnPDeviceProxy *, gpo STATIC void _rygel_content_cb (GUPnPServiceProxy *, GUPnPServiceProxyAction *, gpointer); STATIC void _rygel_metadata_cb (GUPnPServiceProxy *, GUPnPServiceProxyAction *, gpointer); STATIC void _rygel_select_cb (GUPnPServiceProxy *, GUPnPServiceProxyAction *, gpointer); +STATIC void _rygel_upload_cb (GUPnPServiceProxy *, GUPnPServiceProxyAction *, gpointer); +STATIC void _rygel_transfer_cb (GUPnPServiceProxy *, GUPnPServiceProxyAction *, gpointer); STATIC void _rygel_do_cb (GUPnPServiceProxy *, GUPnPServiceProxyAction *, gpointer); static unsigned int client_count = 0; -- cgit 1.2.3-korg