diff --git a/Makefile.am b/Makefile.am index 9b55a8a..9273aa5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -28,7 +28,7 @@ minidlnad_SOURCES = minidlna.c upnphttp.c upnpdescgen.c upnpsoap.c \ sql.c utils.c metadata.c scanner.c inotify.c \ tivo_utils.c tivo_beacon.c tivo_commands.c \ playlist.c image_utils.c albumart.c log.c \ - tagutils/tagutils.c + containers.c tagutils/tagutils.c #if NEED_VORBIS vorbisflag = -lvorbis diff --git a/clients.h b/clients.h index dc766c6..d30f4ed 100644 --- a/clients.h +++ b/clients.h @@ -38,9 +38,7 @@ #define FLAG_FORCE_SORT 0x00000800 #define FLAG_CAPTION_RES 0x00001000 /* Response-related flags */ -#define FLAG_HAS_CAPTIONS 0x20000000 -#define FLAG_FREE_OBJECT_ID 0x40000000 -#define FLAG_ROOT_CONTAINER 0x80000000 +#define FLAG_HAS_CAPTIONS 0x80000000 enum match_types { EMatchNone, diff --git a/containers.c b/containers.c new file mode 100644 index 0000000..8e95ee3 --- /dev/null +++ b/containers.c @@ -0,0 +1,170 @@ +/* MiniDLNA media server + * Copyright (C) 2014 NETGEAR + * + * This file is part of MiniDLNA. + * + * MiniDLNA is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * MiniDLNA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MiniDLNA. If not, see . + */ +#include +#include "clients.h" +#include "minidlnatypes.h" +#include "scanner.h" +#include "upnpglobalvars.h" +#include "containers.h" +#include "log.h" + +#define NINETY_DAYS "7776000" + +const char *music_id = MUSIC_ID; +const char *music_all_id = MUSIC_ALL_ID; +const char *music_genre_id = MUSIC_GENRE_ID; +const char *music_artist_id = MUSIC_ARTIST_ID; +const char *music_album_id = MUSIC_ALBUM_ID; +const char *music_plist_id = MUSIC_PLIST_ID; +const char *music_dir_id = MUSIC_DIR_ID; +const char *video_all_id = VIDEO_ALL_ID; +const char *video_dir_id = VIDEO_DIR_ID; +const char *image_all_id = IMAGE_ALL_ID; +const char *image_date_id = IMAGE_DATE_ID; +const char *image_camera_id = IMAGE_CAMERA_ID; +const char *image_dir_id = IMAGE_DIR_ID; + +struct magic_container_s magic_containers[] = +{ + /* Alternate root container */ + { NULL, + "0", + &runtime_vars.root_container, + NULL, + "0", + NULL, + NULL, + NULL, + NULL, + -1, + 0, + }, + + /* Recent 50 audio items */ + { "Recently Added", + "1$FF0", + NULL, + "\"1$FF0$\" || OBJECT_ID", + "\"1$FF0\"", + "o.OBJECT_ID", + "(select null from DETAILS where MIME glob 'a*' and timestamp > (strftime('%s','now') - "NINETY_DAYS") limit 50)", + "MIME glob 'a*' and REF_ID is NULL and timestamp > (strftime('%s','now') - "NINETY_DAYS")", + "order by TIMESTAMP DESC", + 50, + 0, + }, + + /* Recent 50 video items */ + { "Recently Added", + "2$FF0", + NULL, + "\"2$FF0$\" || OBJECT_ID", + "\"2$FF0\"", + "o.OBJECT_ID", + "(select null from DETAILS where MIME glob 'v*' and timestamp > (strftime('%s','now') - "NINETY_DAYS") limit 50)", + "MIME glob 'v*' and REF_ID is NULL and timestamp > (strftime('%s','now') - "NINETY_DAYS")", + "order by TIMESTAMP DESC", + 50, + 0, + }, + + /* Recent 50 image items */ + { "Recently Added", + "3$FF0", + NULL, + "\"3$FF0$\" || OBJECT_ID", + "\"3$FF0\"", + "o.OBJECT_ID", + "(select null from DETAILS where MIME glob 'i*' and timestamp > (strftime('%s','now') - "NINETY_DAYS") limit 50)", + "MIME glob 'i*' and REF_ID is NULL and timestamp > (strftime('%s','now') - "NINETY_DAYS")", + "order by TIMESTAMP DESC", + 50, + 0, + }, + + /* Microsoft PlaysForSure containers */ + { NULL, "4", &music_all_id, NULL, NULL, NULL, NULL, NULL, NULL, -1, FLAG_MS_PFS }, + { NULL, "5", &music_genre_id, NULL, NULL, NULL, NULL, NULL, NULL, -1, FLAG_MS_PFS }, + { NULL, "6", &music_artist_id, NULL, NULL, NULL, NULL, NULL, NULL, -1, FLAG_MS_PFS }, + { NULL, "7", &music_album_id, NULL, NULL, NULL, NULL, NULL, NULL, -1, FLAG_MS_PFS }, + { NULL, "8", &video_all_id, NULL, NULL, NULL, NULL, NULL, NULL, -1, FLAG_MS_PFS }, + { NULL, "B", &image_all_id, NULL, NULL, NULL, NULL, NULL, NULL, -1, FLAG_MS_PFS }, + { NULL, "C", &image_date_id, NULL, NULL, NULL, NULL, NULL, NULL, -1, FLAG_MS_PFS }, + { NULL, "F", &music_plist_id, NULL, NULL, NULL, NULL, NULL, NULL, -1, FLAG_MS_PFS }, + { NULL, "14", &music_dir_id, NULL, NULL, NULL, NULL, NULL, NULL, -1, FLAG_MS_PFS }, + { NULL, "15", &video_dir_id, NULL, NULL, NULL, NULL, NULL, NULL, -1, FLAG_MS_PFS }, + { NULL, "16", &image_dir_id, NULL, NULL, NULL, NULL, NULL, NULL, -1, FLAG_MS_PFS }, + { NULL, "D2", &image_camera_id, NULL, NULL, NULL, NULL, NULL, NULL, -1, FLAG_MS_PFS }, + + /* Jump straight to Music on audio-only devices */ + { NULL, "0", &music_id, NULL, "0", NULL, NULL, NULL, NULL, -1, FLAG_AUDIO_ONLY }, + + { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, 0 } +}; + +struct magic_container_s * +in_magic_container(const char *id, int flags, const char **real_id) +{ + size_t len; + int i; + + for (i = 0; magic_containers[i].objectid_match; i++) + { + if (magic_containers[i].required_flags && !(flags & magic_containers[i].required_flags)) + continue; + if (magic_containers[i].objectid && !(*magic_containers[i].objectid)) + continue; + DPRINTF(E_DEBUG, L_HTTP, "Checking magic container %d [%s]\n", i, magic_containers[i].objectid_match); + len = strlen(magic_containers[i].objectid_match); + if (strncmp(id, magic_containers[i].objectid_match, len) == 0) + { + if (*(id+len) == '$') + *real_id = id+len+1; + else if (*(id+len) == '\0') + *real_id = id; + else + continue; + DPRINTF(E_DEBUG, L_HTTP, "Found magic container %d [%s]\n", i, magic_containers[i].objectid_match); + return &magic_containers[i]; + } + } + + return NULL; +} + +struct magic_container_s * +check_magic_container(const char *id, int flags) +{ + int i; + + for (i = 0; magic_containers[i].objectid_match; i++) + { + if (magic_containers[i].required_flags && !(flags & magic_containers[i].required_flags)) + continue; + if (magic_containers[i].objectid && !(*magic_containers[i].objectid)) + continue; + DPRINTF(E_DEBUG, L_HTTP, "Checking magic container %d [%s]\n", i, magic_containers[i].objectid_match); + if (strcmp(id, magic_containers[i].objectid_match) == 0) + { + DPRINTF(E_DEBUG, L_HTTP, "Found magic container %d [%s]\n", i, magic_containers[i].objectid_match); + return &magic_containers[i]; + } + } + + return NULL; +} diff --git a/containers.h b/containers.h new file mode 100644 index 0000000..aef77c5 --- /dev/null +++ b/containers.h @@ -0,0 +1,36 @@ +/* MiniDLNA media server + * Copyright (C) 2014 NETGEAR + * + * This file is part of MiniDLNA. + * + * MiniDLNA is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * MiniDLNA is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MiniDLNA. If not, see . + */ + +struct magic_container_s { + const char *name; + const char *objectid_match; + const char **objectid; + const char *objectid_sql; + const char *parentid_sql; + const char *refid_sql; + const char *child_count; + const char *where; + const char *orderby; + int max_count; + int required_flags; +}; + +extern struct magic_container_s magic_containers[]; + +struct magic_container_s *in_magic_container(const char *id, int flags, const char **real_id); +struct magic_container_s *check_magic_container(const char *id, int flags); diff --git a/minidlnatypes.h b/minidlnatypes.h index bc4e195..6879b70 100644 --- a/minidlnatypes.h +++ b/minidlnatypes.h @@ -49,8 +49,8 @@ struct runtime_vars_s { int port; /* HTTP Port */ int notify_interval; /* seconds between SSDP announces */ int max_connections; /* max number of simultaneous conenctions */ - char *root_container; /* root ObjectID (instead of "0") */ - char *ifaces[MAX_LAN_ADDR]; /* list of configured network interfaces */ + const char *root_container; /* root ObjectID (instead of "0") */ + const char *ifaces[MAX_LAN_ADDR]; /* list of configured network interfaces */ }; struct string_s { diff --git a/scanner.c b/scanner.c index 5ae5612..ee3af48 100644 --- a/scanner.c +++ b/scanner.c @@ -44,6 +44,7 @@ #include "sql.h" #include "scanner.h" #include "albumart.h" +#include "containers.h" #include "log.h" #if SCANDIR_CONST @@ -580,6 +581,25 @@ CreateDatabase(void) if( ret != SQLITE_OK ) goto sql_failed; } + for( i=0; magic_containers[i].objectid_match; i++ ) + { + struct magic_container_s *magic = &magic_containers[i]; + if (!magic->name) + continue; + if( sql_get_int_field(db, "SELECT 1 from OBJECTS where OBJECT_ID = '%s'", magic->objectid_match) == 0 ) + { + char *parent = strdup(magic->objectid_match); + if (strrchr(parent, '$')) + *strrchr(parent, '$') = '\0'; + ret = sql_exec(db, "INSERT into OBJECTS (OBJECT_ID, PARENT_ID, DETAIL_ID, CLASS, NAME)" + " values " + "('%s', '%s', %lld, 'container.storageFolder', '%q')", + magic->objectid_match, parent, GetFolderMetadata(magic->name, NULL, NULL, NULL, 0), magic->name); + free(parent); + if( ret != SQLITE_OK ) + goto sql_failed; + } + } sql_exec(db, "create INDEX IDX_OBJECTS_OBJECT_ID ON OBJECTS(OBJECT_ID);"); sql_exec(db, "create INDEX IDX_OBJECTS_PARENT_ID ON OBJECTS(PARENT_ID);"); sql_exec(db, "create INDEX IDX_OBJECTS_DETAIL_ID ON OBJECTS(DETAIL_ID);"); diff --git a/upnpsoap.c b/upnpsoap.c index 8c789bb..d226272 100644 --- a/upnpsoap.c +++ b/upnpsoap.c @@ -65,12 +65,19 @@ #include "utils.h" #include "upnphttp.h" #include "upnpsoap.h" +#include "containers.h" #include "upnpreplyparse.h" #include "getifaddr.h" #include "scanner.h" #include "sql.h" #include "log.h" +#ifdef __sparc__ /* Sorting takes too long on slow processors with very large containers */ +# define __SORT_LIMIT if( totalMatches < 10000 ) +#else +# define __SORT_LIMIT +#endif + static void BuildSendAndCloseSoapResp(struct upnphttp * h, const char * body, int bodylen) @@ -682,10 +689,17 @@ add_res(char *size, char *duration, char *bitrate, char *sampleFrequency, } static int -get_child_count(const char *object) +get_child_count(const char *object, struct magic_container_s *magic) { int ret; - ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%s';", object); + + if (magic && magic->child_count) + ret = sql_get_int_field(db, "SELECT count(*) from %s", magic->child_count); + else if (magic && magic->objectid && *(magic->objectid)) + ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%s';", *(magic->objectid)); + else + ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%s';", object); + return (ret > 0) ? ret : 0; } @@ -698,11 +712,11 @@ object_exists(const char *object) return (ret > 0); } -#define COLUMNS "o.REF_ID, 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.ALBUM, d.GENRE, d.COMMENT, d.CHANNELS, d.TRACK, d.DATE, d.RESOLUTION," \ " d.THUMBNAIL, d.CREATOR, d.DLNA_PN, d.MIME, d.ALBUM_ART, d.DISC " -#define SELECT_COLUMNS "SELECT o.OBJECT_ID, o.PARENT_ID, " COLUMNS +#define SELECT_COLUMNS "SELECT o.OBJECT_ID, o.PARENT_ID, o.REF_ID, " COLUMNS static int callback(void *args, int argc, char **argv, char **azColName) @@ -747,9 +761,6 @@ callback(void *args, int argc, char **argv, char **azColName) } passed_args->returned++; - if( runtime_vars.root_container && strcmp(parent, runtime_vars.root_container) == 0 ) - parent = "0"; - if( strncmp(class, "item", 4) == 0 ) { uint32_t dlna_flags = DLNA_FLAG_DLNA_V1_5|DLNA_FLAG_HTTP_STALLING|DLNA_FLAG_TM_B; @@ -1051,10 +1062,10 @@ callback(void *args, int argc, char **argv, char **azColName) { ret = strcatf(str, "<container id=\"%s\" parentID=\"%s\" restricted=\"1\" ", id, parent); if( passed_args->filter & FILTER_SEARCHABLE ) { - ret = strcatf(str, "searchable=\"1\" "); + ret = strcatf(str, "searchable=\"%d\" ", check_magic_container(id, passed_args->flags) ? 0 : 1); } if( passed_args->filter & FILTER_CHILDCOUNT ) { - ret = strcatf(str, "childCount=\"%d\"", get_child_count(id)); + ret = strcatf(str, "childCount=\"%d\"", get_child_count(id, check_magic_container(id, passed_args->flags))); } /* If the client calls for BrowseMetadata on root, we have to include our "upnp:searchClass"'s, unless they're filtered out */ if( passed_args->requested == 1 && strcmp(id, "0") == 0 && (passed_args->filter & FILTER_UPNP_SEARCHCLASS) ) { @@ -1108,29 +1119,6 @@ callback(void *args, int argc, char **argv, char **azColName) return 0; } -static int -translate_object_id(char **id, struct Response *args) -{ - if (args->flags & FLAG_MS_PFS) - { - if( !strchr(*id, '$') && (strcmp(*id, "0") != 0) ) - { - char *s; - s = sql_get_text_field(db, "SELECT OBJECT_ID from OBJECTS" - " where OBJECT_ID in " - "('"MUSIC_ID"$%q', '"VIDEO_ID"$%q', '"IMAGE_ID"$%q')", - *id, *id, *id); - if (s) - { - *id = s; - args->flags |= FLAG_FREE_OBJECT_ID; - } - } - } - - return 0; -} - static void BrowseContentDirectory(struct upnphttp * h, const char * action) { @@ -1140,13 +1128,19 @@ BrowseContentDirectory(struct upnphttp * h, const char * action) "" "<DIDL-Lite" CONTENT_DIRECTORY_SCHEMAS; + struct magic_container_s *magic; char *zErrMsg = NULL; char *sql, *ptr; struct Response args; struct string_s str; int totalMatches = 0; int ret; - char *ObjectID, *Filter, *BrowseFlag, *SortCriteria; + const char *ObjectID, *BrowseFlag; + char *Filter, *SortCriteria; + const char *objectid_sql = "o.OBJECT_ID"; + const char *parentid_sql = "o.PARENT_ID"; + const char *refid_sql = "o.REF_ID"; + char where[256] = ""; char *orderBy = NULL; struct NameValueParserData data; int RequestedCount = 0; @@ -1206,7 +1200,6 @@ BrowseContentDirectory(struct upnphttp * h, const char * action) args.client = h->req_client ? h->req_client->type->type : 0; args.flags = h->req_client ? h->req_client->type->flags : 0; args.str = &str; - translate_object_id(&ObjectID, &args); DPRINTF(E_DEBUG, L_HTTP, "Browsing ContentDirectory:\n" " * ObjectID: %s\n" " * Count: %d\n" @@ -1217,44 +1210,59 @@ BrowseContentDirectory(struct upnphttp * h, const char * action) ObjectID, RequestedCount, StartingIndex, BrowseFlag, Filter, SortCriteria); - if( strcmp(ObjectID, "0") == 0 ) - { - args.flags |= FLAG_ROOT_CONTAINER; - if( runtime_vars.root_container ) - { - if( (args.flags & FLAG_AUDIO_ONLY) && (strcmp(runtime_vars.root_container, BROWSEDIR_ID) == 0) ) - ObjectID = MUSIC_DIR_ID; - else - ObjectID = runtime_vars.root_container; - } - else - { - if( args.flags & FLAG_AUDIO_ONLY ) - ObjectID = MUSIC_ID; - } - } - if( strcmp(BrowseFlag+6, "Metadata") == 0 ) { + const char *id = ObjectID; args.requested = 1; - sql = sqlite3_mprintf("SELECT %s, " COLUMNS - "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)" - " where OBJECT_ID = '%q';", - (args.flags & FLAG_ROOT_CONTAINER) ? "0, -1" : "o.OBJECT_ID, o.PARENT_ID", - ObjectID); + magic = in_magic_container(ObjectID, args.flags, &id); + if (magic) + { + if (magic->objectid_sql && strcmp(id, ObjectID) != 0) + objectid_sql = magic->objectid_sql; + if (magic->parentid_sql && strcmp(id, ObjectID) != 0) + parentid_sql = magic->parentid_sql; + if (magic->refid_sql) + refid_sql = magic->refid_sql; + } + sql = sqlite3_mprintf("SELECT %s, %s, %s, " COLUMNS + "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)" + " where OBJECT_ID = '%q';", + objectid_sql, parentid_sql, refid_sql, id); ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg); totalMatches = args.returned; } else { + magic = check_magic_container(ObjectID, args.flags); + if (magic) + { + if (magic->objectid && *(magic->objectid)) + ObjectID = *(magic->objectid); + if (magic->objectid_sql) + objectid_sql = magic->objectid_sql; + if (magic->parentid_sql) + parentid_sql = magic->parentid_sql; + if (magic->refid_sql) + refid_sql = magic->refid_sql; + if (magic->where) + strncpyt(where, magic->where, sizeof(where)); + if (magic->max_count > 0) + { + ret = get_child_count(ObjectID, magic); + totalMatches = ret > magic->max_count ? magic->max_count : ret; + if (RequestedCount > magic->max_count || RequestedCount < 0) + RequestedCount = magic->max_count; + } + } + if (!where[0]) + sqlite3_snprintf(sizeof(where), where, "PARENT_ID = '%q'", ObjectID); + if (!totalMatches) - totalMatches = get_child_count(ObjectID); + totalMatches = get_child_count(ObjectID, magic); ret = 0; if( SortCriteria ) { -#ifdef __sparc__ /* Sorting takes too long on slow processors with very large containers */ - if( totalMatches < 10000 ) -#endif + __SORT_LIMIT orderBy = parse_sort_criteria(SortCriteria, &ret); } else if (!orderBy) @@ -1268,9 +1276,7 @@ BrowseContentDirectory(struct upnphttp * h, const char * action) } else if( args.flags & FLAG_FORCE_SORT ) { -#ifdef __sparc__ - if( totalMatches < 10000 ) -#endif + __SORT_LIMIT ret = xasprintf(&orderBy, "order by o.CLASS, d.DISC, d.TRACK, d.TITLE"); } else @@ -1289,10 +1295,11 @@ BrowseContentDirectory(struct upnphttp * h, const char * action) goto browse_error; } - sql = sqlite3_mprintf( SELECT_COLUMNS + sql = sqlite3_mprintf("SELECT %s, %s, %s, " COLUMNS "from OBJECTS o left join DETAILS d on (d.ID = o.DETAIL_ID)" - " where PARENT_ID = '%q' %s limit %d, %d;", - ObjectID, THISORNUL(orderBy), StartingIndex, RequestedCount); + " where %s %s limit %d, %d;", + objectid_sql, parentid_sql, refid_sql, + where, THISORNUL(orderBy), StartingIndex, RequestedCount); DPRINTF(E_DEBUG, L_HTTP, "Browse SQL: %s\n", sql); ret = sqlite3_exec(db, sql, callback, (void *) &args, &zErrMsg); } @@ -1322,8 +1329,6 @@ BrowseContentDirectory(struct upnphttp * h, const char * action) BuildSendAndCloseSoapResp(h, str.data, str.off); browse_error: ClearNameValueList(&data); - if( args.flags & FLAG_FREE_OBJECT_ID ) - sqlite3_free(ObjectID); free(orderBy); free(str.data); } @@ -1608,13 +1613,15 @@ SearchContentDirectory(struct upnphttp * h, const char * action) "" "<DIDL-Lite" CONTENT_DIRECTORY_SCHEMAS; + struct magic_container_s *magic; char *zErrMsg = NULL; char *sql, *ptr; struct Response args; struct string_s str; int totalMatches; int ret; - char *ContainerID, *Filter, *SearchCriteria, *SortCriteria; + const char *ContainerID; + char *Filter, *SearchCriteria, *SortCriteria; char *orderBy = NULL, *where = NULL, sep[] = "$*"; char groupBy[] = "group by DETAIL_ID"; struct NameValueParserData data; @@ -1663,7 +1670,6 @@ SearchContentDirectory(struct upnphttp * h, const char * action) args.client = h->req_client ? h->req_client->type->type : 0; args.flags = h->req_client ? h->req_client->type->flags : 0; args.str = &str; - translate_object_id(&ContainerID, &args); DPRINTF(E_DEBUG, L_HTTP, "Searching ContentDirectory:\n" " * ObjectID: %s\n" " * Count: %d\n" @@ -1674,8 +1680,12 @@ SearchContentDirectory(struct upnphttp * h, const char * action) ContainerID, RequestedCount, StartingIndex, SearchCriteria, Filter, SortCriteria); + magic = check_magic_container(ContainerID, args.flags); + if (magic && magic->objectid && *(magic->objectid)) + ContainerID = *(magic->objectid); + if( strcmp(ContainerID, "0") == 0 ) - ContainerID[0] = '*'; + ContainerID = "*"; if( strcmp(ContainerID, MUSIC_ALL_ID) == 0 || GETFLAG(DLNA_STRICT_MASK) ) @@ -1706,11 +1716,9 @@ SearchContentDirectory(struct upnphttp * h, const char * action) goto search_error; } } -#ifdef __sparc__ /* Sorting takes too long on slow processors with very large containers */ ret = 0; - if( totalMatches < 10000 ) -#endif - orderBy = parse_sort_criteria(SortCriteria, &ret); + __SORT_LIMIT + orderBy = parse_sort_criteria(SortCriteria, &ret); /* If it's a DLNA client, return an error for bad sort criteria */ if( ret < 0 && ((args.flags & FLAG_DLNA) || GETFLAG(DLNA_STRICT_MASK)) ) { @@ -1746,8 +1754,6 @@ SearchContentDirectory(struct upnphttp * h, const char * action) BuildSendAndCloseSoapResp(h, str.data, str.off); search_error: ClearNameValueList(&data); - if( args.flags & FLAG_FREE_OBJECT_ID ) - sqlite3_free(ContainerID); free(orderBy); free(where); free(str.data);