851 lines
26 KiB
C++
851 lines
26 KiB
C++
/*
|
|
* cds.c : GeeXboX uShare Content Directory Service
|
|
* Originally developped for the GeeXboX project.
|
|
* Parts of the code are originated from GMediaServer from Oskar Liljeblad.
|
|
* Copyright (C) 2005-2007 Benjamin Zores <ben@geexbox.org>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <upnp/upnp.h>
|
|
#include <upnp/upnptools.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
#include "ushare.h"
|
|
#include "services.h"
|
|
#include "ushare.h"
|
|
#include "services.h"
|
|
#include "metadata.h"
|
|
#include "mime.h"
|
|
#include "buffer.h"
|
|
#include "minmax.h"
|
|
|
|
/* Represent the CDS GetSearchCapabilities action. */
|
|
#define SERVICE_CDS_ACTION_SEARCH_CAPS "GetSearchCapabilities"
|
|
|
|
/* Represent the CDS GetSortCapabilities action. */
|
|
#define SERVICE_CDS_ACTION_SORT_CAPS "GetSortCapabilities"
|
|
|
|
/* Represent the CDS GetSystemUpdateID action. */
|
|
#define SERVICE_CDS_ACTION_UPDATE_ID "GetSystemUpdateID"
|
|
|
|
/* Represent the CDS Browse action. */
|
|
#define SERVICE_CDS_ACTION_BROWSE "Browse"
|
|
|
|
/* Represent the CDS Search action. */
|
|
#define SERVICE_CDS_ACTION_SEARCH "Search"
|
|
|
|
/* Represent the CDS SearchCaps argument. */
|
|
#define SERVICE_CDS_ARG_SEARCH_CAPS "SearchCaps"
|
|
|
|
/* Represent the CDS SortCaps argument. */
|
|
#define SERVICE_CDS_ARG_SORT_CAPS "SortCaps"
|
|
|
|
/* Represent the CDS UpdateId argument. */
|
|
#define SERVICE_CDS_ARG_UPDATE_ID "Id"
|
|
|
|
/* Represent the CDS StartingIndex argument. */
|
|
#define SERVICE_CDS_ARG_START_INDEX "StartingIndex"
|
|
|
|
/* Represent the CDS RequestedCount argument. */
|
|
#define SERVICE_CDS_ARG_REQUEST_COUNT "RequestedCount"
|
|
|
|
/* Represent the CDS ObjectID argument. */
|
|
#define SERVICE_CDS_ARG_OBJECT_ID "ObjectID"
|
|
|
|
/* Represent the CDS Filter argument. */
|
|
#define SERVICE_CDS_ARG_FILTER "Filter"
|
|
|
|
/* Represent the CDS BrowseFlag argument. */
|
|
#define SERVICE_CDS_ARG_BROWSE_FLAG "BrowseFlag"
|
|
|
|
/* Represent the CDS SortCriteria argument. */
|
|
#define SERVICE_CDS_ARG_SORT_CRIT "SortCriteria"
|
|
|
|
/* Represent the CDS SearchCriteria argument. */
|
|
#define SERVICE_CDS_ARG_SEARCH_CRIT "SearchCriteria"
|
|
|
|
/* Represent the CDS Root Object ID argument. */
|
|
#define SERVICE_CDS_ROOT_OBJECT_ID "0"
|
|
|
|
/* Represent the CDS DIDL Message Metadata Browse flag argument. */
|
|
#define SERVICE_CDS_BROWSE_METADATA "BrowseMetadata"
|
|
|
|
/* Represent the CDS DIDL Message DirectChildren Browse flag argument. */
|
|
#define SERVICE_CDS_BROWSE_CHILDREN "BrowseDirectChildren"
|
|
|
|
/* Represent the CDS DIDL Message Result argument. */
|
|
#define SERVICE_CDS_DIDL_RESULT "Result"
|
|
|
|
/* Represent the CDS DIDL Message NumberReturned argument. */
|
|
#define SERVICE_CDS_DIDL_NUM_RETURNED "NumberReturned"
|
|
|
|
/* Represent the CDS DIDL Message TotalMatches argument. */
|
|
#define SERVICE_CDS_DIDL_TOTAL_MATCH "TotalMatches"
|
|
|
|
/* Represent the CDS DIDL Message UpdateID argument. */
|
|
#define SERVICE_CDS_DIDL_UPDATE_ID "UpdateID"
|
|
|
|
/* DIDL parameters */
|
|
/* Represent the CDS DIDL Message Header Namespace. */
|
|
#define DIDL_NAMESPACE \
|
|
"xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\" " \
|
|
"xmlns:dc=\"http://purl.org/dc/elements/1.1/\" " \
|
|
"xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\""
|
|
|
|
/* Represent the CDS DIDL Message Header Tag. */
|
|
#define DIDL_LITE "DIDL-Lite"
|
|
|
|
/* Represent the CDS DIDL Message Item value. */
|
|
#define DIDL_ITEM "item"
|
|
|
|
/* Represent the CDS DIDL Message Item ID value. */
|
|
#define DIDL_ITEM_ID "id"
|
|
|
|
/* Represent the CDS DIDL Message Item Parent ID value. */
|
|
#define DIDL_ITEM_PARENT_ID "parentID"
|
|
|
|
/* Represent the CDS DIDL Message Item Restricted value. */
|
|
#define DIDL_ITEM_RESTRICTED "restricted"
|
|
|
|
/* Represent the CDS DIDL Message Item UPnP Class value. */
|
|
#define DIDL_ITEM_CLASS "upnp:class"
|
|
|
|
/* Represent the CDS DIDL Message Item Title value. */
|
|
#define DIDL_ITEM_TITLE "dc:title"
|
|
|
|
/* Represent the CDS DIDL Message Item Resource value. */
|
|
#define DIDL_RES "res"
|
|
|
|
/* Represent the CDS DIDL Message Item Protocol Info value. */
|
|
#define DIDL_RES_INFO "protocolInfo"
|
|
|
|
/* Represent the CDS DIDL Message Item Resource Size value. */
|
|
#define DIDL_RES_SIZE "size"
|
|
|
|
/* Represent the CDS DIDL Message Container value. */
|
|
#define DIDL_CONTAINER "container"
|
|
|
|
/* Represent the CDS DIDL Message Container ID value. */
|
|
#define DIDL_CONTAINER_ID "id"
|
|
|
|
/* Represent the CDS DIDL Message Container Parent ID value. */
|
|
#define DIDL_CONTAINER_PARENT_ID "parentID"
|
|
|
|
/* Represent the CDS DIDL Message Container number of children value. */
|
|
#define DIDL_CONTAINER_CHILDS "childCount"
|
|
|
|
/* Represent the CDS DIDL Message Container Restricted value. */
|
|
#define DIDL_CONTAINER_RESTRICTED "restricted"
|
|
|
|
/* Represent the CDS DIDL Message Container Searchable value. */
|
|
#define DIDL_CONTAINER_SEARCH "searchable"
|
|
|
|
/* Represent the CDS DIDL Message Container UPnP Class value. */
|
|
#define DIDL_CONTAINER_CLASS "upnp:class"
|
|
|
|
/* Represent the CDS DIDL Message Container Title value. */
|
|
#define DIDL_CONTAINER_TITLE "dc:title"
|
|
|
|
/* Represent the "upnp:class" reserved keyword for Search action */
|
|
#define SEARCH_CLASS_MATCH_KEYWORD "(upnp:class = \""
|
|
|
|
/* Represent the "upnp:class derived from" reserved keyword */
|
|
#define SEARCH_CLASS_DERIVED_KEYWORD "(upnp:class derivedfrom \""
|
|
|
|
/* Represent the "res@protocolInfo contains" reserved keyword */
|
|
#define SEARCH_PROTOCOL_CONTAINS_KEYWORD "(res@protocolInfo contains \""
|
|
|
|
/* Represent the Search default keyword */
|
|
#define SEARCH_OBJECT_KEYWORD "object"
|
|
|
|
/* Represent the Search 'AND' connector keyword */
|
|
#define SEARCH_AND ") and ("
|
|
|
|
static bool filter_has_val(const char *filter, const char *val) {
|
|
char *x = NULL, *token = NULL;
|
|
char *m_buffer = NULL, *buffer;
|
|
int len = strlen (val);
|
|
bool ret = false;
|
|
|
|
if (!strcmp (filter, "*")) {
|
|
return true;
|
|
}
|
|
|
|
x = strdup (filter);
|
|
if (x) {
|
|
m_buffer = (char*) malloc (strlen (x));
|
|
if (m_buffer) {
|
|
buffer = m_buffer;
|
|
token = strtok_r (x, ",", &buffer);
|
|
while (token) {
|
|
if (*val == '@') {
|
|
token = strchr (token, '@');
|
|
}
|
|
if (token && !strncmp (token, val, len)) {
|
|
ret = true;
|
|
break;
|
|
}
|
|
token = strtok_r (NULL, ",", &buffer);
|
|
}
|
|
free (m_buffer);
|
|
}
|
|
free (x);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* UPnP ContentDirectory Service actions */
|
|
static bool cds_get_search_capabilities(struct action_event_t *event) {
|
|
upnp_add_response(event, SERVICE_CDS_ARG_SEARCH_CAPS, "");
|
|
return event->status;
|
|
}
|
|
|
|
static bool cds_get_sort_capabilities(struct action_event_t *event) {
|
|
upnp_add_response(event, SERVICE_CDS_ARG_SORT_CAPS, "");
|
|
return event->status;
|
|
}
|
|
|
|
static bool cds_get_system_update_id(struct action_event_t *event) {
|
|
upnp_add_response(event, SERVICE_CDS_ARG_UPDATE_ID,
|
|
SERVICE_CDS_ROOT_OBJECT_ID);
|
|
return event->status;
|
|
}
|
|
|
|
static void didl_add_header (struct buffer_t *out) {
|
|
buffer_appendf(out, "<%s %s>", DIDL_LITE, DIDL_NAMESPACE);
|
|
}
|
|
|
|
static void didl_add_footer (struct buffer_t *out) {
|
|
buffer_appendf(out, "</%s>", DIDL_LITE);
|
|
}
|
|
|
|
static void didl_add_tag(struct buffer_t *out, char *tag, char *value) {
|
|
if (value) {
|
|
buffer_appendf(out, "<%s>%s</%s>", tag, value, tag);
|
|
}
|
|
}
|
|
|
|
static void didl_add_param(struct buffer_t *out, char *param, char *value) {
|
|
if (value) {
|
|
buffer_appendf (out, " %s=\"%s\"", param, value);
|
|
}
|
|
}
|
|
|
|
static void didl_add_value(struct buffer_t *out, char *param, off_t value) {
|
|
buffer_appendf (out, " %s=\"%lld\"", param, value);
|
|
}
|
|
|
|
static void didl_add_item (struct buffer_t *out, int item_id,
|
|
int parent_id, char *restricted, char *classPointer, char *title,
|
|
char *protocol_info, off_t size, char *url, char *filter) {
|
|
buffer_appendf (out, "<%s", DIDL_ITEM);
|
|
didl_add_value (out, DIDL_ITEM_ID, item_id);
|
|
didl_add_value (out, DIDL_ITEM_PARENT_ID, parent_id);
|
|
didl_add_param (out, DIDL_ITEM_RESTRICTED, restricted);
|
|
buffer_append (out, ">");
|
|
didl_add_tag (out, DIDL_ITEM_CLASS, classPointer);
|
|
didl_add_tag (out, DIDL_ITEM_TITLE, title);
|
|
|
|
if (filter_has_val (filter, DIDL_RES))
|
|
{
|
|
buffer_appendf (out, "<%s", DIDL_RES);
|
|
// protocolInfo is required :
|
|
didl_add_param (out, DIDL_RES_INFO, protocol_info);
|
|
if (filter_has_val (filter, "@"DIDL_RES_SIZE))
|
|
didl_add_value (out, DIDL_RES_SIZE, size);
|
|
buffer_append (out, ">");
|
|
if (url)
|
|
{
|
|
extern struct ushare_t *ut;
|
|
buffer_appendf (out, "http://%s:%d%s/%s",
|
|
UpnpGetServerIpAddress (), ut->port, VIRTUAL_DIR, url);
|
|
}
|
|
buffer_appendf (out, "</%s>", DIDL_RES);
|
|
}
|
|
buffer_appendf (out, "</%s>", DIDL_ITEM);
|
|
}
|
|
|
|
static void
|
|
didl_add_container (struct buffer_t *out, int id, int parent_id,
|
|
int child_count, char *restricted, char *searchable,
|
|
char *title, char *classPointer)
|
|
{
|
|
buffer_appendf (out, "<%s", DIDL_CONTAINER);
|
|
|
|
didl_add_value (out, DIDL_CONTAINER_ID, id);
|
|
didl_add_value (out, DIDL_CONTAINER_PARENT_ID, parent_id);
|
|
if (child_count >= 0)
|
|
didl_add_value (out, DIDL_CONTAINER_CHILDS, child_count);
|
|
didl_add_param (out, DIDL_CONTAINER_RESTRICTED, restricted);
|
|
didl_add_param (out, DIDL_CONTAINER_SEARCH, searchable);
|
|
buffer_append (out, ">");
|
|
|
|
didl_add_tag (out, DIDL_CONTAINER_CLASS, classPointer);
|
|
didl_add_tag (out, DIDL_CONTAINER_TITLE, title);
|
|
|
|
buffer_appendf (out, "</%s>", DIDL_CONTAINER);
|
|
}
|
|
|
|
static int
|
|
cds_browse_metadata (struct action_event_t *event, struct buffer_t *out,
|
|
int index, int count, struct upnp_entry_t *entry,
|
|
char *filter)
|
|
{
|
|
int result_count = 0, c = 0;
|
|
|
|
if (!entry)
|
|
return -1;
|
|
|
|
if (entry->child_count == -1) /* item : file */
|
|
{
|
|
#ifdef HAVE_DLNA
|
|
extern struct ushare_t *ut;
|
|
#endif /* HAVE_DLNA */
|
|
|
|
char *protocol =
|
|
#ifdef HAVE_DLNA
|
|
entry->dlna_profile ?
|
|
dlna_write_protocol_info (DLNA_PROTOCOL_INFO_TYPE_HTTP,
|
|
DLNA_ORG_PLAY_SPEED_NORMAL,
|
|
DLNA_ORG_CONVERSION_NONE,
|
|
DLNA_ORG_OPERATION_RANGE,
|
|
ut->dlna_flags, entry->dlna_profile) :
|
|
#endif /* HAVE_DLNA */
|
|
mime_get_protocol (entry->mime_type);
|
|
|
|
didl_add_header (out);
|
|
#ifdef HAVE_DLNA
|
|
entry->dlna_profile ?
|
|
didl_add_item (out, entry->id, entry->parent
|
|
? entry->parent->id : -1, "false",
|
|
dlna_profile_upnp_object_item (entry->dlna_profile),
|
|
entry->title,
|
|
protocol, entry->size,
|
|
entry->url, filter) :
|
|
#endif /* HAVE_DLNA */
|
|
didl_add_item (out, entry->id, entry->parent
|
|
? entry->parent->id : -1, "false",
|
|
entry->mime_type->mime_class, entry->title,
|
|
protocol, entry->size,
|
|
entry->url, filter);
|
|
|
|
didl_add_footer (out);
|
|
free (protocol);
|
|
|
|
for (c = index; c < MIN (index + count, entry->child_count); c++)
|
|
result_count++;
|
|
}
|
|
else /* container : directory */
|
|
{
|
|
didl_add_header (out);
|
|
didl_add_container (out, entry->id, entry->parent
|
|
? entry->parent->id : -1, entry->child_count,
|
|
"true", "true", entry->title,
|
|
entry->mime_type->mime_class);
|
|
didl_add_footer (out);
|
|
|
|
result_count = 1;
|
|
}
|
|
|
|
upnp_add_response (event, SERVICE_CDS_DIDL_RESULT, out->buf);
|
|
upnp_add_response (event, SERVICE_CDS_DIDL_NUM_RETURNED, "1");
|
|
upnp_add_response (event, SERVICE_CDS_DIDL_TOTAL_MATCH, "1");
|
|
|
|
return result_count;
|
|
}
|
|
|
|
static int
|
|
cds_browse_directchildren (struct action_event_t *event,
|
|
struct buffer_t *out, int index,
|
|
int count, struct upnp_entry_t *entry, char *filter)
|
|
{
|
|
struct upnp_entry_t **childs;
|
|
int s, result_count = 0;
|
|
char tmp[32];
|
|
|
|
if (entry->child_count == -1) /* item : file */
|
|
return -1;
|
|
|
|
didl_add_header (out);
|
|
|
|
/* go to the child pointed out by index */
|
|
childs = entry->childs;
|
|
for (s = 0; s < index; s++)
|
|
if (*childs)
|
|
childs++;
|
|
|
|
/* UPnP CDS compliance : If starting index = 0 and requested count = 0
|
|
then all children must be returned */
|
|
if (index == 0 && count == 0)
|
|
count = entry->child_count;
|
|
|
|
for (; *childs; childs++)
|
|
{
|
|
if (count == 0 || result_count < count)
|
|
/* only fetch the requested count number or all entries if count = 0 */
|
|
{
|
|
if ((*childs)->child_count >= 0) /* container */
|
|
didl_add_container (out, (*childs)->id, (*childs)->parent ?
|
|
(*childs)->parent->id : -1,
|
|
(*childs)->child_count, "true", NULL,
|
|
(*childs)->title,
|
|
(*childs)->mime_type->mime_class);
|
|
else /* item */
|
|
{
|
|
#ifdef HAVE_DLNA
|
|
extern struct ushare_t *ut;
|
|
#endif /* HAVE_DLNA */
|
|
|
|
char *protocol =
|
|
#ifdef HAVE_DLNA
|
|
(*childs)->dlna_profile ?
|
|
dlna_write_protocol_info (DLNA_PROTOCOL_INFO_TYPE_HTTP,
|
|
DLNA_ORG_PLAY_SPEED_NORMAL,
|
|
DLNA_ORG_CONVERSION_NONE,
|
|
DLNA_ORG_OPERATION_RANGE,
|
|
ut->dlna_flags, (*childs)->dlna_profile) :
|
|
#endif /* HAVE_DLNA */
|
|
mime_get_protocol ((*childs)->mime_type);
|
|
|
|
#ifdef HAVE_DLNA
|
|
(*childs)->dlna_profile ?
|
|
didl_add_item (out, (*childs)->id,
|
|
(*childs)->parent ? (*childs)->parent->id : -1,
|
|
"true", dlna_profile_upnp_object_item ((*childs)->dlna_profile),
|
|
(*childs)->title, protocol,
|
|
(*childs)->size, (*childs)->url, filter) :
|
|
#endif /* HAVE_DLNA */
|
|
didl_add_item (out, (*childs)->id,
|
|
(*childs)->parent ? (*childs)->parent->id : -1,
|
|
"true", (*childs)->mime_type->mime_class,
|
|
(*childs)->title, protocol,
|
|
(*childs)->size, (*childs)->url, filter);
|
|
|
|
free (protocol);
|
|
}
|
|
result_count++;
|
|
}
|
|
}
|
|
|
|
didl_add_footer (out);
|
|
|
|
upnp_add_response (event, SERVICE_CDS_DIDL_RESULT, out->buf);
|
|
sprintf (tmp, "%d", result_count);
|
|
upnp_add_response (event, SERVICE_CDS_DIDL_NUM_RETURNED, tmp);
|
|
sprintf (tmp, "%d", entry->child_count);
|
|
upnp_add_response (event, SERVICE_CDS_DIDL_TOTAL_MATCH, tmp);
|
|
|
|
return result_count;
|
|
}
|
|
|
|
static bool
|
|
cds_browse (struct action_event_t *event)
|
|
{
|
|
extern struct ushare_t *ut;
|
|
struct upnp_entry_t *entry = NULL;
|
|
int result_count = 0, index, count, id, sort_criteria;
|
|
char *flag = NULL;
|
|
char *filter = NULL;
|
|
struct buffer_t *out = NULL;
|
|
bool metadata;
|
|
|
|
if (!event)
|
|
return false;
|
|
|
|
/* Check for status */
|
|
if (!event->status)
|
|
return false;
|
|
|
|
/* check if metadatas have been well inited */
|
|
if (!ut->init)
|
|
return false;
|
|
|
|
/* Retrieve Browse arguments */
|
|
index = upnp_get_ui4 (event->request, SERVICE_CDS_ARG_START_INDEX);
|
|
count = upnp_get_ui4 (event->request, SERVICE_CDS_ARG_REQUEST_COUNT);
|
|
id = upnp_get_ui4 (event->request, SERVICE_CDS_ARG_OBJECT_ID);
|
|
flag = upnp_get_string (event->request, SERVICE_CDS_ARG_BROWSE_FLAG);
|
|
filter = upnp_get_string (event->request, SERVICE_CDS_ARG_FILTER);
|
|
sort_criteria = upnp_get_ui4 (event->request, SERVICE_CDS_ARG_SORT_CRIT);
|
|
|
|
if (!flag || !filter)
|
|
return false;
|
|
|
|
/* Check arguments validity */
|
|
if (!strcmp (flag, SERVICE_CDS_BROWSE_METADATA))
|
|
{
|
|
if (index)
|
|
{
|
|
free (flag);
|
|
return false;
|
|
}
|
|
metadata = true;
|
|
}
|
|
else if (!strcmp (flag, SERVICE_CDS_BROWSE_CHILDREN))
|
|
metadata = false;
|
|
else
|
|
{
|
|
free (flag);
|
|
return false;
|
|
}
|
|
free (flag);
|
|
|
|
entry = upnp_get_entry (ut, id);
|
|
if (!entry && (id < ut->starting_id))
|
|
entry = upnp_get_entry (ut, ut->starting_id);
|
|
|
|
if (!entry)
|
|
{
|
|
free (filter);
|
|
return false;
|
|
}
|
|
|
|
out = buffer_new ();
|
|
if (!out)
|
|
{
|
|
free (filter);
|
|
return false;
|
|
}
|
|
|
|
if (metadata)
|
|
result_count =
|
|
cds_browse_metadata (event, out, index, count, entry, filter);
|
|
else
|
|
result_count =
|
|
cds_browse_directchildren (event, out, index, count, entry, filter);
|
|
free (filter);
|
|
|
|
if (result_count < 0)
|
|
{
|
|
buffer_free (out);
|
|
return false;
|
|
}
|
|
|
|
buffer_free (out);
|
|
upnp_add_response (event, SERVICE_CDS_DIDL_UPDATE_ID,
|
|
SERVICE_CDS_ROOT_OBJECT_ID);
|
|
|
|
return event->status;
|
|
}
|
|
|
|
static bool
|
|
matches_search (char *search_criteria, struct upnp_entry_t *entry)
|
|
{
|
|
char keyword[256] = SEARCH_OBJECT_KEYWORD;
|
|
bool derived_from = false, protocol_contains = false, result = false;
|
|
char *quote_closed = NULL, *and_clause = NULL;
|
|
#ifdef HAVE_DLNA
|
|
extern struct ushare_t *ut;
|
|
#endif /* HAVE_DLNA */
|
|
char *protocol =
|
|
#ifdef HAVE_DLNA
|
|
entry->dlna_profile ?
|
|
dlna_write_protocol_info (DLNA_PROTOCOL_INFO_TYPE_HTTP,
|
|
DLNA_ORG_PLAY_SPEED_NORMAL,
|
|
DLNA_ORG_CONVERSION_NONE,
|
|
DLNA_ORG_OPERATION_RANGE,
|
|
ut->dlna_flags, entry->dlna_profile) :
|
|
#endif /* HAVE_DLNA */
|
|
mime_get_protocol (entry->mime_type);
|
|
|
|
if (!strncmp (search_criteria, SEARCH_CLASS_MATCH_KEYWORD,
|
|
strlen (SEARCH_CLASS_MATCH_KEYWORD)))
|
|
{
|
|
strncpy (keyword, search_criteria
|
|
+ strlen (SEARCH_CLASS_MATCH_KEYWORD), sizeof (keyword));
|
|
quote_closed = strchr (keyword, '"');
|
|
|
|
if (quote_closed)
|
|
*quote_closed = '\0';
|
|
}
|
|
else if (!strncmp (search_criteria, SEARCH_CLASS_DERIVED_KEYWORD,
|
|
strlen (SEARCH_CLASS_DERIVED_KEYWORD)))
|
|
{
|
|
derived_from = true;
|
|
strncpy (keyword, search_criteria
|
|
+ strlen (SEARCH_CLASS_DERIVED_KEYWORD), sizeof (keyword));
|
|
quote_closed = strchr (keyword, '"');
|
|
|
|
if (quote_closed)
|
|
*quote_closed = '\0';
|
|
}
|
|
else if (!strncmp (search_criteria, SEARCH_PROTOCOL_CONTAINS_KEYWORD,
|
|
strlen (SEARCH_PROTOCOL_CONTAINS_KEYWORD)))
|
|
{
|
|
protocol_contains = true;
|
|
strncpy (keyword, search_criteria
|
|
+ strlen (SEARCH_PROTOCOL_CONTAINS_KEYWORD), sizeof (keyword));
|
|
quote_closed = strchr (keyword, '"');
|
|
|
|
if (quote_closed)
|
|
*quote_closed = '\0';
|
|
}
|
|
|
|
if (derived_from && entry->mime_type
|
|
&& !strncmp (entry->mime_type->mime_class, keyword, strlen (keyword)))
|
|
result = true;
|
|
else if (protocol_contains && strstr (protocol, keyword))
|
|
result = true;
|
|
else if (entry->mime_type &&
|
|
!strcmp (entry->mime_type->mime_class, keyword))
|
|
result = true;
|
|
free (protocol);
|
|
|
|
and_clause = strstr (search_criteria, SEARCH_AND);
|
|
if (and_clause)
|
|
return (result &&
|
|
matches_search (and_clause + strlen (SEARCH_AND) -1, entry));
|
|
|
|
return true;
|
|
}
|
|
|
|
static int
|
|
cds_search_directchildren_recursive (struct buffer_t *out, int count,
|
|
struct upnp_entry_t *entry, char *filter,
|
|
char *search_criteria)
|
|
{
|
|
struct upnp_entry_t **childs;
|
|
int result_count = 0;
|
|
|
|
if (entry->child_count == -1) /* item : file */
|
|
return -1;
|
|
|
|
/* go to the first child */
|
|
childs = entry->childs;
|
|
|
|
for (; *childs; childs++)
|
|
{
|
|
if (count == 0 || result_count < count)
|
|
/* only fetch the requested count number or all entries if count = 0 */
|
|
{
|
|
if ((*childs)->child_count >= 0) /* container */
|
|
{
|
|
int new_count;
|
|
new_count = cds_search_directchildren_recursive
|
|
(out, (count == 0) ? 0 : (count - result_count),
|
|
(*childs), filter, search_criteria);
|
|
result_count += new_count;
|
|
}
|
|
else /* item */
|
|
{
|
|
if (matches_search (search_criteria, *childs))
|
|
{
|
|
#ifdef HAVE_DLNA
|
|
extern struct ushare_t *ut;
|
|
#endif /* HAVE_DLNA */
|
|
char *protocol =
|
|
#ifdef HAVE_DLNA
|
|
(*childs)->dlna_profile ?
|
|
dlna_write_protocol_info(DLNA_PROTOCOL_INFO_TYPE_HTTP,
|
|
DLNA_ORG_PLAY_SPEED_NORMAL,
|
|
DLNA_ORG_CONVERSION_NONE,
|
|
DLNA_ORG_OPERATION_RANGE,
|
|
ut->dlna_flags, (*childs)->dlna_profile):
|
|
#endif /* HAVE_DLNA */
|
|
mime_get_protocol ((*childs)->mime_type);
|
|
|
|
#ifdef HAVE_DLNA
|
|
(*childs)->dlna_profile ?
|
|
didl_add_item (out, (*childs)->id,
|
|
(*childs)->parent ? (*childs)->parent->id : -1,
|
|
"true", dlna_profile_upnp_object_item ((*childs)->dlna_profile),
|
|
(*childs)->title, protocol,
|
|
(*childs)->size, (*childs)->url, filter) :
|
|
#endif /* HAVE_DLNA */
|
|
didl_add_item (out, (*childs)->id,
|
|
(*childs)->parent ? (*childs)->parent->id : -1,
|
|
"true", (*childs)->mime_type->mime_class,
|
|
(*childs)->title, protocol,
|
|
(*childs)->size, (*childs)->url, filter);
|
|
free (protocol);
|
|
result_count++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result_count;
|
|
}
|
|
|
|
static int
|
|
cds_search_directchildren (struct action_event_t *event,
|
|
struct buffer_t *out, int index,
|
|
int count, struct upnp_entry_t *entry,
|
|
char *filter, char *search_criteria)
|
|
{
|
|
struct upnp_entry_t **childs;
|
|
int s, result_count = 0;
|
|
char tmp[32];
|
|
|
|
index = 0;
|
|
|
|
if (entry->child_count == -1) /* item : file */
|
|
return -1;
|
|
|
|
didl_add_header (out);
|
|
|
|
/* go to the child pointed out by index */
|
|
childs = entry->childs;
|
|
for (s = 0; s < index; s++)
|
|
if (*childs)
|
|
childs++;
|
|
|
|
/* UPnP CDS compliance : If starting index = 0 and requested count = 0
|
|
then all children must be returned */
|
|
if (index == 0 && count == 0)
|
|
count = entry->child_count;
|
|
|
|
for (; *childs; childs++)
|
|
{
|
|
if (count == 0 || result_count < count)
|
|
/* only fetch the requested count number or all entries if count = 0 */
|
|
{
|
|
if ((*childs)->child_count >= 0) /* container */
|
|
{
|
|
int new_count;
|
|
new_count = cds_search_directchildren_recursive
|
|
(out, (count == 0) ? 0 : (count - result_count),
|
|
(*childs), filter, search_criteria);
|
|
result_count += new_count;
|
|
}
|
|
else /* item */
|
|
{
|
|
if (matches_search (search_criteria, *childs))
|
|
{
|
|
#ifdef HAVE_DLNA
|
|
extern struct ushare_t *ut;
|
|
#endif /* HAVE_DLNA */
|
|
char *protocol =
|
|
#ifdef HAVE_DLNA
|
|
(*childs)->dlna_profile ?
|
|
dlna_write_protocol_info(DLNA_PROTOCOL_INFO_TYPE_HTTP,
|
|
DLNA_ORG_PLAY_SPEED_NORMAL,
|
|
DLNA_ORG_CONVERSION_NONE,
|
|
DLNA_ORG_OPERATION_RANGE,
|
|
ut->dlna_flags, (*childs)->dlna_profile):
|
|
#endif /* HAVE_DLNA */
|
|
mime_get_protocol ((*childs)->mime_type);
|
|
|
|
#ifdef HAVE_DLNA
|
|
(*childs)->dlna_profile ?
|
|
didl_add_item (out, (*childs)->id,
|
|
(*childs)->parent ? (*childs)->parent->id : -1,
|
|
"true", dlna_profile_upnp_object_item ((*childs)->dlna_profile),
|
|
(*childs)->title, protocol,
|
|
(*childs)->size, (*childs)->url, filter) :
|
|
#endif /* HAVE_DLNA */
|
|
didl_add_item (out, (*childs)->id,
|
|
(*childs)->parent ? (*childs)->parent->id : -1,
|
|
"true", (*childs)->mime_type->mime_class,
|
|
(*childs)->title, protocol,
|
|
(*childs)->size, (*childs)->url, filter);
|
|
free (protocol);
|
|
result_count++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
didl_add_footer (out);
|
|
|
|
upnp_add_response (event, SERVICE_CDS_DIDL_RESULT, out->buf);
|
|
|
|
sprintf (tmp, "%d", result_count);
|
|
upnp_add_response (event, SERVICE_CDS_DIDL_NUM_RETURNED, tmp);
|
|
sprintf (tmp, "%d", result_count);
|
|
upnp_add_response (event, SERVICE_CDS_DIDL_TOTAL_MATCH, tmp);
|
|
|
|
return result_count;
|
|
}
|
|
|
|
static bool
|
|
cds_search (struct action_event_t *event)
|
|
{
|
|
extern struct ushare_t *ut;
|
|
struct upnp_entry_t *entry = NULL;
|
|
int result_count = 0, index, count, id, sort_criteria;
|
|
char *search_criteria = NULL;
|
|
char *filter = NULL;
|
|
struct buffer_t *out = NULL;
|
|
|
|
if (!event)
|
|
return false;
|
|
|
|
/* Check for status */
|
|
if (!event->status)
|
|
return false;
|
|
|
|
/* check if metadatas have been well inited */
|
|
if (!ut->init)
|
|
return false;
|
|
|
|
/* Retrieve Browse arguments */
|
|
index = upnp_get_ui4 (event->request, SERVICE_CDS_ARG_START_INDEX);
|
|
count = upnp_get_ui4 (event->request, SERVICE_CDS_ARG_REQUEST_COUNT);
|
|
id = upnp_get_ui4 (event->request, SERVICE_CDS_ARG_OBJECT_ID);
|
|
|
|
search_criteria = upnp_get_string (event->request,
|
|
SERVICE_CDS_ARG_SEARCH_CRIT);
|
|
filter = upnp_get_string (event->request, SERVICE_CDS_ARG_FILTER);
|
|
sort_criteria = upnp_get_ui4 (event->request, SERVICE_CDS_ARG_SORT_CRIT);
|
|
|
|
if (!search_criteria || !filter)
|
|
return false;
|
|
|
|
entry = upnp_get_entry (ut, id);
|
|
|
|
if (!entry && (id < ut->starting_id))
|
|
entry = upnp_get_entry (ut, ut->starting_id);
|
|
|
|
if (!entry)
|
|
return false;
|
|
|
|
out = buffer_new ();
|
|
if (!out)
|
|
return false;
|
|
|
|
result_count =
|
|
cds_search_directchildren (event, out, index, count, entry,
|
|
filter, search_criteria);
|
|
|
|
if (result_count < 0)
|
|
{
|
|
buffer_free (out);
|
|
return false;
|
|
}
|
|
|
|
buffer_free (out);
|
|
upnp_add_response (event, SERVICE_CDS_DIDL_UPDATE_ID,
|
|
SERVICE_CDS_ROOT_OBJECT_ID);
|
|
|
|
free (search_criteria);
|
|
free (filter);
|
|
|
|
return event->status;
|
|
}
|
|
|
|
/* List of UPnP ContentDirectory Service actions */
|
|
struct service_action_t cds_service_actions[] = {
|
|
{ SERVICE_CDS_ACTION_SEARCH_CAPS, cds_get_search_capabilities },
|
|
{ SERVICE_CDS_ACTION_SORT_CAPS, cds_get_sort_capabilities },
|
|
{ SERVICE_CDS_ACTION_UPDATE_ID, cds_get_system_update_id },
|
|
{ SERVICE_CDS_ACTION_BROWSE, cds_browse },
|
|
{ SERVICE_CDS_ACTION_SEARCH, cds_search },
|
|
{ NULL, NULL }
|
|
};
|