* Add full support for SortOrder and Filter parameters from TiVo.

This commit is contained in:
Justin Maggard 2009-03-16 06:59:27 +00:00
parent c5ba967f47
commit a62cceda99
4 changed files with 247 additions and 61 deletions

View File

@ -48,6 +48,7 @@
#include "log.h" #include "log.h"
#ifdef TIVO_SUPPORT #ifdef TIVO_SUPPORT
#include "tivo_beacon.h" #include "tivo_beacon.h"
#include "tivo_utils.h"
#endif #endif
/* MAX_LAN_ADDR : maximum number of interfaces /* MAX_LAN_ADDR : maximum number of interfaces
@ -673,6 +674,11 @@ main(int argc, char * * argv)
if( GETFLAG(TIVOMASK) ) if( GETFLAG(TIVOMASK) )
{ {
DPRINTF(E_WARN, L_GENERAL, "TiVo support is enabled.\n"); DPRINTF(E_WARN, L_GENERAL, "TiVo support is enabled.\n");
/* Add TiVo-specific randomize function to sqlite */
if( sqlite3_create_function(db, "tivorandom", 1, SQLITE_UTF8, NULL, &TiVoRandomSeedFunc, NULL, NULL) != SQLITE_OK )
{
DPRINTF(E_ERROR, L_TIVO, "ERROR: Failed to add sqlite randomize function for TiVo!\n");
}
/* open socket for sending Tivo notifications */ /* open socket for sending Tivo notifications */
sbeacon = OpenAndConfTivoBeaconSocket(); sbeacon = OpenAndConfTivoBeaconSocket();
if(sbeacon < 0) if(sbeacon < 0)

View File

