diff --git a/scanner.c b/scanner.c index 768f736..fbb0d23 100644 --- a/scanner.c +++ b/scanner.c @@ -461,7 +461,7 @@ insert_directory(const char * name, const char * path, const char * base, const { if( strcmp(id_buf, last_found) == 0 ) break; - sql = sqlite3_mprintf("SELECT count(OBJECT_ID) from OBJECTS where OBJECT_ID = '%s'", id_buf); + sql = sqlite3_mprintf("SELECT count(*) from OBJECTS where OBJECT_ID = '%s'", id_buf); if( (sql_get_table(db, sql, &result, NULL, NULL) == SQLITE_OK) && atoi(result[1]) ) { sqlite3_free_table(result); diff --git a/tivo_commands.c b/tivo_commands.c index 6d919a1..d572f3d 100644 --- a/tivo_commands.c +++ b/tivo_commands.c @@ -554,11 +554,10 @@ SendContainer(struct upnphttp * h, const char * objectID, int itemStart, int ite args.start = itemStart+anchorOffset; sqlite3Prng.isInit = 0; - asprintf(&sql, "SELECT count(*) from " - "( select 1 from OBJECTS o left join DETAILS d on (o.DETAIL_ID = d.ID)" - " where %s and (%s)" - " %s )", - which, myfilter, groupBy); + asprintf(&sql, "SELECT count(distinct DETAIL_ID) " + "from OBJECTS o left join DETAILS d on (o.DETAIL_ID = d.ID)" + " where %s and (%s)", + which, myfilter); DPRINTF(E_DEBUG, L_TIVO, "Count SQL: %s\n", sql); ret = sql_get_table(db, sql, &result, NULL, NULL); if( ret == SQLITE_OK ) diff --git a/upnpsoap.c b/upnpsoap.c index 1d83470..91b3f9f 100644 --- a/upnpsoap.c +++ b/upnpsoap.c @@ -127,7 +127,12 @@ GetSortCapabilities(struct upnphttp * h, const char * action) static const char resp[] = "" - "" + "" + "dc:title," + "dc:date," + "upnp:class," + "upnp:originalTrackNumber" + "" ""; char body[512]; @@ -275,6 +280,81 @@ set_filter_flags(char * filter) return flags; } +char * +parse_sort_criteria(char * sortCriteria) +{ + char *order = NULL; + char *item, *saveptr; + int i, ret, reverse, title_sorted = 0; + + if( !sortCriteria ) + return NULL; + + if( (item = strtok_r(sortCriteria, ",", &saveptr)) ) + { + order = malloc(4096); + strcpy(order, "order by "); + } + for( i=0; item != NULL; i++ ) + { + reverse=0; + if( i ) + strcat(order, ", "); + if( *item == '+' ) + { + item++; + } + else if( *item == '-' ) + { + reverse = 1; + item++; + } + if( strcasecmp(item, "upnp:class") == 0 ) + { + strcat(order, "o.CLASS"); + } + else if( strcasecmp(item, "dc:title") == 0 ) + { + strcat(order, "d.TITLE"); + title_sorted = 1; + } + else if( strcasecmp(item, "dc:date") == 0 ) + { + strcat(order, "d.DATE"); + } + else if( strcasecmp(item, "upnp:originalTrackNumber") == 0 ) + { + strcat(order, "d.TRACK"); + } + else + { + printf("Unhandled SortCriteria [%s]\n", item); + if( i ) + { + ret = strlen(order); + order[ret-2] = '\0'; + } + i--; + goto unhandled_order; + } + + if( reverse ) + strcat(order, " DESC"); + unhandled_order: + item = strtok_r(NULL, ",", &saveptr); + } + if( i <= 0 ) + { + free(order); + return NULL; + } + /* Add a "tiebreaker" sort order */ + if( !title_sorted ) + strcat(order, ", TITLE ASC"); + + return order; +} + #define SELECT_COLUMNS "SELECT o.OBJECT_ID, o.PARENT_ID, o.REF_ID, o.DETAIL_ID, o.CLASS," \ " 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.RESOLUTION," \ @@ -294,10 +374,6 @@ callback(void *args, int argc, char **argv, char **azColName) int children, ret = 0; static int warned = 0; - passed_args->total++; - - if( passed_args->requested && (passed_args->returned >= passed_args->requested) ) - return 0; /* Make sure we have at least 4KB left of allocated memory to finish the response. */ if( passed_args->size > 1044480 && !warned ) { @@ -459,7 +535,7 @@ callback(void *args, int argc, char **argv, char **azColName) passed_args->size += ret; if( passed_args->filter & FILTER_CHILDCOUNT ) { - sprintf(str_buf, "SELECT count(ID) from OBJECTS where PARENT_ID = '%s';", id); + sprintf(str_buf, "SELECT count(*) from OBJECTS where PARENT_ID = '%s';", id); ret = sql_get_table(db, str_buf, &result, NULL, NULL); if( ret == SQLITE_OK ) { children = atoi(result[1]); @@ -542,8 +618,10 @@ BrowseContentDirectory(struct upnphttp * h, const char * action) char str_buf[512]; char *zErrMsg = 0; char *sql; + char **result; int ret; struct Response args; + int totalMatches = 0; struct NameValueParserData data; *resp = '\0'; @@ -554,10 +632,19 @@ BrowseContentDirectory(struct upnphttp * h, const char * action) char * Filter = GetValueFromNameValueList(&data, "Filter"); char * BrowseFlag = GetValueFromNameValueList(&data, "BrowseFlag"); char * SortCriteria = GetValueFromNameValueList(&data, "SortCriteria"); + char * orderBy = NULL; if( !ObjectId ) ObjectId = GetValueFromNameValueList(&data, "ContainerID"); memset(&args, 0, sizeof(args)); + if( !RequestedCount ) + RequestedCount = -1; +#ifdef __sparc__ /* Sorting takes too long on slow processors with very large containers */ + if( totalMatches < 10000 ) +#endif + orderBy = parse_sort_criteria(SortCriteria); + + args.resp = resp; args.size = sprintf(resp, "%s", resp0); /* See if we need to include DLNA namespace reference */ @@ -572,7 +659,6 @@ BrowseContentDirectory(struct upnphttp * h, const char * action) memcpy(resp+args.size, &str_buf, ret+1); args.size += ret; - args.total = StartingIndex; args.returned = 0; args.requested = RequestedCount; args.client = h->req_client; @@ -603,14 +689,23 @@ BrowseContentDirectory(struct upnphttp * h, const char * action) " where OBJECT_ID = '%s';" , ObjectId); ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg); + totalMatches = args.returned; } else { + sprintf(str_buf, "SELECT count(*) from OBJECTS where PARENT_ID = '%s'", ObjectId); + ret = sql_get_table(db, str_buf, &result, NULL, NULL); + if( ret == SQLITE_OK ) { + totalMatches = atoi(result[1]); + sqlite3_free_table(result); + } sql = sqlite3_mprintf( SELECT_COLUMNS "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)" - " where PARENT_ID = '%s' order by d.TRACK, d.ARTIST, d.TITLE limit %d, -1;", - ObjectId, StartingIndex); + " where PARENT_ID = '%s' %s limit %d, %d;", + ObjectId, orderBy, StartingIndex, RequestedCount); + DPRINTF(E_DEBUG, L_HTTP, "Browse SQL: %s\n", sql); ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg); + totalMatches = args.returned; } sqlite3_free(sql); if( ret != SQLITE_OK ) @@ -623,11 +718,13 @@ BrowseContentDirectory(struct upnphttp * h, const char * action) "%u\n" "%u" "", - args.returned, args.total, updateID); + args.returned, totalMatches, updateID); memcpy(resp+args.size, &str_buf, ret+1); args.size += ret; BuildSendAndCloseSoapResp(h, resp, args.size); ClearNameValueList(&data); + if( orderBy ) + free(orderBy); free(resp); if( h->req_client == EXbox ) { @@ -648,9 +745,11 @@ SearchContentDirectory(struct upnphttp * h, const char * action) char *resp = malloc(1048576); char *zErrMsg = 0; char *sql; + char **result; char str_buf[4096]; int ret; struct Response args; + int totalMatches = 0; *resp = '\0'; struct NameValueParserData data; @@ -662,9 +761,17 @@ SearchContentDirectory(struct upnphttp * h, const char * action) char * SearchCriteria = GetValueFromNameValueList(&data, "SearchCriteria"); char * SortCriteria = GetValueFromNameValueList(&data, "SortCriteria"); char * newSearchCriteria = NULL; - + char * orderBy = NULL; + char groupBy[] = "group by DETAIL_ID"; memset(&args, 0, sizeof(args)); + if( !RequestedCount ) + RequestedCount = -1; +#ifdef __sparc__ /* Sorting takes too long on slow processors with very large containers */ + if( totalMatches < 10000 ) +#endif + orderBy = parse_sort_criteria(SortCriteria); + args.resp = resp; args.size = sprintf(resp, "%s", resp0); /* See if we need to include DLNA namespace reference */ @@ -679,7 +786,6 @@ SearchContentDirectory(struct upnphttp * h, const char * action) memcpy(resp+args.size, &str_buf, ret+1); args.size += ret; - args.total = StartingIndex; args.returned = 0; args.requested = RequestedCount; args.client = h->req_client; @@ -695,6 +801,20 @@ SearchContentDirectory(struct upnphttp * h, const char * action) ContainerID = strdup("1$7"); else ContainerID = strdup(ContainerID); + #if 0 // Looks like the 360 already does this + /* Sort by track number for some containers */ + if( orderBy && + ((strncmp(ContainerID, "1$5", 3) == 0) || + (strncmp(ContainerID, "1$6", 3) == 0) || + (strncmp(ContainerID, "1$7", 3) == 0)) ) + { + DPRINTF(E_DEBUG, L_HTTP, "Old sort order: %s\n", orderBy); + sprintf(str_buf, "d.TRACK, "); + memmove(orderBy+18, orderBy+9, strlen(orderBy)+1); + memmove(orderBy+9, &str_buf, 9); + DPRINTF(E_DEBUG, L_HTTP, "New sort order: %s\n", orderBy); + } + #endif } DPRINTF(E_DEBUG, L_HTTP, "Browsing ContentDirectory:\n" " * ObjectID: %s\n" @@ -708,6 +828,8 @@ SearchContentDirectory(struct upnphttp * h, const char * action) if( strcmp(ContainerID, "0") == 0 ) *ContainerID = '*'; + else if( strcmp(ContainerID, "1$4") == 0 ) + groupBy[0] = '\0'; if( !SearchCriteria ) { asprintf(&newSearchCriteria, "1 = 1"); @@ -717,8 +839,8 @@ SearchContentDirectory(struct upnphttp * h, const char * action) { SearchCriteria = modifyString(SearchCriteria, """, "\"", 0); SearchCriteria = modifyString(SearchCriteria, "'", "'", 0); - SearchCriteria = modifyString(SearchCriteria, "derivedfrom", "like", 1); - SearchCriteria = modifyString(SearchCriteria, "contains", "like", 1); + SearchCriteria = modifyString(SearchCriteria, "derivedfrom", "glob", 1); + SearchCriteria = modifyString(SearchCriteria, "contains", "glob", 1); SearchCriteria = modifyString(SearchCriteria, "dc:title", "d.TITLE", 0); SearchCriteria = modifyString(SearchCriteria, "dc:creator", "d.CREATOR", 0); SearchCriteria = modifyString(SearchCriteria, "upnp:class", "o.CLASS", 0); @@ -738,17 +860,30 @@ SearchContentDirectory(struct upnphttp * h, const char * action) } DPRINTF(E_DEBUG, L_HTTP, "Translated SearchCriteria: %s\n", SearchCriteria); + sprintf(str_buf, "SELECT (select count(distinct DETAIL_ID) from OBJECTS o left join DETAILS d on (o.DETAIL_ID = d.ID)" + " where (OBJECT_ID glob '%s$*') and (%s))" + " + " + "(select count(*) from OBJECTS o left join DETAILS d on (o.DETAIL_ID = d.ID)" + " where (OBJECT_ID = '%s') and (%s))", + ContainerID, SearchCriteria, ContainerID, SearchCriteria); + //DEBUG DPRINTF(E_DEBUG, L_HTTP, "Count SQL: %s\n", sql); + ret = sql_get_table(db, str_buf, &result, NULL, NULL); + if( ret == SQLITE_OK ) { + totalMatches = atoi(result[1]); + sqlite3_free_table(result); + } + sql = sqlite3_mprintf( SELECT_COLUMNS "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)" - " where OBJECT_ID glob '%s$*' and (%s) group by DETAIL_ID " - "%z" - " order by d.TRACK, d.TITLE limit %d, -1;", - ContainerID, SearchCriteria, + " where OBJECT_ID glob '%s$*' and (%s) %s " + "%z %s" + " limit %d, %d", + ContainerID, SearchCriteria, groupBy, (*ContainerID == '*') ? NULL : sqlite3_mprintf("UNION ALL " SELECT_COLUMNS "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)" " where OBJECT_ID = '%s' and (%s) ", ContainerID, SearchCriteria), - StartingIndex); + orderBy, StartingIndex, RequestedCount); DPRINTF(E_DEBUG, L_HTTP, "Search SQL: %s\n", sql); ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg); if( ret != SQLITE_OK ) @@ -763,11 +898,13 @@ SearchContentDirectory(struct upnphttp * h, const char * action) "%u\n" "%u" "", - args.returned, args.total, updateID); + args.returned, totalMatches, updateID); memcpy(resp+args.size, &str_buf, ret+1); args.size += ret; BuildSendAndCloseSoapResp(h, resp, args.size); ClearNameValueList(&data); + if( orderBy ) + free(orderBy); if( newSearchCriteria ) free(newSearchCriteria); free(resp); diff --git a/upnpsoap.h b/upnpsoap.h index c910213..2af832e 100644 --- a/upnpsoap.h +++ b/upnpsoap.h @@ -20,7 +20,6 @@ struct Response int start; int returned; int requested; - int total; int size; u_int32_t filter; enum clientType client;