diff --git a/minidlna.c b/minidlna.c index 4e81a50..b7f8e2e 100644 --- a/minidlna.c +++ b/minidlna.c @@ -48,6 +48,7 @@ #include "log.h" #ifdef TIVO_SUPPORT #include "tivo_beacon.h" +#include "tivo_utils.h" #endif /* MAX_LAN_ADDR : maximum number of interfaces @@ -673,6 +674,11 @@ main(int argc, char * * argv) if( GETFLAG(TIVOMASK) ) { 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 */ sbeacon = OpenAndConfTivoBeaconSocket(); if(sbeacon < 0) diff --git a/tivo_commands.c b/tivo_commands.c index bf7f6a4..f47ce75 100644 --- a/tivo_commands.c +++ b/tivo_commands.c @@ -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) ) return 0; -printf("%s [%d]\n", title, passed_args->total); if( strncmp(class, "item", 4) == 0 ) { if( strcmp(mime, "audio/mpeg") == 0 ) @@ -180,18 +179,23 @@ printf("%s [%d]\n", title, passed_args->total); } 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 * items = malloc(1048576); - char *sql; + char *sql, *item, *saveptr; char *zErrMsg = NULL; char **result; char *title; - char what[10], order[5]; + char what[10], order[64], order2[64], myfilter[128]; + char *which; struct Response args; int i, ret; *items = '\0'; + order[0] = '\0'; + order2[0] = '\0'; + myfilter[0] = '\0'; memset(&args, 0, sizeof(args)); args.resp = items; @@ -223,6 +227,137 @@ SendContainer(struct upnphttp * h, const char * objectID, int itemStart, int ite 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( strstr(anchorItem, "QueryContainer") ) @@ -234,13 +369,10 @@ SendContainer(struct upnphttp * h, const char * objectID, int itemStart, int ite { strcpy(what, "DETAIL_ID"); } - if( itemCount < 0 ) - strcpy(order, "DESC"); - else - strcpy(order, "ASC"); + sqlite3Prng.isInit = 0; 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*')" - " order by CLASS %s, NAME %s, DETAIL_ID %s", what, objectID, order, order, order); + " where %s and (%s)" + " order by %s", what, which, myfilter, order2); if( itemCount < 0 ) { args.requested *= -1; @@ -264,9 +396,11 @@ SendContainer(struct upnphttp * h, const char * objectID, int itemStart, int ite sqlite3_free(sql); } args.start = itemStart+anchorOffset; + sqlite3Prng.isInit = 0; 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*')" - " order by CLASS, NAME, DETAIL_ID", objectID); + " where %s and (%s)" + " order by %s", which, myfilter, order); + DPRINTF(E_DEBUG, L_TIVO, "%s\n", sql); ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg); sqlite3_free(sql); if( ret != SQLITE_OK ) @@ -291,8 +425,9 @@ SendContainer(struct upnphttp * h, const char * objectID, int itemStart, int ite args.total, title, args.start, args.returned, items); - free(title); free(items); + free(title); + free(which); BuildResp_upnphttp(h, resp, strlen(resp)); free(resp); SendResp_upnphttp(h); @@ -305,7 +440,9 @@ ProcessTiVoCommand(struct upnphttp * h, const char * orig_path) char *key, *val; char *saveptr, *item; 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); 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); } + 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 { DPRINTF(E_DEBUG, L_GENERAL, "Unhandled parameter [%s]\n", key); @@ -366,55 +523,10 @@ ProcessTiVoCommand(struct upnphttp * h, const char * orig_path) } else { - SendContainer(h, container, itemStart, itemCount, anchorItem, anchorOffset); + SendContainer(h, container, itemStart, itemCount, anchorItem, anchorOffset, recurse, sortOrder, filter, randomSeed); } } free(path); 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 diff --git a/tivo_utils.c b/tivo_utils.c index 4d8a20e..645bc6d 100644 --- a/tivo_utils.c +++ b/tivo_utils.c @@ -3,6 +3,9 @@ #include #include #include +#include // Defines __u32 +#include +#include "tivo_utils.h" /* This function based on byRequest */ char * @@ -62,4 +65,58 @@ decodeString(char * string, int inplace) 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 diff --git a/tivo_utils.h b/tivo_utils.h index b32c13f..5dcf2a6 100644 --- a/tivo_utils.h +++ b/tivo_utils.h @@ -1,5 +1,16 @@ #include "config.h" #ifdef TIVO_SUPPORT +#include + +struct sqlite3PrngType { + unsigned char isInit; /* True if initialized */ + unsigned char i, j; /* State variables */ + unsigned char s[256]; /* State variables */ +} sqlite3Prng; + char * -decodeString(const char * string, int inplace); +decodeString(char * string, int inplace); + +void +TiVoRandomSeedFunc(sqlite3_context *context, int argc, sqlite3_value **argv); #endif