@ -76,7 +76,6 @@ int callback(void *args, int argc, char **argv, char **azColName)
if( (passed_args->requested > -100) && (passed_args->returned >= passed_args->requested) ) if( (passed_args->requested > -100) && (passed_args->returned >= passed_args->requested) )
return 0; return 0;
printf("%s [%d]\n", title, passed_args->total);
if( strncmp(class, "item", 4) == 0 ) if( strncmp(class, "item", 4) == 0 )
{ {
if( strcmp(mime, "audio/mpeg") == 0 ) if( strcmp(mime, "audio/mpeg") == 0 )
@ -180,18 +179,23 @@ printf("%s [%d]\n", title, passed_args->total);
} }
void void
SendContainer(struct upnphttp * h, const char * objectID, int itemStart, int itemCount, char * anchorItem, int anchorOffset) SendContainer(struct upnphttp * h, const char * objectID, int itemStart, int itemCount, char * anchorItem,
int anchorOffset, int recurse, char * sortOrder, char * filter, unsigned long int randomSeed)
{ {
char * resp = malloc(1048576); char * resp = malloc(1048576);
char * items = malloc(1048576); char * items = malloc(1048576);
char *sql; char *sql, *item, *saveptr;
char *zErrMsg = NULL; char *zErrMsg = NULL;
char **result; char **result;
char *title; char *title;
char what[10], order[5]; char what[10], order[64], order2[64], myfilter[128];
char *which;
struct Response args; struct Response args;
int i, ret; int i, ret;
*items = '\0'; *items = '\0';
order[0] = '\0';
order2[0] = '\0';
myfilter[0] = '\0';
memset(&args, 0, sizeof(args)); memset(&args, 0, sizeof(args));
args.resp = items; args.resp = items;
@ -223,6 +227,137 @@ SendContainer(struct upnphttp * h, const char * objectID, int itemStart, int ite
sqlite3_free_table(result); sqlite3_free_table(result);
} }
if( recurse )
asprintf(&which, "OBJECT_ID glob '%s$*'", objectID);
else
asprintf(&which, "PARENT_ID = '%s'", objectID);
if( sortOrder )
{
if( strcasestr(sortOrder, "Random") )
{
sprintf(order, "tivorandom(%lu)", randomSeed);
if( itemCount < 0 )
sprintf(order2, "tivorandom(%lu) DESC", randomSeed);
else
sprintf(order2, "tivorandom(%lu)", randomSeed);
}
else
{
item = strtok_r(sortOrder, ",", &saveptr);
for( i=0; item != NULL; i++ )
{
int reverse=0;
if( *item == '!' )
{
reverse = 1;
item++;
}
if( strcasecmp(item, "Type") == 0 )
{
strcat(order, "CLASS");
strcat(order2, "CLASS");
}
else if( strcasecmp(item, "Title") == 0 )
{
strcat(order, "TITLE");
strcat(order2, "TITLE");
}
else if( strcasecmp(item, "CreationDate") == 0 ||
strcasecmp(item, "CaptureDate") == 0 )
{
strcat(order, "DATE");
strcat(order2, "DATE");
}
else
{
DPRINTF(E_INFO, L_TIVO, "Unhandled SortOrder [%s]\n", item);
goto unhandled_order;
}
if( reverse )
{
strcat(order, " DESC");
if( itemCount >= 0 )
{
strcat(order2, " DESC");
}
else
{
strcat(order2, " ASC");
}
}
else
{
strcat(order, " ASC");
if( itemCount >= 0 )
{
strcat(order2, " ASC");
}
else
{
strcat(order2, " DESC");
}
}
strcat(order, ", ");
strcat(order2, ", ");
unhandled_order:
item = strtok_r(NULL, ",", &saveptr);
}
strcat(order, "DETAIL_ID ASC");
if( itemCount >= 0 )
{
strcat(order2, "DETAIL_ID ASC");
}
else
{
strcat(order2, "DETAIL_ID DESC");
}
}
}
else
{
sprintf(order, "CLASS, NAME, DETAIL_ID");
if( itemCount < 0 )
sprintf(order2, "CLASS DESC, NAME DESC, DETAIL_ID DESC");
else
sprintf(order2, "CLASS, NAME, DETAIL_ID");
}
if( filter )
{
item = strtok_r(filter, ",", &saveptr);
for( i=0; item != NULL; i++ )
{
if( i )
{
strcat(myfilter, " or ");
}
if( strcasecmp(item, "x-container/folder") == 0 )
{
strcat(myfilter, "CLASS glob 'container*'");
}
else if( strncasecmp(item, "image", 5) == 0 )
{
strcat(myfilter, "MIME = 'image/jpeg'");
}
else if( strncasecmp(item, "audio", 5) == 0 )
{
strcat(myfilter, "MIME = 'audio/mpeg'");
}
else
{
DPRINTF(E_INFO, L_TIVO, "Unhandled Filter [%s]\n", item);
strcat(myfilter, "0 = 1");
}
item = strtok_r(NULL, ",", &saveptr);
}
}
else
{
strcpy(myfilter, "MIME = 'image/jpeg' or MIME = 'audio/mpeg' or CLASS glob 'container*'");
}
if( anchorItem ) if( anchorItem )
{ {
if( strstr(anchorItem, "QueryContainer") ) if( strstr(anchorItem, "QueryContainer") )
@ -234,13 +369,10 @@ SendContainer(struct upnphttp * h, const char * objectID, int itemStart, int ite
{ {
strcpy(what, "DETAIL_ID"); strcpy(what, "DETAIL_ID");
} }
if( itemCount < 0 ) sqlite3Prng.isInit = 0;
strcpy(order, "DESC");
else
strcpy(order, "ASC");
sql = sqlite3_mprintf("SELECT %s from OBJECTS o left join DETAILS d on (o.DETAIL_ID = d.ID)" sql = sqlite3_mprintf("SELECT %s from OBJECTS o left join DETAILS d on (o.DETAIL_ID = d.ID)"
" where PARENT_ID = '%s' and (MIME = 'image/jpeg' or MIME = 'audio/mpeg' or CLASS glob 'container*')" " where %s and (%s)"
" order by CLASS %s, NAME %s, DETAIL_ID %s", what, objectID, order, order, order); " order by %s", what, which, myfilter, order2);
if( itemCount < 0 ) if( itemCount < 0 )
{ {
args.requested *= -1; args.requested *= -1;
@ -264,9 +396,11 @@ SendContainer(struct upnphttp * h, const char * objectID, int itemStart, int ite
sqlite3_free(sql); sqlite3_free(sql);
} }
args.start = itemStart+anchorOffset; args.start = itemStart+anchorOffset;
sqlite3Prng.isInit = 0;
sql = sqlite3_mprintf("SELECT * from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)" sql = sqlite3_mprintf("SELECT * from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)"
" where PARENT_ID = '%s' and (MIME = 'image/jpeg' or MIME = 'audio/mpeg' or CLASS glob 'container*')" " where %s and (%s)"
" order by CLASS, NAME, DETAIL_ID", objectID); " order by %s", which, myfilter, order);
DPRINTF(E_DEBUG, L_TIVO, "%s\n", sql);
ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg); ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg);
sqlite3_free(sql); sqlite3_free(sql);
if( ret != SQLITE_OK ) if( ret != SQLITE_OK )
@ -291,8 +425,9 @@ SendContainer(struct upnphttp * h, const char * objectID, int itemStart, int ite
args.total, args.total,
title, args.start, title, args.start,
args.returned, items); args.returned, items);
free(title);
free(items); free(items);
free(title);
free(which);
BuildResp_upnphttp(h, resp, strlen(resp)); BuildResp_upnphttp(h, resp, strlen(resp));
free(resp); free(resp);
SendResp_upnphttp(h); SendResp_upnphttp(h);
@ -305,7 +440,9 @@ ProcessTiVoCommand(struct upnphttp * h, const char * orig_path)
char *key, *val; char *key, *val;
char *saveptr, *item; char *saveptr, *item;
char *command = NULL, *container = NULL, *anchorItem = NULL; char *command = NULL, *container = NULL, *anchorItem = NULL;
int itemStart=0, itemCount=-100, anchorOffset=0; char *sortOrder = NULL, *filter = NULL;
int itemStart=0, itemCount=-100, anchorOffset=0, recurse=0;
unsigned long int randomSeed=0;
path = strdup(orig_path); path = strdup(orig_path);
DPRINTF(E_DEBUG, L_GENERAL, "Processing TiVo command %s\n", path); DPRINTF(E_DEBUG, L_GENERAL, "Processing TiVo command %s\n", path);
@ -347,6 +484,26 @@ ProcessTiVoCommand(struct upnphttp * h, const char * orig_path)
{ {
anchorOffset = atoi(val); anchorOffset = atoi(val);
} }
else if( strcasecmp(key, "Recurse") == 0 )
{
recurse = strcasecmp("yes", val) == 0 ? 1 : 0;
}
else if( strcasecmp(key, "SortOrder") == 0 )
{
sortOrder = val;
}
else if( strcasecmp(key, "Filter") == 0 )
{
filter = val;
}
else if( strcasecmp(key, "RandomSeed") == 0 )
{
randomSeed = strtoul(val, NULL, 10);
}
else if( strcasecmp(key, "Format") == 0 )
{
// Only send XML
}
else else
{ {
DPRINTF(E_DEBUG, L_GENERAL, "Unhandled parameter [%s]\n", key); DPRINTF(E_DEBUG, L_GENERAL, "Unhandled parameter [%s]\n", key);
@ -366,55 +523,10 @@ ProcessTiVoCommand(struct upnphttp * h, const char * orig_path)
} }
else else
{ {
SendContainer(h, container, itemStart, itemCount, anchorItem, anchorOffset); SendContainer(h, container, itemStart, itemCount, anchorItem, anchorOffset, recurse, sortOrder, filter, randomSeed);
} }
} }
free(path); free(path);
CloseSocket_upnphttp(h); CloseSocket_upnphttp(h);
} }
void
ProcessTiVoRequest(struct upnphttp * h, const char * orig_path)
{
char *path;
char *key, *val;
char *saveptr, *item;
char *command = NULL, *container = NULL;
int itemStart=0, itemCount=0;
path = decodeString(orig_path, 0);
DPRINTF(E_DEBUG, L_GENERAL, "Processing TiVo request %s\n", path);
item = strtok_r( path, "&", &saveptr );
while( item != NULL )
{
if( strlen( item ) == 0 )
{
item = strtok_r( NULL, "&", &saveptr );
continue;
}
val = item;
key = strsep(&val, "=");
DPRINTF(E_DEBUG, L_GENERAL, "%s: %s\n", key, val);
if( strcasecmp(key, "width") == 0 )
{
command = val;
}
else if( strcasecmp(key, "height") == 0 )
{
container = val;
}
else if( strcasecmp(key, "rotation") == 0 )
{
itemStart = atoi(val);
}
else if( strcasecmp(key, "pixelshape") == 0 )
{
itemCount = atoi(val);
}
item = strtok_r( NULL, "&", &saveptr );
}
CloseSocket_upnphttp(h);
}
#endif // TIVO_SUPPORT #endif // TIVO_SUPPORT

