* Some SQL optimizations. Noticeable mostly on very slow systems with very large media libraries.
* Add basic DLNA/UPnP-AV SortCriteria support.
This commit is contained in:
parent
11b208a6e6
commit
226cf8bb66
@ -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);
|
||||
|
@ -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 )
|
||||
|
177
upnpsoap.c
177
upnpsoap.c
@ -127,7 +127,12 @@ GetSortCapabilities(struct upnphttp * h, const char * action)
|
||||
static const char resp[] =
|
||||
"<u:%sResponse "
|
||||
"xmlns:u=\"%s\">"
|
||||
"<SortCaps></SortCaps>"
|
||||
"<SortCaps>"
|
||||
"dc:title,"
|
||||
"dc:date,"
|
||||
"upnp:class,"
|
||||
"upnp:originalTrackNumber"
|
||||
"</SortCaps>"
|
||||
"</u:%sResponse>";
|
||||
|
||||
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)
|
||||
"<TotalMatches>%u</TotalMatches>\n"
|
||||
"<UpdateID>%u</UpdateID>"
|
||||
"</u:BrowseResponse>",
|
||||
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)
|
||||
"<TotalMatches>%u</TotalMatches>\n"
|
||||
"<UpdateID>%u</UpdateID>"
|
||||
"</u:SearchResponse>",
|
||||
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);
|
||||
|
@ -20,7 +20,6 @@ struct Response
|
||||
int start;
|
||||
int returned;
|
||||
int requested;
|
||||
int total;
|
||||
int size;
|
||||
u_int32_t filter;
|
||||
enum clientType client;
|
||||
|
Loading…
x
Reference in New Issue
Block a user