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/NEWS b/NEWS index 565321d..72a3093 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,21 @@ +1.1.3 - Released 05-June-2014 +-------------------------------- +- Enhance log level settings. +- Fix Samsung browsing when root_container is set. +- Add Clang compiling support. +- Fix compiling on systems without iconv. +- Add merge_media_dirs option, to revert to the old behavior. +- Add Asus O!Play client support. +- Fix Broken SSDP multicast membership addition. +- Fix crash bug with an emtpy filter argument. +- Accept SMI subtitles in addition to SRT. +- Add BubbleUPnP detection and enable subtitle support. +- Allow the user to specify an arbitrary root container. +- Add libavcodec > 54 / libav 10 compatibility. +- Get embedded cover art from video files with recent libavformat versions. +- Disable Samsung DCM10 capability, as it breaks compatibility with new models. +- Add subtitle support for NetFront™ Living Connect middleware-based clients. + 1.1.2 - Released 06-Mar-2014 -------------------------------- - Show client status on our basic presentation page. diff --git a/clients.c b/clients.c index 78d6453..8d82ba2 100644 --- a/clients.c +++ b/clients.c @@ -58,7 +58,7 @@ struct client_type_s client_types[] = /* User-Agent: DLNADOC/1.50 SEC_HHP_BD-D5100/1.0 */ { ESamsungSeriesCDEBDP, FLAG_SAMSUNG | FLAG_DLNA | FLAG_NO_RESIZE, - "Samsung Series [CDE] BDP", + "Samsung Series [CDEF] BDP", "SEC_HHP_BD", EUserAgent }, @@ -66,8 +66,8 @@ struct client_type_s client_types[] = /* User-Agent: DLNADOC/1.50 SEC_HHP_[TV]UE40D7000/1.0 */ /* User-Agent: DLNADOC/1.50 SEC_HHP_ Family TV/1.0 */ { ESamsungSeriesCDE, - FLAG_SAMSUNG | FLAG_DLNA | FLAG_NO_RESIZE | FLAG_SAMSUNG_DCM10, - "Samsung Series [CDE]", + FLAG_SAMSUNG | FLAG_DLNA | FLAG_NO_RESIZE, + "Samsung Series [CDEF]", "SEC_HHP_", EUserAgent }, @@ -94,6 +94,14 @@ struct client_type_s client_types[] = EUserAgent }, + /* User-Agent: IPI/1.0 UPnP/1.0 DLNADOC/1.50 */ + { ENetFrontLivingConnect, + FLAG_DLNA | FLAG_FORCE_SORT | FLAG_CAPTION_RES, + "NetFront Living Connect", + "IPI/1", + EUserAgent + }, + { EDenonReceiver, FLAG_DLNA, "Denon Receiver", @@ -232,7 +240,7 @@ struct client_type_s client_types[] = struct client_cache_s clients[CLIENT_CACHE_SLOTS]; -int +struct client_cache_s * SearchClientCache(struct in_addr addr, int quiet) { int i; @@ -255,20 +263,20 @@ SearchClientCache(struct in_addr addr, int quiet) else { memset(&clients[i], 0, sizeof(struct client_cache_s)); - return -1; + return NULL; } } if (!quiet) DPRINTF(E_DEBUG, L_HTTP, "Client found in cache. [%s/entry %d]\n", - client_types[clients[i].type].name, i); - return i; + clients[i].type->name, i); + return &clients[i]; } } - return -1; + return NULL; } -int +struct client_cache_s * AddClientCache(struct in_addr addr, int type) { int i; @@ -279,15 +287,15 @@ AddClientCache(struct in_addr addr, int type) continue; get_remote_mac(addr, clients[i].mac); clients[i].addr = addr; - clients[i].type = type; + clients[i].type = &client_types[type]; clients[i].age = time(NULL); DPRINTF(E_DEBUG, L_HTTP, "Added client [%s/%s/%02X:%02X:%02X:%02X:%02X:%02X] to cache slot %d.\n", client_types[type].name, inet_ntoa(clients[i].addr), clients[i].mac[0], clients[i].mac[1], clients[i].mac[2], clients[i].mac[3], clients[i].mac[4], clients[i].mac[5], i); - return 0; + return &clients[i]; } - return -1; + return NULL; } diff --git a/clients.h b/clients.h index a6966f4..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, @@ -75,6 +73,7 @@ enum client_types { EToshibaTV, EAsusOPlay, EBubbleUPnP, + ENetFrontLivingConnect, EStandardDLNA150, EStandardUPnP }; @@ -90,14 +89,15 @@ struct client_type_s { struct client_cache_s { struct in_addr addr; unsigned char mac[6]; - enum client_types type; + struct client_type_s *type; time_t age; + int connections; }; extern struct client_type_s client_types[]; extern struct client_cache_s clients[CLIENT_CACHE_SLOTS]; -int SearchClientCache(struct in_addr addr, int quiet); -int AddClientCache(struct in_addr addr, int type); +struct client_cache_s *SearchClientCache(struct in_addr addr, int quiet); +struct client_cache_s *AddClientCache(struct in_addr addr, int type); #endif 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/libav.h b/libav.h index 4ef7f18..01da752 100644 --- a/libav.h +++ b/libav.h @@ -78,6 +78,10 @@ #define AVMEDIA_TYPE_VIDEO CODEC_TYPE_VIDEO #endif +#if LIBAVCODEC_VERSION_INT <= ((51<<16)+(50<<8)+1) +#define CODEC_ID_WMAPRO CODEC_ID_NONE +#endif + #if LIBAVCODEC_VERSION_MAJOR < 55 #define AV_CODEC_ID_AAC CODEC_ID_AAC #define AV_CODEC_ID_AC3 CODEC_ID_AC3 @@ -103,3 +107,76 @@ #define av_strerror(x, y, z) snprintf(y, z, "%d", x) #endif +#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0) +# if LIBAVUTIL_VERSION_INT < ((51<<16)+(5<<8)+0) && !defined(FF_API_OLD_METADATA2) +#define AV_DICT_IGNORE_SUFFIX AV_METADATA_IGNORE_SUFFIX +#define av_dict_get av_metadata_get +typedef AVMetadataTag AVDictionaryEntry; +# endif +#endif + +static inline int +lav_open(AVFormatContext **ctx, const char *filename) +{ + int ret; +#if LIBAVFORMAT_VERSION_INT >= ((53<<16)+(17<<8)+0) + ret = avformat_open_input(ctx, filename, NULL, NULL); + if (ret == 0) + avformat_find_stream_info(*ctx, NULL); +#else + ret = av_open_input_file(ctx, filename, NULL, 0, NULL); + if (ret == 0) + av_find_stream_info(*ctx); +#endif + return ret; +} + +static inline void +lav_close(AVFormatContext *ctx) +{ +#if LIBAVFORMAT_VERSION_INT >= ((53<<16)+(17<<8)+0) + avformat_close_input(&ctx); +#else + av_close_input_file(ctx); +#endif +} + +static inline int +lav_get_fps(AVStream *s) +{ +#if LIBAVCODEC_VERSION_MAJOR < 54 + if (s->r_frame_rate.den) + return s->r_frame_rate.num / s->r_frame_rate.den; +#else + if (s->avg_frame_rate.den) + return s->avg_frame_rate.num / s->avg_frame_rate.den; +#endif + return 0; +} + +static inline int +lav_get_interlaced(AVCodecContext *vc, AVStream *s) +{ +#if LIBAVCODEC_VERSION_MAJOR < 54 + return (vc->time_base.den ? (s->r_frame_rate.num / vc->time_base.den) : 0); +#else + return (vc->time_base.den ? (s->avg_frame_rate.num / vc->time_base.den) : 0); +#endif +} + +static inline int +lav_is_thumbnail_stream(AVStream *s, uint8_t **data, int *size) +{ +#if LIBAVFORMAT_VERSION_INT >= ((54<<16)+(6<<8)) + if (s->disposition & AV_DISPOSITION_ATTACHED_PIC && + s->codec->codec_id == AV_CODEC_ID_MJPEG) + { + if (data) + *data = s->attached_pic.data; + if (size) + *size = s->attached_pic.size; + return 1; + } +#endif + return 0; +} diff --git a/metadata.c b/metadata.c index 2ed36f7..f3987c6 100644 --- a/metadata.c +++ b/metadata.c @@ -55,11 +55,6 @@ #define FLAG_MIME 0x00000100 #define FLAG_DURATION 0x00000200 #define FLAG_RESOLUTION 0x00000400 -#define FLAG_BITRATE 0x00000800 -#define FLAG_FREQUENCY 0x00001000 -#define FLAG_BPS 0x00002000 -#define FLAG_CHANNELS 0x00004000 -#define FLAG_ROTATION 0x00008000 /* Audio profile flags */ enum audio_profiles { @@ -76,40 +71,6 @@ enum audio_profiles { PROFILE_AUDIO_AMR }; -static inline int -lav_open(AVFormatContext **ctx, const char *filename) -{ - int ret; -#if LIBAVFORMAT_VERSION_INT >= ((53<<16)+(17<<8)+0) - ret = avformat_open_input(ctx, filename, NULL, NULL); - if (ret == 0) - avformat_find_stream_info(*ctx, NULL); -#else - ret = av_open_input_file(ctx, filename, NULL, 0, NULL); - if (ret == 0) - av_find_stream_info(*ctx); -#endif - return ret; -} - -static inline void -lav_close(AVFormatContext *ctx) -{ -#if LIBAVFORMAT_VERSION_INT >= ((53<<16)+(17<<8)+0) - avformat_close_input(&ctx); -#else - av_close_input_file(ctx); -#endif -} - -#if LIBAVFORMAT_VERSION_INT >= ((52<<16)+(31<<8)+0) -# if LIBAVUTIL_VERSION_INT < ((51<<16)+(5<<8)+0) && !defined(FF_API_OLD_METADATA2) -#define AV_DICT_IGNORE_SUFFIX AV_METADATA_IGNORE_SUFFIX -#define av_dict_get av_metadata_get -typedef AVMetadataTag AVDictionaryEntry; -# endif -#endif - /* This function shamelessly copied from libdlna */ #define MPEG_TS_SYNC_CODE 0x47 #define MPEG_TS_PACKET_LENGTH 188 @@ -296,16 +257,6 @@ free_metadata(metadata_t *m, uint32_t flags) free(m->duration); if( flags & FLAG_RESOLUTION ) free(m->resolution); - if( flags & FLAG_BITRATE ) - free(m->bitrate); - if( flags & FLAG_FREQUENCY ) - free(m->frequency); - if( flags & FLAG_BPS ) - free(m->bps); - if( flags & FLAG_CHANNELS ) - free(m->channels); - if( flags & FLAG_ROTATION ) - free(m->rotation); } int64_t @@ -502,10 +453,10 @@ GetAudioMetadata(const char *path, char *name) " (PATH, SIZE, TIMESTAMP, DURATION, CHANNELS, BITRATE, SAMPLERATE, DATE," " TITLE, CREATOR, ARTIST, ALBUM, GENRE, COMMENT, DISC, TRACK, DLNA_PN, MIME, ALBUM_ART) " "VALUES" - " (%Q, %lld, %ld, '%s', %d, %d, %d, %Q, %Q, %Q, %Q, %Q, %Q, %Q, %d, %d, %Q, '%s', %lld);", - path, (long long)file.st_size, file.st_mtime, m.duration, song.channels, song.bitrate, song.samplerate, m.date, - m.title, m.creator, m.artist, m.album, m.genre, m.comment, song.disc, song.track, - m.dlna_pn, song.mime?song.mime:m.mime, album_art); + " (%Q, %lld, %lld, '%s', %d, %d, %d, %Q, %Q, %Q, %Q, %Q, %Q, %Q, %d, %d, %Q, '%s', %lld);", + path, (long long)file.st_size, (long long)file.st_mtime, m.duration, song.channels, song.bitrate, + song.samplerate, m.date, m.title, m.creator, m.artist, m.album, m.genre, m.comment, song.disc, + song.track, m.dlna_pn, song.mime?song.mime:m.mime, album_art); if( ret != SQLITE_OK ) { DPRINTF(E_ERROR, L_METADATA, "Error inserting details for '%s'!\n", path); @@ -605,24 +556,21 @@ GetImageMetadata(const char *path, char *name) e = exif_content_get_entry(ed->ifd[EXIF_IFD_0], EXIF_TAG_ORIENTATION); if( e ) { - int rotate; switch( exif_get_short(e->data, exif_data_get_byte_order(ed)) ) { case 3: - rotate = 180; + m.rotation = 180; break; case 6: - rotate = 90; + m.rotation = 90; break; case 8: - rotate = 270; + m.rotation = 270; break; default: - rotate = 0; + m.rotation = 0; break; } - if( rotate ) - xasprintf(&m.rotation, "%d", rotate); } if( ed->size ) @@ -686,9 +634,9 @@ no_exifdata: " (PATH, TITLE, SIZE, TIMESTAMP, DATE, RESOLUTION," " ROTATION, THUMBNAIL, CREATOR, DLNA_PN, MIME) " "VALUES" - " (%Q, '%q', %lld, %ld, %Q, %Q, %Q, %d, %Q, %Q, %Q);", - path, name, (long long)file.st_size, file.st_mtime, m.date, m.resolution, - m.rotation, thumb, m.creator, m.dlna_pn, m.mime); + " (%Q, '%q', %lld, %lld, %Q, %Q, %u, %d, %Q, %Q, %Q);", + path, name, (long long)file.st_size, (long long)file.st_mtime, m.date, + m.resolution, m.rotation, thumb, m.creator, m.dlna_pn, m.mime); if( ret != SQLITE_OK ) { DPRINTF(E_ERROR, L_METADATA, "Error inserting details for '%s'!\n", path); @@ -749,6 +697,7 @@ GetVideoMetadata(const char *path, char *name) continue; } else if( video_stream == -1 && + !lav_is_thumbnail_stream(ctx->streams[i], &m.thumb_data, &m.thumb_size) && ctx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO ) { video_stream = i; @@ -830,11 +779,9 @@ GetVideoMetadata(const char *path, char *name) else if ( ac->bit_rate <= 385000 ) audio_profile = PROFILE_AUDIO_WMA_FULL; break; - #if LIBAVCODEC_VERSION_INT > ((51<<16)+(50<<8)+1) case AV_CODEC_ID_WMAPRO: audio_profile = PROFILE_AUDIO_WMA_PRO; break; - #endif case AV_CODEC_ID_MP2: audio_profile = PROFILE_AUDIO_MP2; break; @@ -849,13 +796,8 @@ GetVideoMetadata(const char *path, char *name) DPRINTF(E_DEBUG, L_METADATA, "Unhandled audio codec [0x%X]\n", ac->codec_id); break; } - xasprintf(&m.frequency, "%u", ac->sample_rate); - #if LIBAVCODEC_VERSION_INT < (52<<16) - xasprintf(&m.bps, "%u", ac->bits_per_sample); - #else - xasprintf(&m.bps, "%u", ac->bits_per_coded_sample); - #endif - xasprintf(&m.channels, "%u", ac->channels); + m.frequency = ac->sample_rate; + m.channels = ac->channels; } if( vc ) { @@ -865,7 +807,7 @@ GetVideoMetadata(const char *path, char *name) DPRINTF(E_DEBUG, L_METADATA, "Container: '%s' [%s]\n", ctx->iformat->name, basepath); xasprintf(&m.resolution, "%dx%d", vc->width, vc->height); if( ctx->bit_rate > 8 ) - xasprintf(&m.bitrate, "%u", ctx->bit_rate / 8); + m.bitrate = ctx->bit_rate / 8; if( ctx->duration > 0 ) { duration = (int)(ctx->duration / AV_TIME_BASE); hours = (int)(duration / 3600); @@ -1006,11 +948,8 @@ GetVideoMetadata(const char *path, char *name) vc->height * vc->sample_aspect_ratio.den, 1024*1024); } - if (ctx->streams[video_stream]->r_frame_rate.den) - fps = ctx->streams[video_stream]->r_frame_rate.num / ctx->streams[video_stream]->r_frame_rate.den; - else - fps = 0; - interlaced = vc->time_base.den ? (ctx->streams[video_stream]->r_frame_rate.num / vc->time_base.den) : 0; + fps = lav_get_fps(ctx->streams[video_stream]); + interlaced = lav_get_interlaced(vc, ctx->streams[video_stream]); if( ((((vc->width == 1920 || vc->width == 1440) && vc->height == 1080) || (vc->width == 720 && vc->height == 480)) && fps == 59 && interlaced) || ((vc->width == 1280 && vc->height == 720) && fps == 59 && !interlaced) ) @@ -1504,6 +1443,11 @@ GetVideoMetadata(const char *path, char *name) m.creator = m.artist; free_flags &= ~FLAG_CREATOR; } + if (!m.thumb_data) + { + m.thumb_data = video.image; + m.thumb_size = video.image_size; + } } } #ifndef NETGEAR @@ -1577,7 +1521,6 @@ video_no_dlna: else DPRINTF(E_WARN, L_METADATA, "%s: Unhandled format: %s\n", path, ctx->iformat->name); } - lav_close(ctx); if( !m.date ) { @@ -1589,15 +1532,16 @@ video_no_dlna: if( !m.title ) m.title = strdup(name); - album_art = find_album_art(path, video.image, video.image_size); + album_art = find_album_art(path, m.thumb_data, m.thumb_size); freetags(&video); + lav_close(ctx); ret = sql_exec(db, "INSERT into DETAILS" " (PATH, SIZE, TIMESTAMP, DURATION, DATE, CHANNELS, BITRATE, SAMPLERATE, RESOLUTION," " TITLE, CREATOR, ARTIST, GENRE, COMMENT, DLNA_PN, MIME, ALBUM_ART) " "VALUES" - " (%Q, %lld, %ld, %Q, %Q, %Q, %Q, %Q, %Q, '%q', %Q, %Q, %Q, %Q, %Q, '%q', %lld);", - path, (long long)file.st_size, file.st_mtime, m.duration, + " (%Q, %lld, %lld, %Q, %Q, %u, %u, %u, %Q, '%q', %Q, %Q, %Q, %Q, %Q, '%q', %lld);", + path, (long long)file.st_size, (long long)file.st_mtime, m.duration, m.date, m.channels, m.bitrate, m.frequency, m.resolution, m.title, m.creator, m.artist, m.genre, m.comment, m.dlna_pn, m.mime, album_art); diff --git a/metadata.h b/metadata.h index 96364dc..a7ceee6 100644 --- a/metadata.h +++ b/metadata.h @@ -25,22 +25,25 @@ #define __METADATA_H__ typedef struct metadata_s { - char *title; - char *artist; - char *creator; - char *album; - char *genre; - char *comment; - char *channels; - char *bitrate; - char *frequency; - char *bps; - char *resolution; - char *rotation; - char *duration; - char *date; - char *mime; - char *dlna_pn; + char * title; + char * artist; + char * creator; + char * album; + char * genre; + char * comment; + unsigned int disc; + unsigned int track; + unsigned int channels; + unsigned int bitrate; + unsigned int frequency; + unsigned int rotation; + char * resolution; + char * duration; + char * date; + char * mime; + char * dlna_pn; + int thumb_size; + uint8_t * thumb_data; } metadata_t; typedef enum { diff --git a/minidlna.c b/minidlna.c index c8eed5b..be34fe3 100644 --- a/minidlna.c +++ b/minidlna.c @@ -372,7 +372,7 @@ rescan: #if USE_FORK scanning = 1; sqlite3_close(db); - *scanner_pid = process_fork(); + *scanner_pid = fork(); open_db(&db); if (*scanner_pid == 0) /* child (scanner) process */ { @@ -464,6 +464,16 @@ static int strtobool(const char *str) (atoi(str) == 1)); } +static void init_nls(void) +{ +#ifdef ENABLE_NLS + setlocale(LC_MESSAGES, ""); + setlocale(LC_CTYPE, "en_US.utf8"); + DPRINTF(E_DEBUG, L_GENERAL, "Using locale dir %s\n", bindtextdomain("minidlna", getenv("TEXTDOMAINDIR"))); + textdomain("minidlna"); +#endif +} + /* init phase : * 1) read configuration file * 2) read command line arguments @@ -969,6 +979,13 @@ init(int argc, char **argv) DPRINTF(E_FATAL, L_GENERAL, "Failed to switch to uid '%d'. [%s] EXITING.\n", uid, strerror(errno)); + children = calloc(runtime_vars.max_connections, sizeof(struct child)); + if (!children) + { + DPRINTF(E_ERROR, L_GENERAL, "Allocation failed\n"); + return 1; + } + return 0; } @@ -1000,12 +1017,7 @@ main(int argc, char **argv) for (i = 0; i < L_MAX; i++) log_level[i] = E_WARN; -#ifdef ENABLE_NLS - setlocale(LC_MESSAGES, ""); - setlocale(LC_CTYPE, "en_US.utf8"); - DPRINTF(E_DEBUG, L_GENERAL, "Using locale dir %s\n", bindtextdomain("minidlna", getenv("TEXTDOMAINDIR"))); - textdomain("minidlna"); -#endif + init_nls(); ret = init(argc, argv); if (ret != 0) @@ -1182,11 +1194,6 @@ main(int argc, char **argv) i++; } } -#ifdef DEBUG - /* for debug */ - if (i > 1) - DPRINTF(E_DEBUG, L_GENERAL, "%d active incoming HTTP connections\n", i); -#endif FD_ZERO(&writeset); upnpevents_selectfds(&readset, &writeset, &max_fd); @@ -1285,7 +1292,11 @@ main(int argc, char **argv) shutdown: /* kill the scanner */ if (scanning && scanner_pid) - kill(scanner_pid, 9); + kill(scanner_pid, SIGKILL); + + /* kill other child processes */ + process_reap_children(); + free(children); /* close out open sockets */ while (upnphttphead.lh_first != NULL) @@ -1298,10 +1309,10 @@ shutdown: close(sssdp); if (shttpl >= 0) close(shttpl); - #ifdef TIVO_SUPPORT +#ifdef TIVO_SUPPORT if (sbeacon >= 0) close(sbeacon); - #endif +#endif for (i = 0; i < n_lan_addr; i++) { 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/minissdp.c b/minissdp.c index 0ab7ab5..ba9a861 100644 --- a/minissdp.c +++ b/minissdp.c @@ -319,7 +319,7 @@ ParseUPnPClient(char *location) char *off = NULL, *p; int content_len = sizeof(buf); struct NameValueParserData xml; - int client; + struct client_cache_s *client; int type = 0; char *model, *serial, *name; @@ -458,14 +458,14 @@ close: return; /* Add this client to the cache if it's not there already. */ client = SearchClientCache(dest.sin_addr, 1); - if (client < 0) + if (!client) { AddClientCache(dest.sin_addr, type); } else { - clients[client].type = type; - clients[client].age = time(NULL); + client->type = &client_types[type]; + client->age = time(NULL); } } @@ -546,13 +546,13 @@ ProcessSSDPRequest(int s, unsigned short port) (strstrc(srv, "DigiOn DiXiM", '\r') != NULL)) /* Marantz Receiver */ { /* Check if the client is already in cache */ - i = SearchClientCache(sendername.sin_addr, 1); - if (i >= 0) + struct client_cache_s *client = SearchClientCache(sendername.sin_addr, 1); + if (client) { - if (clients[i].type < EStandardDLNA150 && - clients[i].type != ESamsungSeriesA) + if (client->type->type < EStandardDLNA150 && + client->type->type != ESamsungSeriesA) { - clients[i].age = time(NULL); + client->age = time(NULL); return; } } diff --git a/process.c b/process.c index 00b802b..96ec1c3 100644 --- a/process.c +++ b/process.c @@ -43,10 +43,47 @@ #include "config.h" #include "log.h" -static int number_of_children = 0; +struct child *children = NULL; +int number_of_children = 0; + +static void +add_process_info(pid_t pid, struct client_cache_s *client) +{ + struct child *child; + int i; + + for (i = 0; i < runtime_vars.max_connections; i++) + { + child = children+i; + if (child->pid) + continue; + child->pid = pid; + child->client = client; + child->age = time(NULL); + break; + } +} + +static inline void +remove_process_info(pid_t pid) +{ + struct child *child; + int i; + + for (i = 0; i < runtime_vars.max_connections; i++) + { + child = children+i; + if (child->pid != pid) + continue; + child->pid = 0; + if (child->client) + child->client->connections--; + break; + } +} pid_t -process_fork(void) +process_fork(struct client_cache_s *client) { if (number_of_children >= runtime_vars.max_connections) { @@ -58,7 +95,13 @@ process_fork(void) pid_t pid = fork(); if (pid > 0) - ++number_of_children; + { + number_of_children++; + if (client) + client->connections++; + add_process_info(pid, client); + } + return pid; } @@ -76,7 +119,8 @@ process_handle_child_termination(int signal) else break; } - --number_of_children; + number_of_children--; + remove_process_info(pid); } } @@ -87,7 +131,7 @@ process_daemonize(void) #ifndef USE_DAEMON int i; - switch(process_fork()) + switch(fork()) { /* fork error */ case -1: @@ -157,3 +201,17 @@ process_check_if_running(const char *fname) return 0; } + +void +process_reap_children(void) +{ + struct child *child; + int i; + + for (i = 0; i < runtime_vars.max_connections; i++) + { + child = children+i; + if (child->pid) + kill(child->pid, SIGKILL); + } +} diff --git a/process.h b/process.h index 7fa7682..713c92c 100644 --- a/process.h +++ b/process.h @@ -30,6 +30,16 @@ #define __PROCESS_H__ #include +#include "clients.h" + +struct child { + pid_t pid; + time_t age; + struct client_cache_s *client; +}; + +extern struct child *children; +extern int number_of_children; /** * Fork a new child (just like fork()) but keep track of how many childs are @@ -37,7 +47,7 @@ * @return -1 if it couldn't fork, 0 in the child process, the pid of the * child process in the parent process. */ -pid_t process_fork(void); +pid_t process_fork(struct client_cache_s *client); /** * Handler to be called upon receiving SIGCHLD. This signal is received by the @@ -63,4 +73,9 @@ int process_daemonize(void); */ int process_check_if_running(const char *fname); +/** + * Kill all child processes + */ +void process_reap_children(void); + #endif // __PROCESS_H__ 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/tagutils/tagutils-aac.c b/tagutils/tagutils-aac.c index 16b1301..ed6fb55 100644 --- a/tagutils/tagutils-aac.c +++ b/tagutils/tagutils-aac.c @@ -107,7 +107,7 @@ _get_aactags(char *file, struct song_metadata *psong) if(fread(current_data, 1, current_size - 8, fin) != current_size - 8) break; - current_data[len-1] = '\0'; + current_data[current_size - 8] = '\0'; if(!memcmp(current_atom, "\xA9" "nam", 4)) psong->title = strdup((char*)¤t_data[16]); else if(!memcmp(current_atom, "\xA9" "ART", 4) || diff --git a/upnpglobalvars.h b/upnpglobalvars.h index 0ff9f2c..75d55e2 100644 --- a/upnpglobalvars.h +++ b/upnpglobalvars.h @@ -57,7 +57,7 @@ #include -#define MINIDLNA_VERSION "1.1.2" +#define MINIDLNA_VERSION "1.1.3" #ifdef NETGEAR # define SERVER_NAME "ReadyDLNA" diff --git a/upnphttp.c b/upnphttp.c index 5834b03..b6dd887 100644 --- a/upnphttp.c +++ b/upnphttp.c @@ -83,8 +83,7 @@ #include "sendfile.h" -//#define MAX_BUFFER_SIZE 4194304 // 4MB -- Too much? -#define MAX_BUFFER_SIZE 2147483647 // 2GB -- Too much? +#define MAX_BUFFER_SIZE 2147483647 #define MIN_BUFFER_SIZE 65536 #include "icons.c" @@ -137,6 +136,7 @@ Delete_upnphttp(struct upnphttp * h) static void ParseHttpHeaders(struct upnphttp * h) { + int client = 0; char * line; char * colon; char * p; @@ -274,7 +274,7 @@ ParseHttpHeaders(struct upnphttp * h) { int i; /* Skip client detection if we already detected it. */ - if( h->req_client ) + if( client ) goto next_header; p = colon + 1; while(isspace(*p)) @@ -285,7 +285,7 @@ ParseHttpHeaders(struct upnphttp * h) continue; if (strstrc(p, client_types[i].match, '\r') != NULL) { - h->req_client = i; + client = i; break; } } @@ -294,7 +294,7 @@ ParseHttpHeaders(struct upnphttp * h) { int i; /* Skip client detection if we already detected it. */ - if( h->req_client && client_types[h->req_client].type < EStandardDLNA150 ) + if( client && client_types[client].type < EStandardDLNA150 ) goto next_header; p = colon + 1; while(isspace(*p)) @@ -305,7 +305,7 @@ ParseHttpHeaders(struct upnphttp * h) continue; if (strstrc(p, client_types[i].match, '\r') != NULL) { - h->req_client = i; + client = i; break; } } @@ -386,7 +386,7 @@ ParseHttpHeaders(struct upnphttp * h) continue; if (strstrc(p, client_types[i].match, '\r') != NULL) { - h->req_client = i; + client = i; break; } } @@ -432,29 +432,24 @@ next_header: /* If the client type wasn't found, search the cache. * This is done because a lot of clients like to send a * different User-Agent with different types of requests. */ - n = SearchClientCache(h->clientaddr, 0); + h->req_client = SearchClientCache(h->clientaddr, 0); /* Add this client to the cache if it's not there already. */ - if( n < 0 ) + if (!h->req_client) { - AddClientCache(h->clientaddr, h->req_client); + h->req_client = AddClientCache(h->clientaddr, client); } - else if (h->req_client) + else if (client) { - enum client_types type = client_types[h->req_client].type; - enum client_types ctype = client_types[clients[n].type].type; + enum client_types type = client_types[client].type; + enum client_types ctype = h->req_client->type->type; /* If we know the client and our new detection is generic, use our cached info */ /* If we detected a Samsung Series B earlier, don't overwrite it with Series A info */ if ((ctype && ctype < EStandardDLNA150 && type >= EStandardDLNA150) || (ctype == ESamsungSeriesB && type == ESamsungSeriesA)) - { - h->req_client = clients[n].type; return; - } - clients[n].type = h->req_client; - clients[n].age = time(NULL); + h->req_client->type = &client_types[client]; + h->req_client->age = time(NULL); } - else - h->req_client = clients[n].type; } /* very minimalistic 400 error message */ @@ -619,17 +614,20 @@ SendResp_presentation(struct upnphttp * h) strcatf(&str, "

Connected clients

" "" - ""); + ""); for (i = 0; i < CLIENT_CACHE_SLOTS; i++) { if (!clients[i].addr.s_addr) continue; - strcatf(&str, "", - i, client_types[clients[i].type].name, inet_ntoa(clients[i].addr), + strcatf(&str, "", + i, clients[i].type->name, inet_ntoa(clients[i].addr), clients[i].mac[0], clients[i].mac[1], clients[i].mac[2], - clients[i].mac[3], clients[i].mac[4], clients[i].mac[5]); + clients[i].mac[3], clients[i].mac[4], clients[i].mac[5], clients[i].connections); } - strcatf(&str, "
IDTypeIP AddressHW Address
IDTypeIP AddressHW AddressConnections
%d%s%s%02X:%02X:%02X:%02X:%02X:%02X
%d%s%s%02X:%02X:%02X:%02X:%02X:%02X%d
\r\n"); + strcatf(&str, ""); + + strcatf(&str, "
%d connection%s currently open
", number_of_children, (number_of_children == 1 ? "" : "s")); + strcatf(&str, "\r\n"); BuildResp_upnphttp(h, str.data, str.off); SendResp_upnphttp(h); @@ -907,7 +905,7 @@ ProcessHttpQuery_upnphttp(struct upnphttp * h) if(strcmp(ROOTDESC_PATH, HttpUrl) == 0) { /* If it's a Xbox360, we might need a special friendly_name to be recognized */ - if( client_types[h->req_client].type == EXbox ) + if( h->req_client && h->req_client->type->type == EXbox ) { char model_sav[2]; i = 0; @@ -923,7 +921,7 @@ ProcessHttpQuery_upnphttp(struct upnphttp * h) friendly_name[i] = '\0'; memcpy(modelnumber, model_sav, 2); } - else if( client_types[h->req_client].flags & FLAG_SAMSUNG_DCM10 ) + else if( h->req_client && h->req_client->type->flags & FLAG_SAMSUNG_DCM10 ) { sendXMLdesc(h, genRootDescSamsung); } @@ -1637,7 +1635,7 @@ SendResp_resizedimg(struct upnphttp * h, char * object) #if USE_FORK pid_t newpid = 0; - newpid = process_fork(); + newpid = process_fork(h->req_client); if( newpid > 0 ) { CloseSocket_upnphttp(h); @@ -1800,7 +1798,8 @@ SendResp_dlnafile(struct upnphttp *h, char *object) int64_t id; int sendfh; uint32_t dlna_flags = DLNA_FLAG_DLNA_V1_5|DLNA_FLAG_HTTP_STALLING|DLNA_FLAG_TM_B; - uint32_t cflags = client_types[h->req_client].flags; + uint32_t cflags = h->req_client ? h->req_client->type->flags : 0; + enum client_types ctype = h->req_client ? h->req_client->type->type : 0; static struct { int64_t id; enum client_types client; char path[PATH_MAX]; @@ -1828,7 +1827,7 @@ SendResp_dlnafile(struct upnphttp *h, char *object) return; } } - if( id != last_file.id || h->req_client != last_file.client ) + if( id != last_file.id || ctype != last_file.client ) { snprintf(buf, sizeof(buf), "SELECT PATH, MIME, DLNA_PN from DETAILS where ID = '%lld'", (long long)id); ret = sql_get_table(db, buf, &result, &rows, NULL); @@ -1847,7 +1846,7 @@ SendResp_dlnafile(struct upnphttp *h, char *object) } /* Cache the result */ last_file.id = id; - last_file.client = h->req_client; + last_file.client = ctype; strncpy(last_file.path, result[3], sizeof(last_file.path)-1); if( result[4] ) { @@ -1860,12 +1859,11 @@ SendResp_dlnafile(struct upnphttp *h, char *object) /* Samsung TV's such as the A750 can natively support many Xvid/DivX AVI's however, the DLNA server needs the mime type to say video/mpeg */ - else if( h->req_client == ESamsungSeriesA && - strcmp(last_file.mime+6, "x-msvideo") == 0 ) + else if( ctype == ESamsungSeriesA && strcmp(last_file.mime+6, "x-msvideo") == 0 ) strcpy(last_file.mime+6, "mpeg"); } /* ... and Sony BDP-S370 won't play MKV unless we pretend it's a DiVX file */ - else if( h->req_client == ESonyBDP ) + else if( ctype == ESonyBDP ) { if( strcmp(last_file.mime+6, "x-matroska") == 0 || strcmp(last_file.mime+6, "mpeg") == 0 ) @@ -1879,7 +1877,7 @@ SendResp_dlnafile(struct upnphttp *h, char *object) sqlite3_free_table(result); } #if USE_FORK - newpid = process_fork(); + newpid = process_fork(h->req_client); if( newpid > 0 ) { CloseSocket_upnphttp(h); diff --git a/upnphttp.h b/upnphttp.h index 8de4499..04db13a 100644 --- a/upnphttp.h +++ b/upnphttp.h @@ -86,7 +86,7 @@ struct upnphttp { int req_contentlen; int req_contentoff; /* header length */ enum httpCommands req_command; - enum client_types req_client; + struct client_cache_s * req_client; const char * req_soapAction; int req_soapActionLen; const char * req_Callback; /* For SUBSCRIBE */ diff --git a/upnpsoap.c b/upnpsoap.c index 0b6ec56..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) @@ -349,7 +356,7 @@ set_filter_flags(char *filter, struct upnphttp *h) { char *item, *saveptr = NULL; uint32_t flags = 0; - int samsung = client_types[h->req_client].flags & FLAG_SAMSUNG; + int samsung = h->req_client && (h->req_client->type->flags & FLAG_SAMSUNG); if( !filter || (strlen(filter) <= 1) ) { /* Not the full 32 bits. Skip vendor-specific stuff by default. */ @@ -681,11 +688,35 @@ add_res(char *size, char *duration, char *bitrate, char *sampleFrequency, runtime_vars.port, detailID, ext); } -#define COLUMNS "o.REF_ID, o.DETAIL_ID, o.CLASS," \ +static int +get_child_count(const char *object, struct magic_container_s *magic) +{ + int ret; + + 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; +} + +static int +object_exists(const char *object) +{ + int ret; + ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where OBJECT_ID = '%q'", + strcmp(object, "*") == 0 ? "0" : object); + return (ret > 0); +} + +#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) @@ -730,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; @@ -1034,13 +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 ) { - int children; - ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%s';", id); - children = (ret > 0) ? ret : 0; - ret = strcatf(str, "childCount=\"%d\"", children); + 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) ) { @@ -1103,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; + 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; @@ -1166,24 +1197,9 @@ BrowseContentDirectory(struct upnphttp * h, const char * action) args.returned = 0; args.requested = RequestedCount; - args.client = client_types[h->req_client].type; - args.flags = client_types[h->req_client].flags; + 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; - if( args.flags & FLAG_MS_PFS ) - { - if( !strchr(ObjectID, '$') && (strcmp(ObjectID, "0") != 0) ) - { - ptr = sql_get_text_field(db, "SELECT OBJECT_ID from OBJECTS" - " where OBJECT_ID in " - "('"MUSIC_ID"$%q', '"VIDEO_ID"$%q', '"IMAGE_ID"$%q')", - ObjectID, ObjectID, ObjectID); - if( ptr ) - { - ObjectID = ptr; - args.flags |= FLAG_FREE_OBJECT_ID; - } - } - } DPRINTF(E_DEBUG, L_HTTP, "Browsing ContentDirectory:\n" " * ObjectID: %s\n" " * Count: %d\n" @@ -1194,47 +1210,62 @@ 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 { - ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where PARENT_ID = '%q'", ObjectID); - totalMatches = (ret > 0) ? ret : 0; + 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, 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 + else if (!orderBy) { if( strncmp(ObjectID, MUSIC_PLIST_ID, strlen(MUSIC_PLIST_ID)) == 0 ) { @@ -1245,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 @@ -1266,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); } @@ -1284,8 +1314,7 @@ BrowseContentDirectory(struct upnphttp * h, const char * action) /* Does the object even exist? */ if( !totalMatches ) { - ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where OBJECT_ID = '%q'", ObjectID); - if( ret <= 0 ) + if( !object_exists(ObjectID) ) { SoapError(h, 701, "No such object error"); goto browse_error; @@ -1300,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); } @@ -1586,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; @@ -1638,24 +1667,9 @@ SearchContentDirectory(struct upnphttp * h, const char * action) args.returned = 0; args.requested = RequestedCount; - args.client = client_types[h->req_client].type; - args.flags = client_types[h->req_client].flags; + 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; - if( args.flags & FLAG_MS_PFS ) - { - if( !strchr(ContainerID, '$') && (strcmp(ContainerID, "0") != 0) ) - { - ptr = sql_get_text_field(db, "SELECT OBJECT_ID from OBJECTS" - " where OBJECT_ID in " - "('"MUSIC_ID"$%q', '"VIDEO_ID"$%q', '"IMAGE_ID"$%q')", - ContainerID, ContainerID, ContainerID); - if( ptr ) - { - ContainerID = ptr; - args.flags |= FLAG_FREE_OBJECT_ID; - } - } - } DPRINTF(E_DEBUG, L_HTTP, "Searching ContentDirectory:\n" " * ObjectID: %s\n" " * Count: %d\n" @@ -1666,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) ) @@ -1692,19 +1710,15 @@ SearchContentDirectory(struct upnphttp * h, const char * action) /* Does the object even exist? */ if( !totalMatches ) { - ret = sql_get_int_field(db, "SELECT count(*) from OBJECTS where OBJECT_ID = '%q'", - !strcmp(ContainerID, "*")?"0":ContainerID); - if( ret <= 0 ) + if( !object_exists(ContainerID) ) { SoapError(h, 710, "No such container"); 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)) ) { @@ -1740,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);