View File

@ -3,6 +3,9 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <ctype.h> #include <ctype.h>
#include <linux/types.h> // Defines __u32
#include <sqlite3.h>
#include "tivo_utils.h"
/* This function based on byRequest */ /* This function based on byRequest */
char * char *
@ -62,4 +65,58 @@ decodeString(char * string, int inplace)
return ns; return ns;
} }
} }
/* These next functions implement a repeatable random function with a user-provided seed */
static int
seedRandomByte(__u32 seed) {
unsigned char t;
if( !sqlite3Prng.isInit )
{
int i;
char k[256];
sqlite3Prng.j = 0;
sqlite3Prng.i = 0;
memset(&k, 0, 256);
memcpy(&k, &seed, 4);
for(i=0; i<256; i++){
sqlite3Prng.s[i] = i;
}
for(i=0; i<256; i++){
sqlite3Prng.j += sqlite3Prng.s[i] + k[i];
t = sqlite3Prng.s[sqlite3Prng.j];
sqlite3Prng.s[sqlite3Prng.j] = sqlite3Prng.s[i];
sqlite3Prng.s[i] = t;
}
sqlite3Prng.isInit = 1;
}
/* Generate and return single random byte */
sqlite3Prng.i++;
t = sqlite3Prng.s[sqlite3Prng.i];
sqlite3Prng.j += t;
sqlite3Prng.s[sqlite3Prng.i] = sqlite3Prng.s[sqlite3Prng.j];
sqlite3Prng.s[sqlite3Prng.j] = t;
t += sqlite3Prng.s[sqlite3Prng.i];
return sqlite3Prng.s[t];
}
void
seedRandomness(int N, void *pBuf, __u32 seed){
unsigned char *zBuf = pBuf;
while( N-- ){
*(zBuf++) = seedRandomByte(seed);
}
}
void
TiVoRandomSeedFunc(sqlite3_context *context, int argc, sqlite3_value **argv)
{
sqlite_int64 r, seed;
if( argc != 1 || sqlite3_value_type(argv[0]) != SQLITE_INTEGER )
return;
seed = sqlite3_value_int64(argv[0]);
seedRandomness(sizeof(r), &r, seed);
sqlite3_result_int64(context, r);
}
#endif #endif

View File

@ -1,5 +1,16 @@
#include "config.h" #include "config.h"
#ifdef TIVO_SUPPORT #ifdef TIVO_SUPPORT
#include <sqlite3.h>
struct sqlite3PrngType {
unsigned char isInit; /* True if initialized */
unsigned char i, j; /* State variables */
unsigned char s[256]; /* State variables */
} sqlite3Prng;
char * char *
decodeString(const char * string, int inplace); decodeString(char * string, int inplace);
void
TiVoRandomSeedFunc(sqlite3_context *context, int argc, sqlite3_value **argv);
#endif #endif