"Fossies" - the Fresh Open Source Software Archive  

Source code changes of the file "upnpsoap.c" between
minidlna-1.2.1.tar.gz and minidlna-1.3.0.tar.gz

About: ReadyMedia (formerly known as MiniDLNA) is a simple media server software, with the aim of being fully compliant with DLNA/UPnP-AV clients.

upnpsoap.c  (minidlna-1.2.1):upnpsoap.c  (minidlna-1.3.0)
skipping to change at line 64 skipping to change at line 64
#include <sys/socket.h> #include <sys/socket.h>
#include <unistd.h> #include <unistd.h>
#include <dirent.h> #include <dirent.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/types.h> #include <sys/types.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include <netinet/in.h> #include <netinet/in.h>
#include <netdb.h> #include <netdb.h>
#include <ctype.h> #include <ctype.h>
#include "event.h"
#include "upnpglobalvars.h" #include "upnpglobalvars.h"
#include "utils.h" #include "utils.h"
#include "upnphttp.h" #include "upnphttp.h"
#include "upnpsoap.h" #include "upnpsoap.h"
#include "containers.h" #include "containers.h"
#include "upnpreplyparse.h" #include "upnpreplyparse.h"
#include "getifaddr.h" #include "getifaddr.h"
#include "scanner.h" #include "scanner.h"
#include "sql.h" #include "sql.h"
#include "log.h" #include "log.h"
#ifdef __sparc__ /* Sorting takes too long on slow processors with very large co ntainers */ #ifdef __sparc__ /* Sorting takes too long on slow processors with very large co ntainers */
# define __SORT_LIMIT if( totalMatches < 10000 ) # define __SORT_LIMIT if( totalMatches < 10000 )
#else #else
# define __SORT_LIMIT # define __SORT_LIMIT
#endif #endif
#define NON_ZERO(x) (x && atoi(x))
#define IS_ZERO(x) (!x || !atoi(x))
/* Standard Errors: /* Standard Errors:
* *
* errorCode errorDescription Description * errorCode errorDescription Description
* -------- ---------------- ----------- * -------- ---------------- -----------
* 401 Invalid Action No action by that name at this service. * 401 Invalid Action No action by that name at this service.
* 402 Invalid Args Could be any of the following: not enough in args, * 402 Invalid Args Could be any of the following: not enough in args,
* too many in args, no in a rg by that name, * too many in args, no in a rg by that name,
* one or more in args are o f the wrong data type. * one or more in args are o f the wrong data type.
* 403 Out of Sync Out of synchronization. * 403 Out of Sync Out of synchronization.
* 501 Action Failed May be returned in current state of servi ce * 501 Action Failed May be returned in current state of servi ce
* prevents invoking that ac tion. * prevents invoking that ac tion.
* 600-699 TBD Common action errors. Defined by UPnP For um * 600-699 TBD Common action errors. Defined by UPnP For um
* Technical Committee. * Technical Committee.
* 700-799 TBD Action-specific errors for standard actio ns. * 700-799 TBD Action-specific errors for standard actio ns.
* Defined by UPnP Forum wor king committee. * Defined by UPnP Forum wor king committee.
* 800-899 TBD Action-specific errors for non-standard a ctions. * 800-899 TBD Action-specific errors for non-standard a ctions.
* Defined by UPnP vendor. * Defined by UPnP vendor.
*/ */
#define SoapError(x,y,z) _SoapError(x,y,z,__func__)
static void static void
SoapError(struct upnphttp * h, int errCode, const char * errDesc) _SoapError(struct upnphttp * h, int errCode, const char * errDesc, const char *f unc)
{ {
static const char resp[] = static const char resp[] =
"<s:Envelope " "<s:Envelope "
"xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" " "xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\" "
"s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" "s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">"
"<s:Body>" "<s:Body>"
"<s:Fault>" "<s:Fault>"
"<faultcode>s:Client</faultcode>" "<faultcode>s:Client</faultcode>"
"<faultstring>UPnPError</faultstring>" "<faultstring>UPnPError</faultstring>"
"<detail>" "<detail>"
skipping to change at line 123 skipping to change at line 127
"<errorDescription>%s</errorDescription>" "<errorDescription>%s</errorDescription>"
"</UPnPError>" "</UPnPError>"
"</detail>" "</detail>"
"</s:Fault>" "</s:Fault>"
"</s:Body>" "</s:Body>"
"</s:Envelope>"; "</s:Envelope>";
char body[2048]; char body[2048];
int bodylen; int bodylen;
DPRINTF(E_WARN, L_HTTP, "Returning UPnPError %d: %s\n", errCode, errDesc) ; DPRINTF(E_WARN, L_HTTP, "%s Returning UPnPError %d: %s\n", func, errCode, errDesc);
bodylen = snprintf(body, sizeof(body), resp, errCode, errDesc); bodylen = snprintf(body, sizeof(body), resp, errCode, errDesc);
BuildResp2_upnphttp(h, 500, "Internal Server Error", body, bodylen); BuildResp2_upnphttp(h, 500, "Internal Server Error", body, bodylen);
SendResp_upnphttp(h); SendResp_upnphttp(h);
CloseSocket_upnphttp(h); CloseSocket_upnphttp(h);
} }
static void static void
BuildSendAndCloseSoapResp(struct upnphttp * h, BuildSendAndCloseSoapResp(struct upnphttp * h,
const char * body, int bodylen) const char * body, int bodylen)
{ {
skipping to change at line 264 skipping to change at line 268
GetSortCapabilities(struct upnphttp * h, const char * action) GetSortCapabilities(struct upnphttp * h, const char * action)
{ {
static const char resp[] = static const char resp[] =
"<u:%sResponse " "<u:%sResponse "
"xmlns:u=\"%s\">" "xmlns:u=\"%s\">"
"<SortCaps>" "<SortCaps>"
"dc:title," "dc:title,"
"dc:date," "dc:date,"
"upnp:class," "upnp:class,"
"upnp:album," "upnp:album,"
"upnp:episodeNumber,"
"upnp:originalTrackNumber" "upnp:originalTrackNumber"
"</SortCaps>" "</SortCaps>"
"</u:%sResponse>"; "</u:%sResponse>";
char body[512]; char body[512];
int bodylen; int bodylen;
bodylen = snprintf(body, sizeof(body), resp, bodylen = snprintf(body, sizeof(body), resp,
action, "urn:schemas-upnp-org:service:ContentDirectory:1", action, "urn:schemas-upnp-org:service:ContentDirectory:1",
action); action);
skipping to change at line 391 skipping to change at line 396
#define FILTER_RES_NRAUDIOCHANNELS 0x00000200 #define FILTER_RES_NRAUDIOCHANNELS 0x00000200
#define FILTER_RES_RESOLUTION 0x00000400 #define FILTER_RES_RESOLUTION 0x00000400
#define FILTER_RES_SAMPLEFREQUENCY 0x00000800 #define FILTER_RES_SAMPLEFREQUENCY 0x00000800
#define FILTER_RES_SIZE 0x00001000 #define FILTER_RES_SIZE 0x00001000
#define FILTER_SEARCHABLE 0x00002000 #define FILTER_SEARCHABLE 0x00002000
#define FILTER_UPNP_ACTOR 0x00004000 #define FILTER_UPNP_ACTOR 0x00004000
#define FILTER_UPNP_ALBUM 0x00008000 #define FILTER_UPNP_ALBUM 0x00008000
#define FILTER_UPNP_ALBUMARTURI 0x00010000 #define FILTER_UPNP_ALBUMARTURI 0x00010000
#define FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID 0x00020000 #define FILTER_UPNP_ALBUMARTURI_DLNA_PROFILEID 0x00020000
#define FILTER_UPNP_ARTIST 0x00040000 #define FILTER_UPNP_ARTIST 0x00040000
#define FILTER_UPNP_GENRE 0x00080000 #define FILTER_UPNP_EPISODENUMBER 0x00080000
#define FILTER_UPNP_ORIGINALTRACKNUMBER 0x00100000 #define FILTER_UPNP_EPISODESEASON 0x00100000
#define FILTER_UPNP_SEARCHCLASS 0x00200000 #define FILTER_UPNP_GENRE 0x00200000
#define FILTER_UPNP_STORAGEUSED 0x00400000 #define FILTER_UPNP_ORIGINALTRACKNUMBER 0x00400000
#define FILTER_UPNP_SEARCHCLASS 0x00800000
#define FILTER_UPNP_STORAGEUSED 0x01000000
/* Not normally used, so leave out of the default filter */ /* Not normally used, so leave out of the default filter */
#define FILTER_UPNP_PLAYBACKCOUNT 0x01000000 #define FILTER_UPNP_PLAYBACKCOUNT 0x02000000
#define FILTER_UPNP_LASTPLAYBACKPOSITION 0x02000000 #define FILTER_UPNP_LASTPLAYBACKPOSITION 0x04000000
/* Vendor-specific filter flags */ /* Vendor-specific filter flags */
#define FILTER_SEC_CAPTION_INFO_EX 0x04000000 #define FILTER_SEC_CAPTION_INFO_EX 0x08000000
#define FILTER_SEC_DCM_INFO 0x08000000 #define FILTER_SEC_DCM_INFO 0x10000000
#define FILTER_PV_SUBTITLE_FILE_TYPE 0x10000000 #define FILTER_SEC 0x18000000
#define FILTER_PV_SUBTITLE_FILE_URI 0x20000000 #define FILTER_PV_SUBTITLE_FILE_TYPE 0x20000000
#define FILTER_PV_SUBTITLE 0x30000000 #define FILTER_PV_SUBTITLE_FILE_URI 0x40000000
#define FILTER_AV_MEDIA_CLASS 0x40000000 #define FILTER_PV_SUBTITLE 0x60000000
#define FILTER_AV_MEDIA_CLASS 0x80000000
/* Masks */ /* Masks */
#define STANDARD_FILTER_MASK 0x00FFFFFF #define STANDARD_FILTER_MASK 0x01FFFFFF
#define FILTER_BOOKMARK_MASK (FILTER_UPNP_PLAYBACKCOUNT | \ #define FILTER_BOOKMARK_MASK (FILTER_UPNP_PLAYBACKCOUNT | \
FILTER_UPNP_LASTPLAYBACKPOSITION | \ FILTER_UPNP_LASTPLAYBACKPOSITION | \
FILTER_SEC_DCM_INFO) FILTER_SEC_DCM_INFO)
static uint32_t static uint32_t
set_filter_flags(char *filter, struct upnphttp *h) set_filter_flags(char *filter, struct upnphttp *h)
{ {
char *item, *saveptr = NULL; char *item, *saveptr = NULL;
uint32_t flags = 0; uint32_t flags = 0;
int samsung = h->req_client && (h->req_client->type->flags & FLAG_SAMSUNG ); int samsung = h->req_client && (h->req_client->type->flags & FLAG_SAMSUNG );
skipping to change at line 577 skipping to change at line 585
flags |= FILTER_PV_SUBTITLE_FILE_TYPE; flags |= FILTER_PV_SUBTITLE_FILE_TYPE;
} }
else if( strcmp(item, "res@pv:subtitleFileUri") == 0 ) else if( strcmp(item, "res@pv:subtitleFileUri") == 0 )
{ {
flags |= FILTER_PV_SUBTITLE_FILE_URI; flags |= FILTER_PV_SUBTITLE_FILE_URI;
} }
else if( strcmp(item, "av:mediaClass") == 0 ) else if( strcmp(item, "av:mediaClass") == 0 )
{ {
flags |= FILTER_AV_MEDIA_CLASS; flags |= FILTER_AV_MEDIA_CLASS;
} }
else if( strcmp(item, "upnp:episodeNumber") == 0 )
{
flags |= FILTER_UPNP_EPISODENUMBER;
}
else if( strcmp(item, "upnp:episodeSeason") == 0 )
{
flags |= FILTER_UPNP_EPISODESEASON;
}
item = strtok_r(NULL, ",", &saveptr); item = strtok_r(NULL, ",", &saveptr);
} }
return flags; return flags;
} }
static char * static char *
parse_sort_criteria(char *sortCriteria, int *error) parse_sort_criteria(char *sortCriteria, int *error)
{ {
char *order = NULL; char *order = NULL;
skipping to change at line 637 skipping to change at line 653
} }
else if( strcasecmp(item, "dc:title") == 0 ) else if( strcasecmp(item, "dc:title") == 0 )
{ {
strcatf(&str, "d.TITLE"); strcatf(&str, "d.TITLE");
title_sorted = 1; title_sorted = 1;
} }
else if( strcasecmp(item, "dc:date") == 0 ) else if( strcasecmp(item, "dc:date") == 0 )
{ {
strcatf(&str, "d.DATE"); strcatf(&str, "d.DATE");
} }
else if( strcasecmp(item, "upnp:originalTrackNumber") == 0 ) else if( strcasecmp(item, "upnp:originalTrackNumber") == 0 ||
strcasecmp(item, "upnp:episodeNumber") == 0 )
{ {
strcatf(&str, "d.DISC, d.TRACK"); strcatf(&str, "d.DISC%s, d.TRACK", reverse ? " DESC" : "" );
} }
else if( strcasecmp(item, "upnp:album") == 0 ) else if( strcasecmp(item, "upnp:album") == 0 )
{ {
strcatf(&str, "d.ALBUM"); strcatf(&str, "d.ALBUM");
} }
else if( strcasecmp(item, "path") == 0 )
{
strcatf(&str, "d.PATH");
}
else else
{ {
DPRINTF(E_ERROR, L_HTTP, "Unhandled SortCriteria [%s]\n", item); DPRINTF(E_ERROR, L_HTTP, "Unhandled SortCriteria [%s]\n", item);
bad_direction: bad_direction:
*error = -1; *error = -1;
if( i ) if( i )
{ {
ret = strlen(order); ret = strlen(order);
order[ret-2] = '\0'; order[ret-2] = '\0';
} }
skipping to change at line 681 skipping to change at line 702
/* Add a "tiebreaker" sort order */ /* Add a "tiebreaker" sort order */
if( !title_sorted ) if( !title_sorted )
strcatf(&str, ", TITLE ASC"); strcatf(&str, ", TITLE ASC");
if( force_sort_criteria ) if( force_sort_criteria )
free(sortCriteria); free(sortCriteria);
return order; return order;
} }
static void
_alphasort_alt_title(char **title, char **alt_title, int requested, int returned
, const char *disc, const char *track)
{
char *old_title = *alt_title ?: NULL;
char buf[8];
int pad;
int ret;
snprintf(buf, sizeof(buf), "%d", requested);
pad = strlen(buf);
if (NON_ZERO(track) && !strstr(*title, track)) {
if (NON_ZERO(disc))
ret = asprintf(alt_title, "%0*d %s.%s %s",
pad, returned, disc, track, *title);
else
ret = asprintf(alt_title, "%0*d %s %s",
pad, returned, track, *title);
}
else
ret = asprintf(alt_title, "%0*d %s", pad, returned, *title);
if (ret > 0)
*title = *alt_title;
else
*alt_title = NULL;
free(old_title);
}
inline static void inline static void
add_resized_res(int srcw, int srch, int reqw, int reqh, char *dlna_pn, add_resized_res(int srcw, int srch, int reqw, int reqh, char *dlna_pn,
char *detailID, struct Response *args) char *detailID, struct Response *args)
{ {
int dstw = reqw; int dstw = reqw;
int dsth = reqh; int dsth = reqh;
if( (args->flags & FLAG_NO_RESIZE) && reqw > 160 && reqh > 160 ) if( (args->flags & FLAG_NO_RESIZE) && reqw > 160 && reqh > 160 )
return; return;
skipping to change at line 786 skipping to change at line 836
strcmp(object, "*") == 0 ? "0" : object); strcmp(object, "*") == 0 ? "0" : object);
return (ret > 0); return (ret > 0);
} }
#define COLUMNS "o.DETAIL_ID, o.CLASS," \ #define COLUMNS "o.DETAIL_ID, o.CLASS," \
" d.SIZE, d.TITLE, d.DURATION, d.BITRATE, d.SAMPLERATE, d.ARTIST ," \ " d.SIZE, d.TITLE, d.DURATION, d.BITRATE, d.SAMPLERATE, d.ARTIST ," \
" d.ALBUM, d.GENRE, d.COMMENT, d.CHANNELS, d.TRACK, d.DATE, d.RE SOLUTION," \ " d.ALBUM, d.GENRE, d.COMMENT, d.CHANNELS, d.TRACK, d.DATE, d.RE SOLUTION," \
" d.THUMBNAIL, d.CREATOR, d.DLNA_PN, d.MIME, d.ALBUM_ART, d.ROTA TION, d.DISC " " d.THUMBNAIL, d.CREATOR, d.DLNA_PN, d.MIME, d.ALBUM_ART, d.ROTA TION, d.DISC "
#define SELECT_COLUMNS "SELECT o.OBJECT_ID, o.PARENT_ID, o.REF_ID, " COLUMNS #define SELECT_COLUMNS "SELECT o.OBJECT_ID, o.PARENT_ID, o.REF_ID, " COLUMNS
#define NON_ZERO(x) (x && atoi(x))
#define IS_ZERO(x) (!x || !atoi(x))
static int static int
callback(void *args, int argc, char **argv, char **azColName) callback(void *args, int argc, char **argv, char **azColName)
{ {
struct Response *passed_args = (struct Response *)args; struct Response *passed_args = (struct Response *)args;
char *id = argv[0], *parent = argv[1], *refID = argv[2], *detailID = argv [3], *class = argv[4], *size = argv[5], *title = argv[6], char *id = argv[0], *parent = argv[1], *refID = argv[2], *detailID = argv [3], *class = argv[4], *size = argv[5], *title = argv[6],
*duration = argv[7], *bitrate = argv[8], *sampleFrequency = argv[9], *artist = argv[10], *album = argv[11], *duration = argv[7], *bitrate = argv[8], *sampleFrequency = argv[9], *artist = argv[10], *album = argv[11],
*genre = argv[12], *comment = argv[13], *nrAudioChannels = argv[14], *track = argv[15], *date = argv[16], *resolution = argv[17], *genre = argv[12], *comment = argv[13], *nrAudioChannels = argv[14], *track = argv[15], *date = argv[16], *resolution = argv[17],
*tn = argv[18], *creator = argv[19], *dlna_pn = argv[20], *mime = ar gv[21], *album_art = argv[22], *rotate = argv[23]; *tn = argv[18], *creator = argv[19], *dlna_pn = argv[20], *mime = ar gv[21], *album_art = argv[22], *rotate = argv[23], *disc = argv[24];
char dlna_buf[128]; char dlna_buf[128];
const char *ext; const char *ext;
struct string_s *str = passed_args->str; struct string_s *str = passed_args->str;
int ret = 0; int ret = 0;
/* Make sure we have at least 8KB left of allocated memory to finish the response. */ /* Make sure we have at least 8KB left of allocated memory to finish the response. */
if( str->off > (str->size - 8192) ) if( str->off > (str->size - 8192) )
{ {
#if MAX_RESPONSE_SIZE > 0 #if MAX_RESPONSE_SIZE > 0
if( (str->size+DEFAULT_RESP_SIZE) <= MAX_RESPONSE_SIZE ) if( (str->size+DEFAULT_RESP_SIZE) <= MAX_RESPONSE_SIZE )
skipping to change at line 818 skipping to change at line 865
#endif #endif
str->data = realloc(str->data, (str->size+DEFAULT_RESP_SI ZE)); str->data = realloc(str->data, (str->size+DEFAULT_RESP_SI ZE));
if( str->data ) if( str->data )
{ {
str->size += DEFAULT_RESP_SIZE; str->size += DEFAULT_RESP_SIZE;
DPRINTF(E_DEBUG, L_HTTP, "UPnP SOAP response enla rged to %lu. [%d results so far]\n", DPRINTF(E_DEBUG, L_HTTP, "UPnP SOAP response enla rged to %lu. [%d results so far]\n",
(unsigned long)str->size, passed_args->re turned); (unsigned long)str->size, passed_args->re turned);
} }
else else
{ {
DPRINTF(E_ERROR, L_HTTP, "UPnP SOAP response was DPRINTF(E_ERROR, L_HTTP, "UPnP SOAP response trun
too big, and realloc failed!\n"); cated, realloc failed\n");
return -1; passed_args->flags |= RESPONSE_TRUNCATED;
return 1;
} }
#if MAX_RESPONSE_SIZE > 0 #if MAX_RESPONSE_SIZE > 0
} }
else else
{ {
DPRINTF(E_ERROR, L_HTTP, "UPnP SOAP response cut short, t DPRINTF(E_ERROR, L_HTTP, "UPnP SOAP response would exceed
o not exceed the max response size [%lld]!\n", (long long int)MAX_RESPONSE_SIZE) the max response size [%lld], truncating\n", (long long int)MAX_RESPONSE_SIZE);
; passed_args->flags |= RESPONSE_TRUNCATED;
return -1; return 1;
} }
#endif #endif
} }
passed_args->returned++; passed_args->returned++;
passed_args->flags &= ~RESPONSE_FLAGS; passed_args->flags &= ~RESPONSE_FLAGS;
if( strncmp(class, "item", 4) == 0 ) if( strncmp(class, "item", 4) == 0 )
{ {
uint32_t dlna_flags = DLNA_FLAG_DLNA_V1_5|DLNA_FLAG_HTTP_STALLING |DLNA_FLAG_TM_B; uint32_t dlna_flags = DLNA_FLAG_DLNA_V1_5|DLNA_FLAG_HTTP_STALLING |DLNA_FLAG_TM_B;
char *alt_title = NULL; char *alt_title = NULL;
/* We may need special handling for certain MIME types */ /* We may need special handling for certain MIME types */
if( *mime == 'v' ) if( *mime == 'v' )
{ {
dlna_flags |= DLNA_FLAG_TM_S; dlna_flags |= DLNA_FLAG_TM_S;
if (GETFLAG(SUBTITLES_MASK) &&
(passed_args->client >= EStandardDLNA150 || !passed_a
rgs->client))
passed_args->flags |= FLAG_CAPTION_RES;
if( passed_args->flags & FLAG_MIME_AVI_DIVX ) if( passed_args->flags & FLAG_MIME_AVI_DIVX )
{ {
if( strcmp(mime, "video/x-msvideo") == 0 ) if( strcmp(mime, "video/x-msvideo") == 0 )
{ {
if( creator ) if( creator )
strcpy(mime+6, "divx"); strcpy(mime+6, "divx");
else else
strcpy(mime+6, "avi"); strcpy(mime+6, "avi");
} }
} }
skipping to change at line 933 skipping to change at line 986
else if( strcmp(mime+6, "x-wav") == 0 ) else if( strcmp(mime+6, "x-wav") == 0 )
{ {
if( passed_args->flags & FLAG_MIME_WAV_WAV ) if( passed_args->flags & FLAG_MIME_WAV_WAV )
{ {
strcpy(mime+6, "wav"); strcpy(mime+6, "wav");
} }
} }
} }
else else
dlna_flags |= DLNA_FLAG_TM_I; dlna_flags |= DLNA_FLAG_TM_I;
/* Force an alphabetical sort, for clients that like to do their
own sorting */
if( GETFLAG(FORCE_ALPHASORT_MASK) )
_alphasort_alt_title(&title, &alt_title, passed_args->req
uested, passed_args->returned, disc, track);
if( passed_args->flags & FLAG_SKIP_DLNA_PN ) if( passed_args->flags & FLAG_SKIP_DLNA_PN )
dlna_pn = NULL; dlna_pn = NULL;
if( dlna_pn ) if( dlna_pn )
snprintf(dlna_buf, sizeof(dlna_buf), "DLNA.ORG_PN=%s;" snprintf(dlna_buf, sizeof(dlna_buf), "DLNA.ORG_PN=%s;"
"DLNA.ORG_OP=01;" "DLNA.ORG_OP=01;"
"DLNA.ORG_CI=0;" "DLNA.ORG_CI=0;"
"DLNA.ORG_FLAGS=%08X %024X", "DLNA.ORG_FLAGS=%08X %024X",
dlna_pn, dlna_flags, 0); dlna_pn, dlna_flags, 0);
skipping to change at line 971 skipping to change at line 1027
} }
if( creator && (passed_args->filter & FILTER_DC_CREATOR) ) { if( creator && (passed_args->filter & FILTER_DC_CREATOR) ) {
ret = strcatf(str, "&lt;dc:creator&gt;%s&lt;/dc:creator&g t;", creator); ret = strcatf(str, "&lt;dc:creator&gt;%s&lt;/dc:creator&g t;", creator);
} }
if( date && (passed_args->filter & FILTER_DC_DATE) ) { if( date && (passed_args->filter & FILTER_DC_DATE) ) {
ret = strcatf(str, "&lt;dc:date&gt;%s&lt;/dc:date&gt;", d ate); ret = strcatf(str, "&lt;dc:date&gt;%s&lt;/dc:date&gt;", d ate);
} }
if( (passed_args->filter & FILTER_BOOKMARK_MASK) ) { if( (passed_args->filter & FILTER_BOOKMARK_MASK) ) {
/* Get bookmark */ /* Get bookmark */
int sec = sql_get_int_field(db, "SELECT SEC from BOOKMARK S where ID = '%s'", detailID); int sec = sql_get_int_field(db, "SELECT SEC from BOOKMARK S where ID = '%s'", detailID);
if( sec > 0 && (passed_args->filter & FILTER_UPNP_LASTPLA YBACKPOSITION) ) { if( sec > 0 ) {
/* This format is wrong according to the UPnP/AV spec. It should be in duration format, /* This format is wrong according to the UPnP/AV spec. It should be in duration format,
** so HH:MM:SS. But Kodi seems to be the only use r of this tag, and it only works with a ** so HH:MM:SS. But Kodi seems to be the only use r of this tag, and it only works with a
** raw seconds value. ** raw seconds value.
** If Kodi gets fixed, we can use duration_str(se c * 1000) here */ ** If Kodi gets fixed, we can use duration_str(se c * 1000) here */
ret = strcatf(str, "&lt;upnp:lastPlaybackPosition if( passed_args->flags & FLAG_CONVERT_MS ) {
&gt;%d&lt;/upnp:lastPlaybackPosition&gt;", sec *= 1000;
sec); }
if( passed_args->filter & FILTER_UPNP_LASTPLAYBAC
KPOSITION )
ret = strcatf(str, "&lt;upnp:lastPlayback
Position&gt;%d&lt;/upnp:lastPlaybackPosition&gt;",
sec);
if( passed_args->filter & FILTER_SEC_DCM_INFO )
ret = strcatf(str, "&lt;sec:dcmInfo&gt;CR
EATIONDATE=0,FOLDER=%s,BM=%d&lt;/sec:dcmInfo&gt;",
title, sec);
} }
if( passed_args->filter & FILTER_SEC_DCM_INFO )
ret = strcatf(str, "&lt;sec:dcmInfo&gt;CREATIONDA
TE=0,FOLDER=%s,BM=%d&lt;/sec:dcmInfo&gt;",
title, sec);
if( passed_args->filter & FILTER_UPNP_PLAYBACKCOUNT ) { if( passed_args->filter & FILTER_UPNP_PLAYBACKCOUNT ) {
ret = strcatf(str, "&lt;upnp:playbackCount&gt;%d& lt;/upnp:playbackCount&gt;", ret = strcatf(str, "&lt;upnp:playbackCount&gt;%d& lt;/upnp:playbackCount&gt;",
sql_get_int_field(db, "SELECT WATCH _COUNT from BOOKMARKS where ID = '%s'", detailID)); sql_get_int_field(db, "SELECT WATCH _COUNT from BOOKMARKS where ID = '%s'", detailID));
} }
} }
free(alt_title);
if( artist ) { if( artist ) {
if( (*mime == 'v') && (passed_args->filter & FILTER_UPNP_ ACTOR) ) { if( (*mime == 'v') && (passed_args->filter & FILTER_UPNP_ ACTOR) ) {
ret = strcatf(str, "&lt;upnp:actor&gt;%s&lt;/upnp :actor&gt;", artist); ret = strcatf(str, "&lt;upnp:actor&gt;%s&lt;/upnp :actor&gt;", artist);
} }
if( passed_args->filter & FILTER_UPNP_ARTIST ) { if( passed_args->filter & FILTER_UPNP_ARTIST ) {
ret = strcatf(str, "&lt;upnp:artist&gt;%s&lt;/upn p:artist&gt;", artist); ret = strcatf(str, "&lt;upnp:artist&gt;%s&lt;/upn p:artist&gt;", artist);
} }
} }
if( album && (passed_args->filter & FILTER_UPNP_ALBUM) ) { if( album && (passed_args->filter & FILTER_UPNP_ALBUM) ) {
ret = strcatf(str, "&lt;upnp:album&gt;%s&lt;/upnp:album&g t;", album); ret = strcatf(str, "&lt;upnp:album&gt;%s&lt;/upnp:album&g t;", album);
} }
if( genre && (passed_args->filter & FILTER_UPNP_GENRE) ) { if( genre && (passed_args->filter & FILTER_UPNP_GENRE) ) {
ret = strcatf(str, "&lt;upnp:genre&gt;%s&lt;/upnp:genre&g t;", genre); ret = strcatf(str, "&lt;upnp:genre&gt;%s&lt;/upnp:genre&g t;", genre);
} }
if( strncmp(id, MUSIC_PLIST_ID, strlen(MUSIC_PLIST_ID)) == 0 ) { if( strncmp(id, MUSIC_PLIST_ID, strlen(MUSIC_PLIST_ID)) == 0 ) {
track = strrchr(id, '$')+1; track = strrchr(id, '$')+1;
} }
if( NON_ZERO(track) && (passed_args->filter & FILTER_UPNP_ORIGINA if( NON_ZERO(track) ) {
LTRACKNUMBER) ) { if( *mime == 'a' && (passed_args->filter & FILTER_UPNP_OR
ret = strcatf(str, "&lt;upnp:originalTrackNumber&gt;%s&lt IGINALTRACKNUMBER) ) {
;/upnp:originalTrackNumber&gt;", track); ret = strcatf(str, "&lt;upnp:originalTrackNumber&
gt;%s&lt;/upnp:originalTrackNumber&gt;", track);
} else if( *mime == 'v' ) {
if( NON_ZERO(disc) && (passed_args->filter & FILT
ER_UPNP_EPISODESEASON) )
ret = strcatf(str, "&lt;upnp:episodeSeaso
n&gt;%s&lt;/upnp:episodeSeason&gt;", disc);
if( passed_args->filter & FILTER_UPNP_EPISODENUMB
ER )
ret = strcatf(str, "&lt;upnp:episodeNumbe
r&gt;%s&lt;/upnp:episodeNumber&gt;", track);
}
} }
if( passed_args->filter & FILTER_RES ) { if( passed_args->filter & FILTER_RES ) {
ext = mime_to_ext(mime); ext = mime_to_ext(mime);
add_res(size, duration, bitrate, sampleFrequency, nrAudio Channels, add_res(size, duration, bitrate, sampleFrequency, nrAudio Channels,
resolution, dlna_buf, mime, detailID, ext, passed _args); resolution, dlna_buf, mime, detailID, ext, passed _args);
if( *mime == 'i' ) { if( *mime == 'i' ) {
int srcw, srch; int srcw, srch;
if( resolution && (sscanf(resolution, "%6dx%6d", &srcw, &srch) == 2) ) if( resolution && (sscanf(resolution, "%6dx%6d", &srcw, &srch) == 2) )
{ {
if( srcw > 4096 || srch > 4096 ) if( srcw > 4096 || srch > 4096 )
skipping to change at line 1117 skipping to change at line 1185
ret = strcatf(str, "&lt;r es protocolInfo=\"http-get:*:text/srt:*\"&gt;" ret = strcatf(str, "&lt;r es protocolInfo=\"http-get:*:text/srt:*\"&gt;"
"htt p://%s:%d/Captions/%s.srt" "htt p://%s:%d/Captions/%s.srt"
"&lt;/ res&gt;", "&lt;/ res&gt;",
lan_ad dr[passed_args->iface].str, runtime_vars.port, detailID); lan_ad dr[passed_args->iface].str, runtime_vars.port, detailID);
if( passed_args->filter & FILTER_ SEC_CAPTION_INFO_EX ) if( passed_args->filter & FILTER_ SEC_CAPTION_INFO_EX )
ret = strcatf(str, "&lt;s ec:CaptionInfoEx sec:type=\"srt\"&gt;" ret = strcatf(str, "&lt;s ec:CaptionInfoEx sec:type=\"srt\"&gt;"
"htt p://%s:%d/Captions/%s.srt" "htt p://%s:%d/Captions/%s.srt"
"&lt;/ sec:CaptionInfoEx&gt;", "&lt;/ sec:CaptionInfoEx&gt;",
lan_ad dr[passed_args->iface].str, runtime_vars.port, detailID); lan_ad dr[passed_args->iface].str, runtime_vars.port, detailID);
} }
free(alt_title);
break; break;
} }
} }
} }
if( NON_ZERO(album_art) ) if( NON_ZERO(album_art) )
{ {
/* Video and audio album art is handled differently */ /* Video and audio album art is handled differently */
if( *mime == 'v' && (passed_args->filter & FILTER_RES) && !(passed_args->flags & FLAG_MS_PFS) ) { if( *mime == 'v' && (passed_args->filter & FILTER_RES) && !(passed_args->flags & FLAG_MS_PFS) ) {
ret = strcatf(str, "&lt;res protocolInfo=\"http-g et:*:image/jpeg:DLNA.ORG_PN=JPEG_TN\"&gt;" ret = strcatf(str, "&lt;res protocolInfo=\"http-g et:*:image/jpeg:DLNA.ORG_PN=JPEG_TN\"&gt;"
"http://%s:%d/AlbumArt/%s-%s.j pg" "http://%s:%d/AlbumArt/%s-%s.j pg"
skipping to change at line 1302 skipping to change at line 1369
str.data = malloc(DEFAULT_RESP_SIZE); str.data = malloc(DEFAULT_RESP_SIZE);
str.size = DEFAULT_RESP_SIZE; str.size = DEFAULT_RESP_SIZE;
str.off = sprintf(str.data, "%s", resp0); str.off = sprintf(str.data, "%s", resp0);
/* See if we need to include DLNA namespace reference */ /* See if we need to include DLNA namespace reference */
args.iface = h->iface; args.iface = h->iface;
args.filter = set_filter_flags(Filter, h); args.filter = set_filter_flags(Filter, h);
if( args.filter & FILTER_DLNA_NAMESPACE ) if( args.filter & FILTER_DLNA_NAMESPACE )
ret = strcatf(&str, DLNA_NAMESPACE); ret = strcatf(&str, DLNA_NAMESPACE);
if( args.filter & FILTER_PV_SUBTITLE ) if( args.filter & FILTER_PV_SUBTITLE )
ret = strcatf(&str, PV_NAMESPACE); ret = strcatf(&str, PV_NAMESPACE);
if( args.filter & FILTER_SEC )
ret = strcatf(&str, SEC_NAMESPACE);
strcatf(&str, "&gt;\n"); strcatf(&str, "&gt;\n");
args.returned = 0; args.returned = 0;
args.requested = RequestedCount; args.requested = RequestedCount;
args.client = h->req_client ? h->req_client->type->type : 0; args.client = h->req_client ? h->req_client->type->type : 0;
args.flags = h->req_client ? h->req_client->type->flags : 0; args.flags = h->req_client ? h->req_client->type->flags : 0;
args.str = &str; args.str = &str;
DPRINTF(E_DEBUG, L_HTTP, "Browsing ContentDirectory:\n" DPRINTF(E_DEBUG, L_HTTP, "Browsing ContentDirectory:\n"
" * ObjectID: %s\n" " * ObjectID: %s\n"
" * Count: %d\n" " * Count: %d\n"
skipping to change at line 1418 skipping to change at line 1487
} }
sql = sqlite3_mprintf("SELECT %s, %s, %s, " COLUMNS sql = sqlite3_mprintf("SELECT %s, %s, %s, " COLUMNS
"from OBJECTS o left join DETAILS d on (d.I D = o.DETAIL_ID)" "from OBJECTS o left join DETAILS d on (d.I D = o.DETAIL_ID)"
" where %s %s limit %d, %d;", " where %s %s limit %d, %d;",
objectid_sql, parentid_sql, refid_sql, objectid_sql, parentid_sql, refid_sql,
where, THISORNUL(orderBy), StartingIndex, R equestedCount); where, THISORNUL(orderBy), StartingIndex, R equestedCount);
DPRINTF(E_DEBUG, L_HTTP, "Browse SQL: %s\n", sql); DPRINTF(E_DEBUG, L_HTTP, "Browse SQL: %s\n", sql);
ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg); ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg);
} }
if( (ret != SQLITE_OK) && (zErrMsg != NULL) ) if( ret != SQLITE_OK )
{ {
DPRINTF(E_WARN, L_HTTP, "SQL error: %s\nBAD SQL: %s\n", zErrMsg, if( args.flags & RESPONSE_TRUNCATED )
sql); {
sqlite3_free(zErrMsg); sqlite3_free(zErrMsg);
SoapError(h, 709, "Unsupported or invalid sort criteria"); }
goto browse_error; else
{
DPRINTF(E_WARN, L_HTTP, "SQL error: %s\nBAD SQL: %s\n", z
ErrMsg, sql);
sqlite3_free(zErrMsg);
SoapError(h, 709, "Unsupported or invalid sort criteria")
;
goto browse_error;
}
} }
sqlite3_free(sql); sqlite3_free(sql);
/* Does the object even exist? */ /* Does the object even exist? */
if( !totalMatches ) if( !totalMatches )
{ {
if( !object_exists(ObjectID) ) if( !object_exists(ObjectID) )
{ {
SoapError(h, 701, "No such object error"); SoapError(h, 701, "No such object error");
goto browse_error; goto browse_error;
} }
skipping to change at line 1465 skipping to change at line 1541
} }
str->data[str->off] = c; str->data[str->off] = c;
str->off += 1; str->off += 1;
} }
static inline char * static inline char *
parse_search_criteria(const char *str, char *sep) parse_search_criteria(const char *str, char *sep)
{ {
struct string_s criteria; struct string_s criteria;
int len; int len;
int literal = 0, like = 0; int literal = 0, like = 0, class = 0;
const char *s; const char *s;
if (!str) if (!str)
return strdup("1 = 1"); return strdup("1 = 1");
len = strlen(str) + 32; len = strlen(str) + 32;
criteria.data = malloc(len); criteria.data = malloc(len);
criteria.size = len; criteria.size = len;
criteria.off = 0; criteria.off = 0;
skipping to change at line 1515 skipping to change at line 1591
break; break;
case '\\': case '\\':
if (strncmp(s, "\\&quot;", 7) == 0) if (strncmp(s, "\\&quot;", 7) == 0)
{ {
strcatf(&criteria, "&amp;quot;"); strcatf(&criteria, "&amp;quot;");
s += 7; s += 7;
continue; continue;
} }
break; break;
case 'o': case 'o':
if (strncmp(s, "object.", 7) == 0) if (class)
s += 7;
else if (strncmp(s, "object\"", 7) == 0 ||
strncmp(s, "object&quot;", 12) == 0)
{ {
s += 6; class = 0;
continue; if (strncmp(s, "object.", 7) == 0)
s += 7;
else if (strncmp(s, "object\"", 7) == 0 |
|
strncmp(s, "object&quot;", 12) =
= 0)
{
s += 6;
continue;
}
} }
default: default:
charcat(&criteria, *s); charcat(&criteria, *s);
break; break;
} }
} }
else else
{ {
switch (*s) { switch (*s) {
case '\\': case '\\':
skipping to change at line 1663 skipping to change at line 1743
} }
else if (strncmp(s, "false", 5) == 0) else if (strncmp(s, "false", 5) == 0)
{ {
strcatf(&criteria, "is NULL"); strcatf(&criteria, "is NULL");
s += 4; s += 4;
} }
} }
else else
charcat(&criteria, *s); charcat(&criteria, *s);
break; break;
case 'o':
if (class)
{
if (strncmp(s, "object.", 7) == 0)
{
s += 7;
charcat(&criteria, '"');
while (*s && !isspace(*s))
{
charcat(&criteria, *s);
s++;
}
charcat(&criteria, '"');
}
class = 0;
continue;
}
case 'u': case 'u':
if (strncmp(s, "upnp:class", 10) == 0) if (strncmp(s, "upnp:class", 10) == 0)
{ {
strcatf(&criteria, "o.CLASS"); strcatf(&criteria, "o.CLASS");
s += 10; s += 10;
class = 1;
continue; continue;
} }
else if (strncmp(s, "upnp:actor", 10) == 0) else if (strncmp(s, "upnp:actor", 10) == 0)
{ {
strcatf(&criteria, "d.ARTIST"); strcatf(&criteria, "d.ARTIST");
s += 10; s += 10;
continue; continue;
} }
else if (strncmp(s, "upnp:artist", 11) == 0) else if (strncmp(s, "upnp:artist", 11) == 0)
{ {
skipping to change at line 1854 skipping to change at line 1952
"%z %s" "%z %s"
" limit %d, %d", " limit %d, %d",
ContainerID, sep, where, groupBy, ContainerID, sep, where, groupBy,
(*ContainerID == '*') ? NULL : (*ContainerID == '*') ? NULL :
sqlite3_mprintf("UNION ALL " SELECT_COLUMNS sqlite3_mprintf("UNION ALL " SELECT_COLUMNS
"from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)" "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
" where OBJECT_ID = '%q' and (%s) " , ContainerID, where), " where OBJECT_ID = '%q' and (%s) " , ContainerID, where),
orderBy, StartingIndex, RequestedCount); orderBy, StartingIndex, RequestedCount);
DPRINTF(E_DEBUG, L_HTTP, "Search SQL: %s\n", sql); DPRINTF(E_DEBUG, L_HTTP, "Search SQL: %s\n", sql);
ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg); ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg);
if( (ret != SQLITE_OK) && (zErrMsg != NULL) ) if( ret != SQLITE_OK )
{ {
DPRINTF(E_WARN, L_HTTP, "SQL error: %s\nBAD SQL: %s\n", zErrMsg, if( !(args.flags & RESPONSE_TRUNCATED) )
sql); DPRINTF(E_WARN, L_HTTP, "SQL error: %s\nBAD SQL: %s\n", z
ErrMsg, sql);
sqlite3_free(zErrMsg); sqlite3_free(zErrMsg);
} }
sqlite3_free(sql); sqlite3_free(sql);
ret = strcatf(&str, "&lt;/DIDL-Lite&gt;</Result>\n" ret = strcatf(&str, "&lt;/DIDL-Lite&gt;</Result>\n"
"<NumberReturned>%u</NumberReturned>\n" "<NumberReturned>%u</NumberReturned>\n"
"<TotalMatches>%u</TotalMatches>\n" "<TotalMatches>%u</TotalMatches>\n"
"<UpdateID>%u</UpdateID>" "<UpdateID>%u</UpdateID>"
"</u:SearchResponse>", "</u:SearchResponse>",
args.returned, totalMatches, updateID); args.returned, totalMatches, updateID);
BuildSendAndCloseSoapResp(h, str.data, str.off); BuildSendAndCloseSoapResp(h, str.data, str.off);
skipping to change at line 1923 skipping to change at line 2022
} }
else else
{ {
DPRINTF(E_WARN, L_HTTP, "%s: Unknown: %s\n", action, THISORNUL(va r_name)); DPRINTF(E_WARN, L_HTTP, "%s: Unknown: %s\n", action, THISORNUL(va r_name));
SoapError(h, 404, "Invalid Var"); SoapError(h, 404, "Invalid Var");
} }
ClearNameValueList(&data); ClearNameValueList(&data);
} }
static int _set_watch_count(long long id, const char *old, const char *new)
{
int64_t rowid = sqlite3_last_insert_rowid(db);
int ret;
ret = sql_exec(db, "INSERT or IGNORE into BOOKMARKS (ID, WATCH_COUNT)"
" VALUES (%lld, %Q)", id, new ?: "1");
if (sqlite3_last_insert_rowid(db) != rowid)
return 0;
if (!new) /* Increment */
ret = sql_exec(db, "UPDATE BOOKMARKS set WATCH_COUNT ="
" ifnull(WATCH_COUNT,'0') + 1"
" where ID = %lld", id);
else if (old && old[0])
ret = sql_exec(db, "UPDATE BOOKMARKS set WATCH_COUNT = %Q"
" where WATCH_COUNT = %Q and ID = %lld",
new, old, id);
else
ret = sql_exec(db, "UPDATE BOOKMARKS set WATCH_COUNT = %Q"
" where ID = %lld",
new, id);
return ret;
}
/* For some reason, Kodi does URI encoding and appends a trailing slash */ /* For some reason, Kodi does URI encoding and appends a trailing slash */
static void _kodi_decode(char *str) static void _kodi_decode(char *str)
{ {
while (*str) while (*str)
{ {
switch (*str) { switch (*str) {
case '%': case '%':
{ {
if (isxdigit(str[1]) && isxdigit(str[2])) if (isxdigit(str[1]) && isxdigit(str[2]))
{ {
skipping to change at line 2011 skipping to change at line 2135
if (sscanf(item, "&lt;%31[^&]&gt;%31[^&]", tag, current) != 2) if (sscanf(item, "&lt;%31[^&]&gt;%31[^&]", tag, current) != 2)
continue; continue;
p = strstr(NewTagValue, tag); p = strstr(NewTagValue, tag);
if (!p || sscanf(p, "%*[^&]&gt;%31[^&]", new) != 1) if (!p || sscanf(p, "%*[^&]&gt;%31[^&]", new) != 1)
continue; continue;
DPRINTF(E_DEBUG, L_HTTP, "Setting %s to %s\n", tag, new); DPRINTF(E_DEBUG, L_HTTP, "Setting %s to %s\n", tag, new);
/* Kodi uses incorrect tag "upnp:playCount" instead of "upnp:play backCount" */ /* Kodi uses incorrect tag "upnp:playCount" instead of "upnp:play backCount" */
if (strcmp(tag, "upnp:playbackCount") == 0 || strcmp(tag, "upnp:p layCount") == 0) if (strcmp(tag, "upnp:playbackCount") == 0 || strcmp(tag, "upnp:p layCount") == 0)
{ {
//ret = sql_exec(db, "INSERT OR IGNORE into BOOKMARKS (ID ret = _set_watch_count(detailID, current, new);
, WATCH_COUNT)"
ret = sql_exec(db, "INSERT into BOOKMARKS (ID, WATCH_COUN
T)"
" VALUES (%lld, %Q)", (long long)detai
lID, new);
if (atoi(new))
ret = sql_exec(db, "UPDATE BOOKMARKS set WATCH_CO
UNT = %Q"
" where WATCH_COUNT = %Q and I
D = %lld",
new, current, (long long)detai
lID);
else
ret = sql_exec(db, "UPDATE BOOKMARKS set WATCH_CO
UNT = 0"
" where ID = %lld", (long long
)detailID);
} }
else if (strcmp(tag, "upnp:lastPlaybackPosition") == 0) else if (strcmp(tag, "upnp:lastPlaybackPosition") == 0)
{ {
int sec = duration_sec(new); int sec = duration_sec(new);
if( h->req_client && (h->req_client->type->flags & FLAG_C
ONVERT_MS) ) {
sec /= 1000;
}
if (sec < 30) if (sec < 30)
sec = 0; sec = 0;
else else
sec -= 1; sec -= 1;
ret = sql_exec(db, "INSERT OR IGNORE into BOOKMARKS (ID, SEC)" ret = sql_exec(db, "INSERT OR IGNORE into BOOKMARKS (ID, SEC)"
" VALUES (%lld, %d)", (long long)detai lID, sec); " VALUES (%lld, %d)", (long long)detai lID, sec);
ret = sql_exec(db, "UPDATE BOOKMARKS set SEC = %d" ret = sql_exec(db, "UPDATE BOOKMARKS set SEC = %d"
" where SEC = %Q and ID = %lld", " where SEC = %Q and ID = %lld",
sec, current, (long long)detailID); sec, current, (long long)detailID);
} }
skipping to change at line 2121 skipping to change at line 2240
if( ObjectID && PosSecond ) if( ObjectID && PosSecond )
{ {
const char *rid = ObjectID; const char *rid = ObjectID;
int64_t detailID; int64_t detailID;
int sec = atoi(PosSecond); int sec = atoi(PosSecond);
int ret; int ret;
in_magic_container(ObjectID, 0, &rid); in_magic_container(ObjectID, 0, &rid);
detailID = sql_get_int64_field(db, "SELECT DETAIL_ID from OBJECTS where OBJECT_ID = '%q'", rid); detailID = sql_get_int64_field(db, "SELECT DETAIL_ID from OBJECTS where OBJECT_ID = '%q'", rid);
if( h->req_client && (h->req_client->type->flags & FLAG_CONVERT_M
S) ) {
sec /= 1000;
}
if ( sec < 30 ) if ( sec < 30 )
sec = 0; sec = 0;
ret = sql_exec(db, "INSERT OR IGNORE into BOOKMARKS (ID, SEC)" ret = sql_exec(db, "INSERT OR IGNORE into BOOKMARKS (ID, SEC)"
" VALUES (%lld, %d)", (long long)detailID, sec ); " VALUES (%lld, %d)", (long long)detailID, sec );
ret = sql_exec(db, "UPDATE BOOKMARKS set SEC = %d" ret = sql_exec(db, "UPDATE BOOKMARKS set SEC = %d"
" where ID = %lld", " where ID = %lld",
sec, (long long)detailID); sec, (long long)detailID);
if( ret != SQLITE_OK ) if( ret != SQLITE_OK )
DPRINTF(E_WARN, L_METADATA, "Error setting bookmark %s on ObjectID='%s'\n", PosSecond, rid); DPRINTF(E_WARN, L_METADATA, "Error setting bookmark %s on ObjectID='%s'\n", PosSecond, rid);
BuildSendAndCloseSoapResp(h, resp, sizeof(resp)-1); BuildSendAndCloseSoapResp(h, resp, sizeof(resp)-1);
 End of changes. 42 change blocks. 
75 lines changed or deleted 202 lines changed or added

Home  |  About  |  Features  |  All  |  Newest  |  Dox  |  Diffs  |  RSS Feeds  |  Screenshots  |  Comments  |  Imprint  |  Privacy  |  HTTP(S)