diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 734b6a9394..ba1f73ff2b 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -1718,7 +1718,7 @@ namespace MediaBrowser.Api.Playback string mediaSourceId, CancellationToken cancellationToken) { - var channelMediaSources = await ChannelManager.GetChannelItemMediaSources(id, cancellationToken) + var channelMediaSources = await ChannelManager.GetChannelItemMediaSources(id, true, cancellationToken) .ConfigureAwait(false); var list = channelMediaSources.ToList(); diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs index f3df6c7b02..f4ea7ef94a 100644 --- a/MediaBrowser.Api/TvShowsService.cs +++ b/MediaBrowser.Api/TvShowsService.cs @@ -344,7 +344,7 @@ namespace MediaBrowser.Api // This must be the last filter if (!string.IsNullOrEmpty(request.AdjacentTo)) { - seasons = ItemsService.FilterForAdjacency(seasons, request.AdjacentTo) + seasons = UserViewBuilder.FilterForAdjacency(seasons, request.AdjacentTo) .Cast(); } @@ -434,7 +434,7 @@ namespace MediaBrowser.Api // This must be the last filter if (!string.IsNullOrEmpty(request.AdjacentTo)) { - episodes = ItemsService.FilterForAdjacency(episodes, request.AdjacentTo) + episodes = UserViewBuilder.FilterForAdjacency(episodes, request.AdjacentTo) .Cast(); } diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs index 15a2d5c337..458cf8bae3 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs @@ -188,7 +188,7 @@ namespace MediaBrowser.Api.UserLibrary var imageTypes = request.GetImageTypes().ToList(); if (imageTypes.Count > 0) { - items = items.Where(item => imageTypes.Any(imageType => ItemsService.HasImage(item, imageType))); + items = items.Where(item => imageTypes.Any(item.HasImage)); } var filters = request.GetFilters().ToList(); diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index 011864d35e..897a68a65c 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -243,6 +243,43 @@ namespace MediaBrowser.Api.UserLibrary [ApiMember(Name = "CollapseBoxSetItems", Description = "Whether or not to hide items behind their boxsets.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] public bool? CollapseBoxSetItems { get; set; } + + public string[] GetAllGenres() + { + return (AllGenres ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); + } + + public string[] GetGenres() + { + return (Genres ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); + } + + public string[] GetStudios() + { + return (Studios ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); + } + + public string[] GetPersonTypes() + { + return (PersonTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + } + + public int[] GetYears() + { + return (Years ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray(); + } + + public IEnumerable GetVideoTypes() + { + var val = VideoTypes; + + if (string.IsNullOrEmpty(val)) + { + return new VideoType[] { }; + } + + return val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(v => (VideoType)Enum.Parse(typeof(VideoType), v, true)); + } } /// @@ -331,21 +368,21 @@ namespace MediaBrowser.Api.UserLibrary items = ApplyFilter(items, filter, user, _userDataRepository); } - items = FilterVirtualEpisodes(request, items, user); + items = UserViewBuilder.FilterVirtualEpisodes(items, + request.IsMissing, + request.IsVirtualUnaired, + request.IsUnaired); - if (CollapseBoxSetItems(request, parentItem, user)) - { - items = _collectionManager.CollapseItemsWithinBoxSets(items, user); - } + var internalQuery = GetItemsQuery(request, user); - items = ApplyPostCollectionCollapseFilters(request, items, user); + items = UserViewBuilder.CollapseBoxSetItemsIfNeeded(items, internalQuery, parentItem, user); items = _libraryManager.Sort(items, user, request.GetOrderBy(), request.SortOrder ?? SortOrder.Ascending); // This must be the last filter if (!string.IsNullOrEmpty(request.AdjacentTo)) { - items = FilterForAdjacency(items, request.AdjacentTo); + items = UserViewBuilder.FilterForAdjacency(items, request.AdjacentTo); } var itemsArray = items.ToList(); @@ -363,33 +400,6 @@ namespace MediaBrowser.Api.UserLibrary }; } - private bool CollapseBoxSetItems(GetItems request, BaseItem parentItem, User user) - { - // Could end up stuck in a loop like this - if (parentItem is BoxSet) - { - return false; - } - - var param = request.CollapseBoxSetItems; - - if (!param.HasValue) - { - if (user != null && !user.Configuration.GroupMoviesIntoBoxSets) - { - return false; - } - - if (!string.IsNullOrWhiteSpace(request.IncludeItemTypes) && - request.IncludeItemTypes.Split(',').Contains("Movie", StringComparer.OrdinalIgnoreCase)) - { - param = true; - } - } - - return param.HasValue && param.Value && AllowBoxSetCollapsing(request); - } - /// /// Gets the items to serialize. /// @@ -486,9 +496,48 @@ namespace MediaBrowser.Api.UserLibrary Filter = (i, u) => ApplyAdditionalFilters(request, i, u, true), Limit = request.Limit, - StartIndex = request.StartIndex + StartIndex = request.StartIndex, + IsMissing = request.IsMissing, + IsVirtualUnaired = request.IsVirtualUnaired, + IsUnaired = request.IsUnaired, + CollapseBoxSetItems = request.CollapseBoxSetItems, + NameLessThan = request.NameLessThan, + NameStartsWith = request.NameStartsWith, + NameStartsWithOrGreater = request.NameStartsWithOrGreater, + HasImdbId = request.HasImdbId, + IsYearMismatched = request.IsYearMismatched, + IsUnidentified = request.IsUnidentified, + IsPlaceHolder = request.IsPlaceHolder, + IsLocked = request.IsLocked, + IsInBoxSet = request.IsInBoxSet, + IsHD = request.IsHD, + Is3D = request.Is3D, + HasTvdbId = request.HasTvdbId, + HasTmdbId = request.HasTmdbId, + HasOverview = request.HasOverview, + HasOfficialRating = request.HasOfficialRating, + HasParentalRating = request.HasParentalRating, + HasSpecialFeature = request.HasSpecialFeature, + HasSubtitles = request.HasSubtitles, + HasThemeSong = request.HasThemeSong, + HasThemeVideo = request.HasThemeVideo, + HasTrailer = request.HasTrailer, + Genres = request.GetGenres(), + AllGenres = request.GetAllGenres(), + Studios = request.GetStudios(), + Person = request.Person, + PersonTypes = request.GetPersonTypes(), + Years = request.GetYears(), + ImageTypes = request.GetImageTypes().ToArray(), + VideoTypes = request.GetVideoTypes().ToArray(), + AdjacentTo = request.AdjacentTo }; + if (!string.IsNullOrWhiteSpace(request.Ids)) + { + query.CollapseBoxSetItems = false; + } + foreach (var filter in request.GetFilters()) { switch (filter) @@ -610,121 +659,6 @@ namespace MediaBrowser.Api.UserLibrary return items; } - private IEnumerable FilterVirtualEpisodes(GetItems request, IEnumerable items, User user) - { - items = FilterVirtualSeasons(request, items, user); - - if (request.IsMissing.HasValue) - { - var val = request.IsMissing.Value; - items = items.Where(i => - { - var e = i as Episode; - if (e != null) - { - return e.IsMissingEpisode == val; - } - return true; - }); - } - - if (request.IsUnaired.HasValue) - { - var val = request.IsUnaired.Value; - items = items.Where(i => - { - var e = i as Episode; - if (e != null) - { - return e.IsUnaired == val; - } - return true; - }); - } - - if (request.IsVirtualUnaired.HasValue) - { - var val = request.IsVirtualUnaired.Value; - items = items.Where(i => - { - var e = i as Episode; - if (e != null) - { - return e.IsVirtualUnaired == val; - } - return true; - }); - } - - return items; - } - - private IEnumerable FilterVirtualSeasons(GetItems request, IEnumerable items, User user) - { - if (request.IsMissing.HasValue && request.IsVirtualUnaired.HasValue) - { - var isMissing = request.IsMissing.Value; - var isVirtualUnaired = request.IsVirtualUnaired.Value; - - if (!isMissing && !isVirtualUnaired) - { - return items.Where(i => - { - var e = i as Season; - if (e != null) - { - return !e.IsMissingOrVirtualUnaired; - } - return true; - }); - } - } - - if (request.IsMissing.HasValue) - { - var val = request.IsMissing.Value; - items = items.Where(i => - { - var e = i as Season; - if (e != null) - { - return e.IsMissingSeason == val; - } - return true; - }); - } - - if (request.IsUnaired.HasValue) - { - var val = request.IsUnaired.Value; - items = items.Where(i => - { - var e = i as Season; - if (e != null) - { - return e.IsUnaired == val; - } - return true; - }); - } - - if (request.IsVirtualUnaired.HasValue) - { - var val = request.IsVirtualUnaired.Value; - items = items.Where(i => - { - var e = i as Season; - if (e != null) - { - return e.IsVirtualUnaired == val; - } - return true; - }); - } - - return items; - } - private bool ApplyAdditionalFilters(GetItems request, BaseItem i, User user, bool isPreFiltered) { if (!isPreFiltered) @@ -760,130 +694,185 @@ namespace MediaBrowser.Api.UserLibrary { return false; } - } - if (request.MinCommunityRating.HasValue) - { - var val = request.MinCommunityRating.Value; + if (request.IsInBoxSet.HasValue) + { + var val = request.IsInBoxSet.Value; + if (i.Parents.OfType().Any() != val) + { + return false; + } + } - if (!(i.CommunityRating.HasValue && i.CommunityRating >= val)) + // Filter by Video3DFormat + if (request.Is3D.HasValue) { - return false; + var val = request.Is3D.Value; + var video = i as Video; + + if (video == null || val != video.Video3DFormat.HasValue) + { + return false; + } } - } - if (request.MinCriticRating.HasValue) - { - var val = request.MinCriticRating.Value; + if (request.IsHD.HasValue) + { + var val = request.IsHD.Value; + var video = i as Video; - var hasCriticRating = i as IHasCriticRating; + if (video == null || val != video.IsHD) + { + return false; + } + } - if (hasCriticRating != null) + if (request.IsUnidentified.HasValue) { - if (!(hasCriticRating.CriticRating.HasValue && hasCriticRating.CriticRating >= val)) + var val = request.IsUnidentified.Value; + if (i.IsUnidentified != val) { return false; } } - else + + if (request.IsLocked.HasValue) { - return false; + var val = request.IsLocked.Value; + if (i.IsLocked != val) + { + return false; + } } - } - // Artists - if (!string.IsNullOrEmpty(request.Artists)) - { - var artists = request.Artists.Split('|'); + if (request.HasOverview.HasValue) + { + var filterValue = request.HasOverview.Value; - var audio = i as IHasArtist; + var hasValue = !string.IsNullOrEmpty(i.Overview); - if (!(audio != null && artists.Any(audio.HasArtist))) - { - return false; + if (hasValue != filterValue) + { + return false; + } } - } - // Albums - if (!string.IsNullOrEmpty(request.Albums)) - { - var albums = request.Albums.Split('|'); + if (request.HasImdbId.HasValue) + { + var filterValue = request.HasImdbId.Value; - var audio = i as Audio; + var hasValue = !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Imdb)); - if (audio != null) - { - if (!albums.Any(a => string.Equals(a, audio.Album, StringComparison.OrdinalIgnoreCase))) + if (hasValue != filterValue) { return false; } } - var album = i as MusicAlbum; - - if (album != null) + if (request.HasTmdbId.HasValue) { - if (!albums.Any(a => string.Equals(a, album.Name, StringComparison.OrdinalIgnoreCase))) + var filterValue = request.HasTmdbId.Value; + + var hasValue = !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Tmdb)); + + if (hasValue != filterValue) { return false; } } - var musicVideo = i as MusicVideo; - - if (musicVideo != null) + if (request.HasTvdbId.HasValue) { - if (!albums.Any(a => string.Equals(a, musicVideo.Album, StringComparison.OrdinalIgnoreCase))) + var filterValue = request.HasTvdbId.Value; + + var hasValue = !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Tvdb)); + + if (hasValue != filterValue) { return false; } } - return false; - } - - // Min index number - if (request.MinIndexNumber.HasValue) - { - if (!(i.IndexNumber.HasValue && i.IndexNumber.Value >= request.MinIndexNumber.Value)) + if (request.IsYearMismatched.HasValue) { - return false; + var filterValue = request.IsYearMismatched.Value; + + if (UserViewBuilder.IsYearMismatched(i) != filterValue) + { + return false; + } } - } - // Min official rating - if (!string.IsNullOrEmpty(request.MinOfficialRating)) - { - var level = _localization.GetRatingLevel(request.MinOfficialRating); + if (request.HasOfficialRating.HasValue) + { + var filterValue = request.HasOfficialRating.Value; - if (level.HasValue) + var hasValue = !string.IsNullOrEmpty(i.OfficialRating); + + if (hasValue != filterValue) + { + return false; + } + } + + if (request.IsPlaceHolder.HasValue) { - var rating = i.CustomRating; + var filterValue = request.IsPlaceHolder.Value; - if (string.IsNullOrEmpty(rating)) + var isPlaceHolder = false; + + var hasPlaceHolder = i as ISupportsPlaceHolders; + + if (hasPlaceHolder != null) { - rating = i.OfficialRating; + isPlaceHolder = hasPlaceHolder.IsPlaceHolder; } - if (!string.IsNullOrEmpty(rating)) + if (isPlaceHolder != filterValue) { - var itemLevel = _localization.GetRatingLevel(rating); + return false; + } + } - if (!(!itemLevel.HasValue || itemLevel.Value >= level.Value)) + if (request.HasSpecialFeature.HasValue) + { + var filterValue = request.HasSpecialFeature.Value; + + var movie = i as IHasSpecialFeatures; + + if (movie != null) + { + var ok = filterValue + ? movie.SpecialFeatureIds.Count > 0 + : movie.SpecialFeatureIds.Count == 0; + + if (!ok) { return false; } } + else + { + return false; + } } - } - // Max official rating - if (!string.IsNullOrEmpty(request.MaxOfficialRating)) - { - var level = _localization.GetRatingLevel(request.MaxOfficialRating); + if (request.HasSubtitles.HasValue) + { + var val = request.HasSubtitles.Value; - if (level.HasValue) + var video = i as Video; + + if (video == null || val != video.HasSubtitles) + { + return false; + } + } + + if (request.HasParentalRating.HasValue) { + var val = request.HasParentalRating.Value; + var rating = i.CustomRating; if (string.IsNullOrEmpty(rating)) @@ -891,246 +880,176 @@ namespace MediaBrowser.Api.UserLibrary rating = i.OfficialRating; } - if (!string.IsNullOrEmpty(rating)) + if (val) { - var itemLevel = _localization.GetRatingLevel(rating); - - if (!(!itemLevel.HasValue || itemLevel.Value <= level.Value)) + if (string.IsNullOrEmpty(rating)) + { + return false; + } + } + else + { + if (!string.IsNullOrEmpty(rating)) { return false; } } } - } - // LocationTypes - if (!string.IsNullOrEmpty(request.LocationTypes)) - { - var vals = request.LocationTypes.Split(','); - if (!vals.Contains(i.LocationType.ToString(), StringComparer.OrdinalIgnoreCase)) + if (request.HasTrailer.HasValue) { - return false; - } - } + var val = request.HasTrailer.Value; + var trailerCount = 0; - // ExcludeLocationTypes - if (!string.IsNullOrEmpty(request.ExcludeLocationTypes)) - { - var vals = request.ExcludeLocationTypes.Split(','); - if (vals.Contains(i.LocationType.ToString(), StringComparer.OrdinalIgnoreCase)) - { - return false; - } - } + var hasTrailers = i as IHasTrailers; + if (hasTrailers != null) + { + trailerCount = hasTrailers.LocalTrailerIds.Count; + } - if (!string.IsNullOrEmpty(request.AlbumArtistStartsWithOrGreater)) - { - var ok = new[] { i }.OfType() - .Any(p => string.Compare(request.AlbumArtistStartsWithOrGreater, p.AlbumArtists.FirstOrDefault(), StringComparison.CurrentCultureIgnoreCase) < 1); + var ok = val ? trailerCount > 0 : trailerCount == 0; - if (!ok) - { - return false; + if (!ok) + { + return false; + } } - } - - // Filter by Series Status - if (!string.IsNullOrEmpty(request.SeriesStatus)) - { - var vals = request.SeriesStatus.Split(','); - var ok = new[] { i }.OfType().Any(p => p.Status.HasValue && vals.Contains(p.Status.Value.ToString(), StringComparer.OrdinalIgnoreCase)); - - if (!ok) + if (request.HasThemeSong.HasValue) { - return false; - } - } + var filterValue = request.HasThemeSong.Value; - // Filter by Series AirDays - if (!string.IsNullOrEmpty(request.AirDays)) - { - var days = request.AirDays.Split(',').Select(d => (DayOfWeek)Enum.Parse(typeof(DayOfWeek), d, true)); + var themeCount = 0; + var iHasThemeMedia = i as IHasThemeMedia; - var ok = new[] { i }.OfType().Any(p => p.AirDays != null && days.Any(d => p.AirDays.Contains(d))); + if (iHasThemeMedia != null) + { + themeCount = iHasThemeMedia.ThemeSongIds.Count; + } + var ok = filterValue ? themeCount > 0 : themeCount == 0; - if (!ok) - { - return false; + if (!ok) + { + return false; + } } - } - - // Filter by Video3DFormat - if (request.Is3D.HasValue) - { - var val = request.Is3D.Value; - var video = i as Video; - if (video == null || val != video.Video3DFormat.HasValue) + if (request.HasThemeVideo.HasValue) { - return false; - } - } + var filterValue = request.HasThemeVideo.Value; - // Filter by VideoType - if (!string.IsNullOrEmpty(request.VideoTypes)) - { - var types = request.VideoTypes.Split(','); + var themeCount = 0; + var iHasThemeMedia = i as IHasThemeMedia; - var video = i as Video; - if (video == null || !types.Contains(video.VideoType.ToString(), StringComparer.OrdinalIgnoreCase)) - { - return false; - } - } + if (iHasThemeMedia != null) + { + themeCount = iHasThemeMedia.ThemeVideoIds.Count; + } + var ok = filterValue ? themeCount > 0 : themeCount == 0; - var imageTypes = request.GetImageTypes().ToList(); - if (imageTypes.Count > 0) - { - if (!(imageTypes.Any(imageType => HasImage(i, imageType)))) - { - return false; + if (!ok) + { + return false; + } } - } - // Apply genre filter - if (!string.IsNullOrEmpty(request.Genres)) - { - var vals = request.Genres.Split('|'); - if (!(vals.Any(v => i.Genres.Contains(v, StringComparer.OrdinalIgnoreCase)))) + // Apply genre filter + var genres = request.GetGenres(); + if (genres.Length > 0 && !(genres.Any(v => i.Genres.Contains(v, StringComparer.OrdinalIgnoreCase)))) { return false; } - } - // Apply genre filter - if (!string.IsNullOrEmpty(request.AllGenres)) - { - var vals = request.AllGenres.Split('|'); - if (!vals.All(v => i.Genres.Contains(v, StringComparer.OrdinalIgnoreCase))) + // Apply genre filter + var allGenres = request.GetAllGenres(); + if (allGenres.Length > 0 && !allGenres.All(v => i.Genres.Contains(v, StringComparer.OrdinalIgnoreCase))) { return false; } - } - // Apply studio filter - if (!string.IsNullOrEmpty(request.Studios)) - { - var vals = request.Studios.Split('|'); - if (!vals.Any(v => i.Studios.Contains(v, StringComparer.OrdinalIgnoreCase))) + // Filter by VideoType + if (!string.IsNullOrEmpty(request.VideoTypes)) { - return false; - } - } - - // Apply year filter - if (!string.IsNullOrEmpty(request.Years)) - { - var vals = request.Years.Split(',').Select(int.Parse).ToList(); - if (!(i.ProductionYear.HasValue && vals.Contains(i.ProductionYear.Value))) - { - return false; - } - } - - // Apply person filter - if (!string.IsNullOrEmpty(request.Person)) - { - var personTypes = request.PersonTypes; + var types = request.VideoTypes.Split(','); - if (string.IsNullOrEmpty(personTypes)) - { - if (!(i.People.Any(p => string.Equals(p.Name, request.Person, StringComparison.OrdinalIgnoreCase)))) + var video = i as Video; + if (video == null || !types.Contains(video.VideoType.ToString(), StringComparer.OrdinalIgnoreCase)) { return false; } } - else - { - var types = personTypes.Split(','); - var ok = new[] { i }.Any(item => - item.People != null && - item.People.Any(p => - p.Name.Equals(request.Person, StringComparison.OrdinalIgnoreCase) && (types.Contains(p.Type, StringComparer.OrdinalIgnoreCase) || types.Contains(p.Role, StringComparer.OrdinalIgnoreCase)))); - - if (!ok) + var imageTypes = request.GetImageTypes().ToList(); + if (imageTypes.Count > 0) + { + if (!(imageTypes.Any(i.HasImage))) { return false; } } - } - - if (request.HasTrailer.HasValue) - { - var val = request.HasTrailer.Value; - var trailerCount = 0; - var hasTrailers = i as IHasTrailers; - if (hasTrailers != null) + // Apply studio filter + var studios = request.GetStudios(); + if (studios.Length > 0 && !studios.Any(v => i.Studios.Contains(v, StringComparer.OrdinalIgnoreCase))) { - trailerCount = hasTrailers.LocalTrailerIds.Count; + return false; } - var ok = val ? trailerCount > 0 : trailerCount == 0; - - if (!ok) + // Apply year filter + var years = request.GetYears(); + if (years.Length > 0 && !(i.ProductionYear.HasValue && years.Contains(i.ProductionYear.Value))) { return false; } - } - if (request.HasThemeSong.HasValue) - { - var filterValue = request.HasThemeSong.Value; + // Apply person filter + if (!string.IsNullOrEmpty(request.Person)) + { + var personTypes = request.GetPersonTypes(); - var themeCount = 0; - var iHasThemeMedia = i as IHasThemeMedia; + if (personTypes.Length == 0) + { + if (!(i.People.Any(p => string.Equals(p.Name, request.Person, StringComparison.OrdinalIgnoreCase)))) + { + return false; + } + } + else + { + var types = personTypes; - if (iHasThemeMedia != null) - { - themeCount = iHasThemeMedia.ThemeSongIds.Count; - } - var ok = filterValue ? themeCount > 0 : themeCount == 0; + var ok = new[] { i }.Any(item => + item.People != null && + item.People.Any(p => + p.Name.Equals(request.Person, StringComparison.OrdinalIgnoreCase) && (types.Contains(p.Type, StringComparer.OrdinalIgnoreCase) || types.Contains(p.Role, StringComparer.OrdinalIgnoreCase)))); - if (!ok) - { - return false; + if (!ok) + { + return false; + } + } } } - if (request.HasThemeVideo.HasValue) + if (request.MinCommunityRating.HasValue) { - var filterValue = request.HasThemeVideo.Value; - - var themeCount = 0; - var iHasThemeMedia = i as IHasThemeMedia; - - if (iHasThemeMedia != null) - { - themeCount = iHasThemeMedia.ThemeVideoIds.Count; - } - var ok = filterValue ? themeCount > 0 : themeCount == 0; + var val = request.MinCommunityRating.Value; - if (!ok) + if (!(i.CommunityRating.HasValue && i.CommunityRating >= val)) { return false; } } - if (request.MinPlayers.HasValue) + if (request.MinCriticRating.HasValue) { - var filterValue = request.MinPlayers.Value; + var val = request.MinCriticRating.Value; - var game = i as Game; + var hasCriticRating = i as IHasCriticRating; - if (game != null) + if (hasCriticRating != null) { - var players = game.PlayersSupported ?? 1; - - var ok = players >= filterValue; - - if (!ok) + if (!(hasCriticRating.CriticRating.HasValue && hasCriticRating.CriticRating >= val)) { return false; } @@ -1141,504 +1060,284 @@ namespace MediaBrowser.Api.UserLibrary } } - if (request.MaxPlayers.HasValue) + // Artists + if (!string.IsNullOrEmpty(request.Artists)) { - var filterValue = request.MaxPlayers.Value; - - var game = i as Game; - - if (game != null) - { - var players = game.PlayersSupported ?? 1; + var artists = request.Artists.Split('|'); - var ok = players <= filterValue; + var audio = i as IHasArtist; - if (!ok) - { - return false; - } - } - else + if (!(audio != null && artists.Any(audio.HasArtist))) { return false; } } - if (request.HasSpecialFeature.HasValue) + // Albums + if (!string.IsNullOrEmpty(request.Albums)) { - var filterValue = request.HasSpecialFeature.Value; + var albums = request.Albums.Split('|'); - var movie = i as IHasSpecialFeatures; + var audio = i as Audio; - if (movie != null) + if (audio != null) { - var ok = filterValue - ? movie.SpecialFeatureIds.Count > 0 - : movie.SpecialFeatureIds.Count == 0; - - if (!ok) + if (!albums.Any(a => string.Equals(a, audio.Album, StringComparison.OrdinalIgnoreCase))) { return false; } } - else - { - return false; - } - } - - if (request.HasSubtitles.HasValue) - { - var val = request.HasSubtitles.Value; - - var video = i as Video; - - if (video == null || val != video.HasSubtitles) - { - return false; - } - } - if (request.HasParentalRating.HasValue) - { - var val = request.HasParentalRating.Value; - - var rating = i.CustomRating; - - if (string.IsNullOrEmpty(rating)) - { - rating = i.OfficialRating; - } + var album = i as MusicAlbum; - if (val) + if (album != null) { - if (string.IsNullOrEmpty(rating)) + if (!albums.Any(a => string.Equals(a, album.Name, StringComparison.OrdinalIgnoreCase))) { return false; } } - else + + var musicVideo = i as MusicVideo; + + if (musicVideo != null) { - if (!string.IsNullOrEmpty(rating)) + if (!albums.Any(a => string.Equals(a, musicVideo.Album, StringComparison.OrdinalIgnoreCase))) { return false; } } - } - - if (request.IsHD.HasValue) - { - var val = request.IsHD.Value; - var video = i as Video; - if (video == null || val != video.IsHD) - { - return false; - } + return false; } - if (request.IsInBoxSet.HasValue) + // Min index number + if (request.MinIndexNumber.HasValue) { - var val = request.IsHD.Value; - if (i.Parents.OfType().Any() != val) + if (!(i.IndexNumber.HasValue && i.IndexNumber.Value >= request.MinIndexNumber.Value)) { return false; } } - if (request.IsUnidentified.HasValue) + // Min official rating + if (!string.IsNullOrEmpty(request.MinOfficialRating)) { - var val = request.IsUnidentified.Value; - if (i.IsUnidentified != val) - { - return false; - } - } + var level = _localization.GetRatingLevel(request.MinOfficialRating); - if (request.IsLocked.HasValue) - { - var val = request.IsLocked.Value; - if (i.IsLocked != val) + if (level.HasValue) { - return false; - } - } - - if (request.ParentIndexNumber.HasValue) - { - var filterValue = request.ParentIndexNumber.Value; - - var episode = i as Episode; + var rating = i.CustomRating; - if (episode != null) - { - if (episode.ParentIndexNumber.HasValue && episode.ParentIndexNumber.Value != filterValue) + if (string.IsNullOrEmpty(rating)) { - return false; + rating = i.OfficialRating; } - } - - var song = i as Audio; - if (song != null) - { - if (song.ParentIndexNumber.HasValue && song.ParentIndexNumber.Value != filterValue) + if (!string.IsNullOrEmpty(rating)) { - return false; + var itemLevel = _localization.GetRatingLevel(rating); + + if (!(!itemLevel.HasValue || itemLevel.Value >= level.Value)) + { + return false; + } } } } - if (request.AiredDuringSeason.HasValue) + // Max official rating + if (!string.IsNullOrEmpty(request.MaxOfficialRating)) { - var episode = i as Episode; + var level = _localization.GetRatingLevel(request.MaxOfficialRating); - if (episode == null) + if (level.HasValue) { - return false; - } + var rating = i.CustomRating; - if (!Series.FilterEpisodesBySeason(new[] { episode }, request.AiredDuringSeason.Value, true).Any()) - { - return false; + if (string.IsNullOrEmpty(rating)) + { + rating = i.OfficialRating; + } + + if (!string.IsNullOrEmpty(rating)) + { + var itemLevel = _localization.GetRatingLevel(rating); + + if (!(!itemLevel.HasValue || itemLevel.Value <= level.Value)) + { + return false; + } + } } } - if (!string.IsNullOrEmpty(request.MinPremiereDate)) + // LocationTypes + if (!string.IsNullOrEmpty(request.LocationTypes)) { - var date = DateTime.Parse(request.MinPremiereDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime(); - - if (!(i.PremiereDate.HasValue && i.PremiereDate.Value >= date)) + var vals = request.LocationTypes.Split(','); + if (!vals.Contains(i.LocationType.ToString(), StringComparer.OrdinalIgnoreCase)) { return false; } } - if (!string.IsNullOrEmpty(request.MaxPremiereDate)) + // ExcludeLocationTypes + if (!string.IsNullOrEmpty(request.ExcludeLocationTypes)) { - var date = DateTime.Parse(request.MaxPremiereDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime(); - - if (!(i.PremiereDate.HasValue && i.PremiereDate.Value <= date)) + var vals = request.ExcludeLocationTypes.Split(','); + if (vals.Contains(i.LocationType.ToString(), StringComparer.OrdinalIgnoreCase)) { return false; } } - if (request.HasOverview.HasValue) + if (!string.IsNullOrEmpty(request.AlbumArtistStartsWithOrGreater)) { - var filterValue = request.HasOverview.Value; - - var hasValue = !string.IsNullOrEmpty(i.Overview); + var ok = new[] { i }.OfType() + .Any(p => string.Compare(request.AlbumArtistStartsWithOrGreater, p.AlbumArtists.FirstOrDefault(), StringComparison.CurrentCultureIgnoreCase) < 1); - if (hasValue != filterValue) + if (!ok) { return false; } } - if (request.HasImdbId.HasValue) + // Filter by Series Status + if (!string.IsNullOrEmpty(request.SeriesStatus)) { - var filterValue = request.HasImdbId.Value; + var vals = request.SeriesStatus.Split(','); - var hasValue = !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Imdb)); + var ok = new[] { i }.OfType().Any(p => p.Status.HasValue && vals.Contains(p.Status.Value.ToString(), StringComparer.OrdinalIgnoreCase)); - if (hasValue != filterValue) + if (!ok) { return false; } } - if (request.HasTmdbId.HasValue) + // Filter by Series AirDays + if (!string.IsNullOrEmpty(request.AirDays)) { - var filterValue = request.HasTmdbId.Value; + var days = request.AirDays.Split(',').Select(d => (DayOfWeek)Enum.Parse(typeof(DayOfWeek), d, true)); - var hasValue = !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Tmdb)); + var ok = new[] { i }.OfType().Any(p => p.AirDays != null && days.Any(d => p.AirDays.Contains(d))); - if (hasValue != filterValue) + if (!ok) { return false; } } - if (request.HasTvdbId.HasValue) + if (request.MinPlayers.HasValue) { - var filterValue = request.HasTvdbId.Value; + var filterValue = request.MinPlayers.Value; - var hasValue = !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Tvdb)); + var game = i as Game; - if (hasValue != filterValue) + if (game != null) { - return false; - } - } + var players = game.PlayersSupported ?? 1; - if (request.IsYearMismatched.HasValue) - { - var filterValue = request.IsYearMismatched.Value; + var ok = players >= filterValue; - if (IsYearMismatched(i) != filterValue) + if (!ok) + { + return false; + } + } + else { return false; } } - if (request.HasOfficialRating.HasValue) + if (request.MaxPlayers.HasValue) { - var filterValue = request.HasOfficialRating.Value; + var filterValue = request.MaxPlayers.Value; - var hasValue = !string.IsNullOrEmpty(i.OfficialRating); + var game = i as Game; - if (hasValue != filterValue) + if (game != null) { - return false; - } - } - - if (request.IsPlaceHolder.HasValue) - { - var filterValue = request.IsPlaceHolder.Value; - - var isPlaceHolder = false; + var players = game.PlayersSupported ?? 1; - var hasPlaceHolder = i as ISupportsPlaceHolders; + var ok = players <= filterValue; - if (hasPlaceHolder != null) - { - isPlaceHolder = hasPlaceHolder.IsPlaceHolder; + if (!ok) + { + return false; + } } - - if (isPlaceHolder != filterValue) + else { return false; } } - return true; - } - - private IEnumerable ApplyPostCollectionCollapseFilters(GetItems request, IEnumerable items, User user) - { - if (!string.IsNullOrEmpty(request.NameStartsWithOrGreater)) - { - items = items.Where(i => string.Compare(request.NameStartsWithOrGreater, i.SortName, StringComparison.CurrentCultureIgnoreCase) < 1); - } - if (!string.IsNullOrEmpty(request.NameStartsWith)) - { - items = items.Where(i => string.Compare(request.NameStartsWith, i.SortName.Substring(0, 1), StringComparison.CurrentCultureIgnoreCase) == 0); - } - - if (!string.IsNullOrEmpty(request.NameLessThan)) + if (request.ParentIndexNumber.HasValue) { - items = items.Where(i => string.Compare(request.NameLessThan, i.SortName, StringComparison.CurrentCultureIgnoreCase) == 1); - } - - return items; - } + var filterValue = request.ParentIndexNumber.Value; - private bool IsYearMismatched(BaseItem item) - { - if (item.ProductionYear.HasValue) - { - var path = item.Path; + var episode = i as Episode; - if (!string.IsNullOrEmpty(path)) + if (episode != null) { - int? yearInName; - string name; - NameParser.ParseName(Path.GetFileName(path), out name, out yearInName); - - // Go up a level if we didn't get a year - if (!yearInName.HasValue) + if (episode.ParentIndexNumber.HasValue && episode.ParentIndexNumber.Value != filterValue) { - NameParser.ParseName(Path.GetFileName(Path.GetDirectoryName(path)), out name, out yearInName); + return false; } + } - if (yearInName.HasValue) + var song = i as Audio; + + if (song != null) + { + if (song.ParentIndexNumber.HasValue && song.ParentIndexNumber.Value != filterValue) { - return yearInName.Value != item.ProductionYear.Value; + return false; } } } - return false; - } - - private bool AllowBoxSetCollapsing(GetItems request) - { - if (!string.IsNullOrWhiteSpace(request.Filters)) - { - return false; - } - - if (!string.IsNullOrWhiteSpace(request.AllGenres)) - { - return false; - } - - if (!string.IsNullOrWhiteSpace(request.Genres)) - { - return false; - } - - if (request.HasImdbId.HasValue) - { - return false; - } - - if (request.HasOfficialRating.HasValue) - { - return false; - } - - if (request.HasOverview.HasValue) - { - return false; - } - - if (request.HasParentalRating.HasValue) - { - return false; - } - - if (request.HasSpecialFeature.HasValue) - { - return false; - } - - if (request.HasSubtitles.HasValue) - { - return false; - } - - if (request.HasThemeSong.HasValue) - { - return false; - } - - if (request.HasThemeVideo.HasValue) - { - return false; - } - - if (request.HasTmdbId.HasValue) - { - return false; - } - - if (request.HasTrailer.HasValue) - { - return false; - } - - if (!string.IsNullOrWhiteSpace(request.Ids)) - { - return false; - } - - if (!string.IsNullOrWhiteSpace(request.ImageTypes)) - { - return false; - } - - if (request.Is3D.HasValue) - { - return false; - } - - if (request.IsHD.HasValue) - { - return false; - } - - if (request.IsInBoxSet.HasValue) - { - return false; - } - - if (request.IsLocked.HasValue) - { - return false; - } - - if (request.IsPlaceHolder.HasValue) - { - return false; - } - - if (request.IsPlayed.HasValue) + if (request.AiredDuringSeason.HasValue) { - return false; - } + var episode = i as Episode; - if (request.IsUnidentified.HasValue) - { - return false; - } + if (episode == null) + { + return false; + } - if (request.IsYearMismatched.HasValue) - { - return false; + if (!Series.FilterEpisodesBySeason(new[] { episode }, request.AiredDuringSeason.Value, true).Any()) + { + return false; + } } - if (!string.IsNullOrWhiteSpace(request.Person)) + if (!string.IsNullOrEmpty(request.MinPremiereDate)) { - return false; - } + var date = DateTime.Parse(request.MinPremiereDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime(); - if (!string.IsNullOrWhiteSpace(request.Studios)) - { - return false; + if (!(i.PremiereDate.HasValue && i.PremiereDate.Value >= date)) + { + return false; + } } - if (!string.IsNullOrWhiteSpace(request.VideoTypes)) + if (!string.IsNullOrEmpty(request.MaxPremiereDate)) { - return false; - } + var date = DateTime.Parse(request.MaxPremiereDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime(); - if (!string.IsNullOrWhiteSpace(request.Years)) - { - return false; + if (!(i.PremiereDate.HasValue && i.PremiereDate.Value <= date)) + { + return false; + } } return true; } - internal static IEnumerable FilterForAdjacency(IEnumerable items, string adjacentToId) - { - var list = items.ToList(); - - var adjacentToIdGuid = new Guid(adjacentToId); - var adjacentToItem = list.FirstOrDefault(i => i.Id == adjacentToIdGuid); - - var index = list.IndexOf(adjacentToItem); - - var previousId = Guid.Empty; - var nextId = Guid.Empty; - - if (index > 0) - { - previousId = list[index - 1].Id; - } - - if (index < list.Count - 1) - { - nextId = list[index + 1].Id; - } - - return list.Where(i => i.Id == previousId || i.Id == nextId || i.Id == adjacentToIdGuid); - } - - /// - /// Determines whether the specified item has image. - /// - /// The item. - /// Type of the image. - /// true if the specified item has image; otherwise, false. - internal static bool HasImage(BaseItem item, ImageType imageType) - { - return item.HasImage(imageType); - } - /// /// Applies the paging. /// diff --git a/MediaBrowser.Controller/Channels/ChannelAudioItem.cs b/MediaBrowser.Controller/Channels/ChannelAudioItem.cs index 6d505aecb4..ede366dab0 100644 --- a/MediaBrowser.Controller/Channels/ChannelAudioItem.cs +++ b/MediaBrowser.Controller/Channels/ChannelAudioItem.cs @@ -1,9 +1,11 @@ using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Model.Channels; using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using System.Collections.Generic; using System.Linq; +using System.Threading; namespace MediaBrowser.Controller.Channels { @@ -69,5 +71,22 @@ namespace MediaBrowser.Controller.Channels { return System.IO.Path.Combine(basePath, "channels", ChannelId, Id.ToString("N")); } + + public override IEnumerable GetMediaSources(bool enablePathSubstitution) + { + var list = base.GetMediaSources(enablePathSubstitution).ToList(); + + var sources = ChannelManager.GetChannelItemMediaSources(Id.ToString("N"), false, CancellationToken.None) + .Result.ToList(); + + if (sources.Count > 0) + { + return sources; + } + + list.InsertRange(0, sources); + + return list; + } } } diff --git a/MediaBrowser.Controller/Channels/ChannelVideoItem.cs b/MediaBrowser.Controller/Channels/ChannelVideoItem.cs index e6fa459723..72e2b110a9 100644 --- a/MediaBrowser.Controller/Channels/ChannelVideoItem.cs +++ b/MediaBrowser.Controller/Channels/ChannelVideoItem.cs @@ -7,6 +7,7 @@ using MediaBrowser.Model.Entities; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Threading; namespace MediaBrowser.Controller.Channels { @@ -90,7 +91,15 @@ namespace MediaBrowser.Controller.Channels { var list = base.GetMediaSources(enablePathSubstitution).ToList(); - list.InsertRange(0, ChannelManager.GetCachedChannelItemMediaSources(Id.ToString("N"))); + var sources = ChannelManager.GetChannelItemMediaSources(Id.ToString("N"), false, CancellationToken.None) + .Result.ToList(); + + if (sources.Count > 0) + { + return sources; + } + + list.InsertRange(0, sources); return list; } diff --git a/MediaBrowser.Controller/Channels/IChannelManager.cs b/MediaBrowser.Controller/Channels/IChannelManager.cs index f4e985c7ad..05015da37d 100644 --- a/MediaBrowser.Controller/Channels/IChannelManager.cs +++ b/MediaBrowser.Controller/Channels/IChannelManager.cs @@ -109,20 +109,14 @@ namespace MediaBrowser.Controller.Channels /// Task<QueryResult<BaseItem>>. Task> GetChannelItemsInternal(ChannelItemQuery query, IProgress progress, CancellationToken cancellationToken); - /// - /// Gets the cached channel item media sources. - /// - /// The identifier. - /// IEnumerable{MediaSourceInfo}. - IEnumerable GetCachedChannelItemMediaSources(string id); - /// /// Gets the channel item media sources. /// /// The identifier. + /// if set to true [include dynamic sources]. /// The cancellation token. /// Task{IEnumerable{MediaSourceInfo}}. - Task> GetChannelItemMediaSources(string id, CancellationToken cancellationToken); + Task> GetChannelItemMediaSources(string id, bool includeDynamicSources, CancellationToken cancellationToken); /// /// Gets the channel folder. diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 4a4ff17a05..93b71ae36b 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1,6 +1,7 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; using MediaBrowser.Controller.Channels; +using MediaBrowser.Controller.Collections; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; @@ -251,6 +252,7 @@ namespace MediaBrowser.Controller.Entities public static IUserDataManager UserDataManager { get; set; } public static ILiveTvManager LiveTvManager { get; set; } public static IChannelManager ChannelManager { get; set; } + public static ICollectionManager CollectionManager { get; set; } /// /// Returns a that represents this instance. diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index bfec0c26e6..4614f2f8a3 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -785,7 +785,7 @@ namespace MediaBrowser.Controller.Entities protected QueryResult SortAndFilter(IEnumerable items, InternalItemsQuery query) { - return UserViewBuilder.SortAndFilter(items, null, query, LibraryManager, UserDataManager); + return UserViewBuilder.SortAndFilter(items, this, null, query, LibraryManager, UserDataManager); } /// diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs index 91cb43291f..1faa6c3914 100644 --- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs @@ -29,6 +29,46 @@ namespace MediaBrowser.Controller.Entities public string[] MediaTypes { get; set; } public string[] IncludeItemTypes { get; set; } public string[] ExcludeItemTypes { get; set; } + public string[] Genres { get; set; } + public string[] AllGenres { get; set; } + + public bool? IsMissing { get; set; } + public bool? IsUnaired { get; set; } + public bool? IsVirtualUnaired { get; set; } + public bool? CollapseBoxSetItems { get; set; } + + public string NameStartsWithOrGreater { get; set; } + public string NameStartsWith { get; set; } + public string NameLessThan { get; set; } + + public string Person { get; set; } + public string AdjacentTo { get; set; } + public string[] PersonTypes { get; set; } + + public bool? Is3D { get; set; } + public bool? IsHD { get; set; } + public bool? IsInBoxSet { get; set; } + public bool? IsLocked { get; set; } + public bool? IsUnidentified { get; set; } + public bool? IsPlaceHolder { get; set; } + public bool? IsYearMismatched { get; set; } + + public bool? HasImdbId { get; set; } + public bool? HasOverview { get; set; } + public bool? HasTmdbId { get; set; } + public bool? HasOfficialRating { get; set; } + public bool? HasTvdbId { get; set; } + public bool? HasThemeSong { get; set; } + public bool? HasThemeVideo { get; set; } + public bool? HasSubtitles { get; set; } + public bool? HasSpecialFeature { get; set; } + public bool? HasTrailer { get; set; } + public bool? HasParentalRating { get; set; } + + public string[] Studios { get; set; } + public ImageType[] ImageTypes { get; set; } + public VideoType[] VideoTypes { get; set; } + public int[] Years { get; set; } public InternalItemsQuery() { @@ -36,6 +76,13 @@ namespace MediaBrowser.Controller.Entities MediaTypes = new string[] { }; IncludeItemTypes = new string[] { }; ExcludeItemTypes = new string[] { }; + AllGenres = new string[] { }; + Genres = new string[] { }; + Studios = new string[] { }; + ImageTypes = new ImageType[] { }; + VideoTypes = new VideoType[] { }; + Years = new int[] { }; + PersonTypes = new string[] { }; } } } diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs index 043c69e275..bcbaa967a1 100644 --- a/MediaBrowser.Controller/Entities/UserView.cs +++ b/MediaBrowser.Controller/Entities/UserView.cs @@ -17,7 +17,7 @@ namespace MediaBrowser.Controller.Entities public override Task> GetItems(InternalItemsQuery query) { - return new UserViewBuilder(UserViewManager, LiveTvManager, ChannelManager, LibraryManager, Logger, UserDataManager, TVSeriesManager) + return new UserViewBuilder(UserViewManager, LiveTvManager, ChannelManager, LibraryManager, Logger, UserDataManager, TVSeriesManager, CollectionManager) .GetUserItems(this, ViewType, query); } diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index 8efa1b6bb4..bf6cac87eb 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -1,9 +1,12 @@ -using MediaBrowser.Controller.Channels; +using System.IO; +using MediaBrowser.Controller.Channels; +using MediaBrowser.Controller.Collections; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; +using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.TV; using MediaBrowser.Model.Channels; using MediaBrowser.Model.Entities; @@ -27,8 +30,9 @@ namespace MediaBrowser.Controller.Entities private readonly ILogger _logger; private readonly IUserDataManager _userDataManager; private readonly ITVSeriesManager _tvSeriesManager; + private readonly ICollectionManager _collectionManager; - public UserViewBuilder(IUserViewManager userViewManager, ILiveTvManager liveTvManager, IChannelManager channelManager, ILibraryManager libraryManager, ILogger logger, IUserDataManager userDataManager, ITVSeriesManager tvSeriesManager) + public UserViewBuilder(IUserViewManager userViewManager, ILiveTvManager liveTvManager, IChannelManager channelManager, ILibraryManager libraryManager, ILogger logger, IUserDataManager userDataManager, ITVSeriesManager tvSeriesManager, ICollectionManager collectionManager) { _userViewManager = userViewManager; _liveTvManager = liveTvManager; @@ -37,6 +41,7 @@ namespace MediaBrowser.Controller.Entities _logger = logger; _userDataManager = userDataManager; _tvSeriesManager = tvSeriesManager; + _collectionManager = collectionManager; } public async Task> GetUserItems(Folder parent, string viewType, InternalItemsQuery query) @@ -102,17 +107,17 @@ namespace MediaBrowser.Controller.Entities { var result = await GetLiveTvFolders(user).ConfigureAwait(false); - return GetResult(result, query); + return GetResult(result, parent, query); } case CollectionType.Folders: - return GetResult(user.RootFolder.GetChildren(user, true), query); + return GetResult(user.RootFolder.GetChildren(user, true), parent, query); case CollectionType.Games: return await GetGameView(user, parent, query).ConfigureAwait(false); case CollectionType.BoxSets: - return GetResult(GetMediaFolders(user).SelectMany(i => i.GetRecursiveChildren(user)).OfType(), query); + return GetResult(GetMediaFolders(user).SelectMany(i => i.GetRecursiveChildren(user)).OfType(), parent, query); case CollectionType.TvShows: return await GetTvView(parent, user, query).ConfigureAwait(false); @@ -205,7 +210,7 @@ namespace MediaBrowser.Controller.Entities return GetFavoriteSongs(parent, user, query); default: - return GetResult(GetMediaFolders(user).SelectMany(i => i.GetChildren(user, true)), query); + return GetResult(GetMediaFolders(user).SelectMany(i => i.GetChildren(user, true)), parent, query); } } @@ -218,7 +223,7 @@ namespace MediaBrowser.Controller.Entities { if (query.Recursive) { - return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Music, CollectionType.MusicVideos }), query); + return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Music, CollectionType.MusicVideos }), parent, query); } var list = new List(); @@ -233,7 +238,7 @@ namespace MediaBrowser.Controller.Entities //list.Add(await GetUserView(CollectionType.MusicGenres, user, "5", parent).ConfigureAwait(false)); list.Add(await GetUserView(category, CollectionType.MusicFavorites, user, "6", parent).ConfigureAwait(false)); - return GetResult(list, query); + return GetResult(list, parent, query); } private async Task> GetMusicFavorites(Folder parent, User user, InternalItemsQuery query) @@ -246,7 +251,7 @@ namespace MediaBrowser.Controller.Entities list.Add(await GetUserView(category, CollectionType.MusicFavoriteArtists, user, "1", parent).ConfigureAwait(false)); list.Add(await GetUserView(category, CollectionType.MusicFavoriteSongs, user, "2", parent).ConfigureAwait(false)); - return GetResult(list, query); + return GetResult(list, parent, query); } private QueryResult GetMusicAlbumArtists(Folder parent, User user, InternalItemsQuery query) @@ -270,7 +275,7 @@ namespace MediaBrowser.Controller.Entities }) .Where(i => i != null); - return GetResult(artists, query); + return GetResult(artists, parent, query); } private QueryResult GetMusicArtists(Folder parent, User user, InternalItemsQuery query) @@ -294,7 +299,7 @@ namespace MediaBrowser.Controller.Entities }) .Where(i => i != null); - return GetResult(artists, query); + return GetResult(artists, parent, query); } private QueryResult GetFavoriteArtists(Folder parent, User user, InternalItemsQuery query) @@ -318,17 +323,17 @@ namespace MediaBrowser.Controller.Entities }) .Where(i => i != null && _userDataManager.GetUserData(user.Id, i.GetUserDataKey()).IsFavorite); - return GetResult(artists, query); + return GetResult(artists, parent, query); } private QueryResult GetMusicAlbums(Folder parent, User user, InternalItemsQuery query) { - return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Music, CollectionType.MusicVideos }).Where(i => i is MusicAlbum), query); + return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Music, CollectionType.MusicVideos }).Where(i => i is MusicAlbum), parent, query); } private QueryResult GetMusicSongs(Folder parent, User user, InternalItemsQuery query) { - return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Music, CollectionType.MusicVideos }).Where(i => i is Audio.Audio), query); + return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Music, CollectionType.MusicVideos }).Where(i => i is Audio.Audio), parent, query); } private QueryResult GetMusicLatest(Folder parent, User user, InternalItemsQuery query) @@ -336,14 +341,14 @@ namespace MediaBrowser.Controller.Entities query.SortBy = new[] { ItemSortBy.DateCreated, ItemSortBy.SortName }; query.SortOrder = SortOrder.Descending; - return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Music, CollectionType.MusicVideos }).Where(i => i is MusicVideo || i is Audio.Audio), GetSpecialItemsLimit(), query); + return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Music, CollectionType.MusicVideos }).Where(i => i is MusicVideo || i is Audio.Audio), parent, GetSpecialItemsLimit(), query); } private async Task> GetMovieFolders(Folder parent, User user, InternalItemsQuery query) { if (query.Recursive) { - return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty }).Where(i => i is Movie || i is BoxSet), query); + return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty }).Where(i => i is Movie || i is BoxSet), parent, query); } var list = new List(); @@ -357,52 +362,52 @@ namespace MediaBrowser.Controller.Entities list.Add(await GetUserView(category, CollectionType.MovieFavorites, user, "4", parent).ConfigureAwait(false)); //list.Add(await GetUserView(CollectionType.MovieGenres, user, "5", parent).ConfigureAwait(false)); - return GetResult(list, query); + return GetResult(list, parent, query); } private QueryResult GetFavoriteMovies(Folder parent, User user, InternalItemsQuery query) { query.IsFavorite = true; - return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty }).Where(i => i is Movie), query); + return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty }).Where(i => i is Movie), parent, query); } private QueryResult GetFavoriteSeries(Folder parent, User user, InternalItemsQuery query) { query.IsFavorite = true; - return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.TvShows, string.Empty }).Where(i => i is Series), query); + return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.TvShows, string.Empty }).Where(i => i is Series), parent, query); } private QueryResult GetFavoriteEpisodes(Folder parent, User user, InternalItemsQuery query) { query.IsFavorite = true; - return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.TvShows, string.Empty }).Where(i => i is Episode), query); + return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.TvShows, string.Empty }).Where(i => i is Episode), parent, query); } private QueryResult GetFavoriteSongs(Folder parent, User user, InternalItemsQuery query) { query.IsFavorite = true; - return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Music }).Where(i => i is Audio.Audio), query); + return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Music }).Where(i => i is Audio.Audio), parent, query); } private QueryResult GetFavoriteAlbums(Folder parent, User user, InternalItemsQuery query) { query.IsFavorite = true; - return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Music }).Where(i => i is MusicAlbum), query); + return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Music }).Where(i => i is MusicAlbum), parent, query); } private QueryResult GetMovieMovies(Folder parent, User user, InternalItemsQuery query) { - return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty }).Where(i => i is Movie), query); + return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty }).Where(i => i is Movie), parent, query); } private QueryResult GetMovieCollections(Folder parent, User user, InternalItemsQuery query) { - return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty }).Where(i => i is BoxSet), query); + return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty }).Where(i => i is BoxSet), parent, query); } private QueryResult GetMovieLatest(Folder parent, User user, InternalItemsQuery query) @@ -410,7 +415,7 @@ namespace MediaBrowser.Controller.Entities query.SortBy = new[] { ItemSortBy.DateCreated, ItemSortBy.SortName }; query.SortOrder = SortOrder.Descending; - return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty }).Where(i => i is Movie), GetSpecialItemsLimit(), query); + return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty }).Where(i => i is Movie), parent, GetSpecialItemsLimit(), query); } private QueryResult GetMovieResume(Folder parent, User user, InternalItemsQuery query) @@ -419,7 +424,7 @@ namespace MediaBrowser.Controller.Entities query.SortOrder = SortOrder.Descending; query.IsResumable = true; - return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty }).Where(i => i is Movie), GetSpecialItemsLimit(), query); + return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty }).Where(i => i is Movie), parent, GetSpecialItemsLimit(), query); } private QueryResult GetMovieGenres(Folder parent, User user, InternalItemsQuery query) @@ -444,14 +449,14 @@ namespace MediaBrowser.Controller.Entities }) .Where(i => i != null); - return GetResult(genres, query); + return GetResult(genres, parent, query); } private async Task> GetTvView(Folder parent, User user, InternalItemsQuery query) { if (query.Recursive) { - return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.TvShows, string.Empty }).Where(i => i is Series || i is Season || i is Episode), query); + return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.TvShows, string.Empty }).Where(i => i is Series || i is Season || i is Episode), parent, query); } var list = new List(); @@ -466,14 +471,14 @@ namespace MediaBrowser.Controller.Entities list.Add(await GetUserView(category, CollectionType.TvFavoriteEpisodes, user, "5", parent).ConfigureAwait(false)); //list.Add(await GetUserView(CollectionType.TvGenres, user, "5", parent).ConfigureAwait(false)); - return GetResult(list, query); + return GetResult(list, parent, query); } private async Task> GetGameView(User user, Folder parent, InternalItemsQuery query) { if (query.Recursive) { - return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Games }), query); + return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Games }), parent, query); } var list = new List(); @@ -486,7 +491,7 @@ namespace MediaBrowser.Controller.Entities list.Add(await GetUserView(category, CollectionType.GameSystems, user, "3", parent).ConfigureAwait(false)); //list.Add(await GetUserView(CollectionType.GameGenres, user, "4", parent).ConfigureAwait(false)); - return GetResult(list, query); + return GetResult(list, parent, query); } private QueryResult GetLatestGames(Folder parent, User user, InternalItemsQuery query) @@ -494,7 +499,7 @@ namespace MediaBrowser.Controller.Entities query.SortBy = new[] { ItemSortBy.DateCreated, ItemSortBy.SortName }; query.SortOrder = SortOrder.Descending; - return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Games }).OfType(), GetSpecialItemsLimit(), query); + return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Games }).OfType(), parent, GetSpecialItemsLimit(), query); } private QueryResult GetRecentlyPlayedGames(Folder parent, User user, InternalItemsQuery query) @@ -503,14 +508,14 @@ namespace MediaBrowser.Controller.Entities query.SortBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.SortName }; query.SortOrder = SortOrder.Descending; - return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Games }).OfType(), GetSpecialItemsLimit(), query); + return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Games }).OfType(), parent, GetSpecialItemsLimit(), query); } private QueryResult GetFavoriteGames(Folder parent, User user, InternalItemsQuery query) { query.IsFavorite = true; - return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Games }).OfType(), query); + return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Games }).OfType(), parent, query); } private QueryResult GetTvLatest(Folder parent, User user, InternalItemsQuery query) @@ -518,7 +523,7 @@ namespace MediaBrowser.Controller.Entities query.SortBy = new[] { ItemSortBy.DateCreated, ItemSortBy.SortName }; query.SortOrder = SortOrder.Descending; - return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.TvShows, string.Empty }).OfType(), GetSpecialItemsLimit(), query); + return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.TvShows, string.Empty }).OfType(), parent, GetSpecialItemsLimit(), query); } private QueryResult GetTvNextUp(Folder parent, InternalItemsQuery query) @@ -542,12 +547,12 @@ namespace MediaBrowser.Controller.Entities query.SortOrder = SortOrder.Descending; query.IsResumable = true; - return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.TvShows, string.Empty }).OfType(), GetSpecialItemsLimit(), query); + return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.TvShows, string.Empty }).OfType(), parent, GetSpecialItemsLimit(), query); } private QueryResult GetTvSeries(Folder parent, User user, InternalItemsQuery query) { - return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.TvShows, string.Empty }).OfType(), query); + return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.TvShows, string.Empty }).OfType(), parent, query); } private QueryResult GetTvGenres(Folder parent, User user, InternalItemsQuery query) @@ -572,12 +577,12 @@ namespace MediaBrowser.Controller.Entities }) .Where(i => i != null); - return GetResult(genres, query); + return GetResult(genres, parent, query); } private QueryResult GetGameSystems(Folder parent, User user, InternalItemsQuery query) { - return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Games }).OfType(), query); + return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Games }).OfType(), parent, query); } private QueryResult GetGameGenres(Folder parent, User user, InternalItemsQuery query) @@ -602,7 +607,7 @@ namespace MediaBrowser.Controller.Entities }) .Where(i => i != null); - return GetResult(genres, query); + return GetResult(genres, parent, query); } private QueryResult GetResult(QueryResult result) @@ -616,21 +621,24 @@ namespace MediaBrowser.Controller.Entities } private QueryResult GetResult(IEnumerable items, + BaseItem parentItem, InternalItemsQuery query) where T : BaseItem { - return GetResult(items, null, query); + return GetResult(items, parentItem, null, query); } private QueryResult GetResult(IEnumerable items, + BaseItem parentItem, int? totalRecordLimit, InternalItemsQuery query) where T : BaseItem { - return SortAndFilter(items, totalRecordLimit, query, _libraryManager, _userDataManager); + return SortAndFilter(items, parentItem, totalRecordLimit, query, _libraryManager, _userDataManager); } public static QueryResult SortAndFilter(IEnumerable items, + BaseItem parentItem, int? totalRecordLimit, InternalItemsQuery query, ILibraryManager libraryManager, @@ -640,9 +648,361 @@ namespace MediaBrowser.Controller.Entities items = items.Where(i => Filter(i, user, query, userDataManager)); + items = FilterVirtualEpisodes(items, + query.IsMissing, + query.IsVirtualUnaired, + query.IsUnaired); + + items = CollapseBoxSetItemsIfNeeded(items, query, parentItem, user); + + // This must be the last filter + if (!string.IsNullOrEmpty(query.AdjacentTo)) + { + items = FilterForAdjacency(items, query.AdjacentTo); + } + return Sort(items, totalRecordLimit, query, libraryManager); } + public static IEnumerable CollapseBoxSetItemsIfNeeded(IEnumerable items, + InternalItemsQuery query, + BaseItem parentItem, + User user) + { + if (CollapseBoxSetItems(query, parentItem, user)) + { + items = BaseItem.CollectionManager.CollapseItemsWithinBoxSets(items, user); + } + + items = ApplyPostCollectionCollapseFilters(query, items, user); + + return items; + } + + private static IEnumerable ApplyPostCollectionCollapseFilters(InternalItemsQuery request, + IEnumerable items, + User user) + { + if (!string.IsNullOrEmpty(request.NameStartsWithOrGreater)) + { + items = items.Where(i => string.Compare(request.NameStartsWithOrGreater, i.SortName, StringComparison.CurrentCultureIgnoreCase) < 1); + } + if (!string.IsNullOrEmpty(request.NameStartsWith)) + { + items = items.Where(i => string.Compare(request.NameStartsWith, i.SortName.Substring(0, 1), StringComparison.CurrentCultureIgnoreCase) == 0); + } + + if (!string.IsNullOrEmpty(request.NameLessThan)) + { + items = items.Where(i => string.Compare(request.NameLessThan, i.SortName, StringComparison.CurrentCultureIgnoreCase) == 1); + } + + return items; + } + + private static bool CollapseBoxSetItems(InternalItemsQuery query, + BaseItem parentItem, + User user) + { + // Could end up stuck in a loop like this + if (parentItem is BoxSet) + { + return false; + } + + var param = query.CollapseBoxSetItems; + + if (!param.HasValue) + { + if (user != null && !user.Configuration.GroupMoviesIntoBoxSets) + { + return false; + } + + if (query.IncludeItemTypes.Contains("Movie", StringComparer.OrdinalIgnoreCase)) + { + param = true; + } + } + + return param.HasValue && param.Value && AllowBoxSetCollapsing(query); + } + + private static bool AllowBoxSetCollapsing(InternalItemsQuery request) + { + if (request.IsFavorite.HasValue) + { + return false; + } + if (request.IsFavoriteOrLiked.HasValue) + { + return false; + } + if (request.IsLiked.HasValue) + { + return false; + } + if (request.IsPlayed.HasValue) + { + return false; + } + if (request.IsResumable.HasValue) + { + return false; + } + if (request.IsFolder.HasValue) + { + return false; + } + + if (request.AllGenres.Length > 0) + { + return false; + } + + if (request.Genres.Length > 0) + { + return false; + } + + if (request.HasImdbId.HasValue) + { + return false; + } + + if (request.HasOfficialRating.HasValue) + { + return false; + } + + if (request.HasOverview.HasValue) + { + return false; + } + + if (request.HasParentalRating.HasValue) + { + return false; + } + + if (request.HasSpecialFeature.HasValue) + { + return false; + } + + if (request.HasSubtitles.HasValue) + { + return false; + } + + if (request.HasThemeSong.HasValue) + { + return false; + } + + if (request.HasThemeVideo.HasValue) + { + return false; + } + + if (request.HasTmdbId.HasValue) + { + return false; + } + + if (request.HasTrailer.HasValue) + { + return false; + } + + if (request.ImageTypes.Length > 0) + { + return false; + } + + if (request.Is3D.HasValue) + { + return false; + } + + if (request.IsHD.HasValue) + { + return false; + } + + if (request.IsInBoxSet.HasValue) + { + return false; + } + + if (request.IsLocked.HasValue) + { + return false; + } + + if (request.IsPlaceHolder.HasValue) + { + return false; + } + + if (request.IsPlayed.HasValue) + { + return false; + } + + if (request.IsUnidentified.HasValue) + { + return false; + } + + if (request.IsYearMismatched.HasValue) + { + return false; + } + + if (!string.IsNullOrWhiteSpace(request.Person)) + { + return false; + } + + if (request.Studios.Length > 0) + { + return false; + } + + if (request.VideoTypes.Length > 0) + { + return false; + } + + if (request.Years.Length > 0) + { + return false; + } + + return true; + } + + public static IEnumerable FilterVirtualEpisodes( + IEnumerable items, + bool? isMissing, + bool? isVirtualUnaired, + bool? isUnaired) + { + items = FilterVirtualSeasons(items, isMissing, isVirtualUnaired, isUnaired); + + if (isMissing.HasValue) + { + var val = isMissing.Value; + items = items.Where(i => + { + var e = i as Episode; + if (e != null) + { + return e.IsMissingEpisode == val; + } + return true; + }); + } + + if (isUnaired.HasValue) + { + var val = isUnaired.Value; + items = items.Where(i => + { + var e = i as Episode; + if (e != null) + { + return e.IsUnaired == val; + } + return true; + }); + } + + if (isVirtualUnaired.HasValue) + { + var val = isVirtualUnaired.Value; + items = items.Where(i => + { + var e = i as Episode; + if (e != null) + { + return e.IsVirtualUnaired == val; + } + return true; + }); + } + + return items; + } + + private static IEnumerable FilterVirtualSeasons( + IEnumerable items, + bool? isMissing, + bool? isVirtualUnaired, + bool? isUnaired) + { + if (isMissing.HasValue && isVirtualUnaired.HasValue) + { + if (!isMissing.Value && !isVirtualUnaired.Value) + { + return items.Where(i => + { + var e = i as Season; + if (e != null) + { + return !e.IsMissingOrVirtualUnaired; + } + return true; + }); + } + } + + if (isMissing.HasValue) + { + var val = isMissing.Value; + items = items.Where(i => + { + var e = i as Season; + if (e != null) + { + return e.IsMissingSeason == val; + } + return true; + }); + } + + if (isUnaired.HasValue) + { + var val = isUnaired.Value; + items = items.Where(i => + { + var e = i as Season; + if (e != null) + { + return e.IsUnaired == val; + } + return true; + }); + } + + if (isVirtualUnaired.HasValue) + { + var val = isVirtualUnaired.Value; + items = items.Where(i => + { + var e = i as Season; + if (e != null) + { + return e.IsVirtualUnaired == val; + } + return true; + }); + } + + return items; + } + public static QueryResult Sort(IEnumerable items, int? totalRecordLimit, InternalItemsQuery query, @@ -692,7 +1052,7 @@ namespace MediaBrowser.Controller.Entities { return false; } - + if (query.IsFolder.HasValue && query.IsFolder.Value != item.IsFolder) { return false; @@ -755,6 +1115,334 @@ namespace MediaBrowser.Controller.Entities } } + if (query.IsInBoxSet.HasValue) + { + var val = query.IsInBoxSet.Value; + if (item.Parents.OfType().Any() != val) + { + return false; + } + } + + // Filter by Video3DFormat + if (query.Is3D.HasValue) + { + var val = query.Is3D.Value; + var video = item as Video; + + if (video == null || val != video.Video3DFormat.HasValue) + { + return false; + } + } + + if (query.IsHD.HasValue) + { + var val = query.IsHD.Value; + var video = item as Video; + + if (video == null || val != video.IsHD) + { + return false; + } + } + + if (query.IsUnidentified.HasValue) + { + var val = query.IsUnidentified.Value; + if (item.IsUnidentified != val) + { + return false; + } + } + + if (query.IsLocked.HasValue) + { + var val = query.IsLocked.Value; + if (item.IsLocked != val) + { + return false; + } + } + + if (query.HasOverview.HasValue) + { + var filterValue = query.HasOverview.Value; + + var hasValue = !string.IsNullOrEmpty(item.Overview); + + if (hasValue != filterValue) + { + return false; + } + } + + if (query.HasImdbId.HasValue) + { + var filterValue = query.HasImdbId.Value; + + var hasValue = !string.IsNullOrEmpty(item.GetProviderId(MetadataProviders.Imdb)); + + if (hasValue != filterValue) + { + return false; + } + } + + if (query.HasTmdbId.HasValue) + { + var filterValue = query.HasTmdbId.Value; + + var hasValue = !string.IsNullOrEmpty(item.GetProviderId(MetadataProviders.Tmdb)); + + if (hasValue != filterValue) + { + return false; + } + } + + if (query.HasTvdbId.HasValue) + { + var filterValue = query.HasTvdbId.Value; + + var hasValue = !string.IsNullOrEmpty(item.GetProviderId(MetadataProviders.Tvdb)); + + if (hasValue != filterValue) + { + return false; + } + } + + if (query.IsYearMismatched.HasValue) + { + var filterValue = query.IsYearMismatched.Value; + + if (IsYearMismatched(item) != filterValue) + { + return false; + } + } + + if (query.HasOfficialRating.HasValue) + { + var filterValue = query.HasOfficialRating.Value; + + var hasValue = !string.IsNullOrEmpty(item.OfficialRating); + + if (hasValue != filterValue) + { + return false; + } + } + + if (query.IsPlaceHolder.HasValue) + { + var filterValue = query.IsPlaceHolder.Value; + + var isPlaceHolder = false; + + var hasPlaceHolder = item as ISupportsPlaceHolders; + + if (hasPlaceHolder != null) + { + isPlaceHolder = hasPlaceHolder.IsPlaceHolder; + } + + if (isPlaceHolder != filterValue) + { + return false; + } + } + + if (query.HasSpecialFeature.HasValue) + { + var filterValue = query.HasSpecialFeature.Value; + + var movie = item as IHasSpecialFeatures; + + if (movie != null) + { + var ok = filterValue + ? movie.SpecialFeatureIds.Count > 0 + : movie.SpecialFeatureIds.Count == 0; + + if (!ok) + { + return false; + } + } + else + { + return false; + } + } + + if (query.HasSubtitles.HasValue) + { + var val = query.HasSubtitles.Value; + + var video = item as Video; + + if (video == null || val != video.HasSubtitles) + { + return false; + } + } + + if (query.HasParentalRating.HasValue) + { + var val = query.HasParentalRating.Value; + + var rating = item.CustomRating; + + if (string.IsNullOrEmpty(rating)) + { + rating = item.OfficialRating; + } + + if (val) + { + if (string.IsNullOrEmpty(rating)) + { + return false; + } + } + else + { + if (!string.IsNullOrEmpty(rating)) + { + return false; + } + } + } + + if (query.HasTrailer.HasValue) + { + var val = query.HasTrailer.Value; + var trailerCount = 0; + + var hasTrailers = item as IHasTrailers; + if (hasTrailers != null) + { + trailerCount = hasTrailers.LocalTrailerIds.Count; + } + + var ok = val ? trailerCount > 0 : trailerCount == 0; + + if (!ok) + { + return false; + } + } + + if (query.HasThemeSong.HasValue) + { + var filterValue = query.HasThemeSong.Value; + + var themeCount = 0; + var iHasThemeMedia = item as IHasThemeMedia; + + if (iHasThemeMedia != null) + { + themeCount = iHasThemeMedia.ThemeSongIds.Count; + } + var ok = filterValue ? themeCount > 0 : themeCount == 0; + + if (!ok) + { + return false; + } + } + + if (query.HasThemeVideo.HasValue) + { + var filterValue = query.HasThemeVideo.Value; + + var themeCount = 0; + var iHasThemeMedia = item as IHasThemeMedia; + + if (iHasThemeMedia != null) + { + themeCount = iHasThemeMedia.ThemeVideoIds.Count; + } + var ok = filterValue ? themeCount > 0 : themeCount == 0; + + if (!ok) + { + return false; + } + } + + // Apply genre filter + if (query.Genres.Length > 0 && !(query.Genres.Any(v => item.Genres.Contains(v, StringComparer.OrdinalIgnoreCase)))) + { + return false; + } + + // Apply genre filter + if (query.AllGenres.Length > 0 && !query.AllGenres.All(v => item.Genres.Contains(v, StringComparer.OrdinalIgnoreCase))) + { + return false; + } + + // Filter by VideoType + if (query.VideoTypes.Length > 0) + { + var video = item as Video; + if (video == null || !query.VideoTypes.Contains(video.VideoType)) + { + return false; + } + } + + if (query.ImageTypes.Length > 0 && !query.ImageTypes.Any(item.HasImage)) + { + return false; + } + + // Apply studio filter + if (query.Studios.Length > 0 && !query.Studios.Any(v => item.Studios.Contains(v, StringComparer.OrdinalIgnoreCase))) + { + return false; + } + + // Apply year filter + if (query.Years.Length > 0) + { + if (!(item.ProductionYear.HasValue && query.Years.Contains(item.ProductionYear.Value))) + { + return false; + } + } + + // Apply person filter + if (!string.IsNullOrEmpty(query.Person)) + { + var personTypes = query.PersonTypes; + + if (personTypes.Length == 0) + { + if (!(item.People.Any(p => string.Equals(p.Name, query.Person, StringComparison.OrdinalIgnoreCase)))) + { + return false; + } + } + else + { + var types = personTypes; + + var ok = new[] { item }.Any(i => + i.People != null && + i.People.Any(p => + p.Name.Equals(query.Person, StringComparison.OrdinalIgnoreCase) && (types.Contains(p.Type, StringComparer.OrdinalIgnoreCase) || types.Contains(p.Role, StringComparer.OrdinalIgnoreCase)))); + + if (!ok) + { + return false; + } + } + } + return true; } @@ -768,7 +1456,7 @@ namespace MediaBrowser.Controller.Entities .Where(i => !excludeFolderIds.Contains(i.Id) && !UserView.IsExcludedFromGrouping(i)); } - private IEnumerable GetMediaFolders(User user, string[] viewTypes) + private IEnumerable GetMediaFolders(User user, IEnumerable viewTypes) { return GetMediaFolders(user) .Where(i => @@ -824,5 +1512,58 @@ namespace MediaBrowser.Controller.Entities return view; } + + public static bool IsYearMismatched(BaseItem item) + { + if (item.ProductionYear.HasValue) + { + var path = item.Path; + + if (!string.IsNullOrEmpty(path)) + { + int? yearInName; + string name; + NameParser.ParseName(Path.GetFileName(path), out name, out yearInName); + + // Go up a level if we didn't get a year + if (!yearInName.HasValue) + { + NameParser.ParseName(Path.GetFileName(Path.GetDirectoryName(path)), out name, out yearInName); + } + + if (yearInName.HasValue) + { + return yearInName.Value != item.ProductionYear.Value; + } + } + } + + return false; + } + + public static IEnumerable FilterForAdjacency(IEnumerable items, string adjacentToId) + { + var list = items.ToList(); + + var adjacentToIdGuid = new Guid(adjacentToId); + var adjacentToItem = list.FirstOrDefault(i => i.Id == adjacentToIdGuid); + + var index = list.IndexOf(adjacentToItem); + + var previousId = Guid.Empty; + var nextId = Guid.Empty; + + if (index > 0) + { + previousId = list[index - 1].Id; + } + + if (index < list.Count - 1) + { + nextId = list[index + 1].Id; + } + + return list.Where(i => i.Id == previousId || i.Id == nextId || i.Id == adjacentToIdGuid); + } } } diff --git a/MediaBrowser.Model/Dlna/StreamInfo.cs b/MediaBrowser.Model/Dlna/StreamInfo.cs index 550bb3cfb3..4d6b301d1e 100644 --- a/MediaBrowser.Model/Dlna/StreamInfo.cs +++ b/MediaBrowser.Model/Dlna/StreamInfo.cs @@ -91,6 +91,13 @@ namespace MediaBrowser.Model.Dlna throw new ArgumentNullException(baseUrl); } + if (IsDirectStream && MediaSource != null && MediaSource.Protocol == MediaProtocol.Http) + { + if (MediaSource.RequiredHttpHeaders.Count == 0) + { + } + } + string dlnaCommand = BuildDlnaParam(this); string extension = string.IsNullOrEmpty(Container) ? string.Empty : "." + Container; diff --git a/MediaBrowser.Providers/Movies/MovieDbProvider.cs b/MediaBrowser.Providers/Movies/MovieDbProvider.cs index 40eb386697..cd0f142005 100644 --- a/MediaBrowser.Providers/Movies/MovieDbProvider.cs +++ b/MediaBrowser.Providers/Movies/MovieDbProvider.cs @@ -273,13 +273,17 @@ namespace MediaBrowser.Providers.Movies languages.Add("en"); } + var firstLetter = string.IsNullOrWhiteSpace(preferredLanguage) + ? string.Empty + : preferredLanguage.Substring(0, 1); + var allLanguages = localization.GetCultures() .Select(i => i.TwoLetterISOLanguageName) .Distinct(StringComparer.OrdinalIgnoreCase) - .Where(i => !languages.Contains(i, StringComparer.OrdinalIgnoreCase)) + .Where(i => !languages.Contains(i, StringComparer.OrdinalIgnoreCase) && i.StartsWith(firstLetter, StringComparison.OrdinalIgnoreCase)) .ToList(); - languages.AddRange(allLanguages); + //languages.AddRange(allLanguages); return string.Join(",", languages.ToArray()); } diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs b/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs index c8aa90b997..af52486089 100644 --- a/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs +++ b/MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs @@ -212,12 +212,10 @@ namespace MediaBrowser.Server.Implementations.Channels } var itemId = item.Id.ToString("N"); - var sources = await _manager.GetChannelItemMediaSources(itemId, cancellationToken) + var sources = await _manager.GetChannelItemMediaSources(itemId, false, cancellationToken) .ConfigureAwait(false); - var list = sources.ToList(); - - var cachedVersions = list.Where(i => i.Protocol == MediaProtocol.File).ToList(); + var cachedVersions = sources.Where(i => i.Protocol == MediaProtocol.File).ToList(); if (cachedVersions.Count > 0) { @@ -225,13 +223,6 @@ namespace MediaBrowser.Server.Implementations.Channels return; } - var source = list.FirstOrDefault(i => i.Protocol == MediaProtocol.Http); - - if (source == null) - { - return; - } - var channelItem = (IChannelMediaItem)item; var destination = Path.Combine(path, channelItem.ChannelId, itemId); diff --git a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs index 8fb6bb4343..b9d2182150 100644 --- a/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs +++ b/MediaBrowser.Server.Implementations/Channels/ChannelManager.cs @@ -244,7 +244,7 @@ namespace MediaBrowser.Server.Implementations.Channels return item; } - public async Task> GetChannelItemMediaSources(string id, CancellationToken cancellationToken) + public async Task> GetChannelItemMediaSources(string id, bool includeDynamicSources, CancellationToken cancellationToken) { var item = (IChannelMediaItem)_libraryManager.GetItemById(id); @@ -255,7 +255,7 @@ namespace MediaBrowser.Server.Implementations.Channels IEnumerable results; - if (requiresCallback != null) + if (requiresCallback != null && includeDynamicSources) { results = await GetChannelItemMediaSourcesInternal(requiresCallback, item.ExternalId, cancellationToken) .ConfigureAwait(false); @@ -374,6 +374,18 @@ namespace MediaBrowser.Server.Implementations.Channels Id = id }; + var bitrate = (info.AudioBitrate ?? 0) + (info.VideoBitrate ?? 0); + + if (bitrate > 0) + { + source.Bitrate = bitrate; + } + + if (item is ChannelVideoItem && info.Protocol != MediaProtocol.Rtmp) + { + + } + return source; } @@ -1447,7 +1459,7 @@ namespace MediaBrowser.Server.Implementations.Channels IProgress progress, CancellationToken cancellationToken) { var itemId = item.Id.ToString("N"); - var sources = await GetChannelItemMediaSources(itemId, cancellationToken) + var sources = await GetChannelItemMediaSources(itemId, true, cancellationToken) .ConfigureAwait(false); var list = sources.Where(i => i.Protocol == MediaProtocol.Http).ToList(); diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/de.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/de.json index 15f4099ec7..070d7a9666 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/de.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/de.json @@ -519,7 +519,7 @@ "MediaInfoLongitude": "L\u00e4nge", "MediaInfoShutterSpeed": "Verschlusszeit", "MediaInfoSoftware": "Software", - "HeaderIfYouLikeCheckTheseOut": "Wenn du {0} magst, schau dir einmal diese an...", + "HeaderIfYouLikeCheckTheseOut": "Wenn du {0} magst, schau dir einmal das an...", "HeaderPlotKeywords": "Handlungsstichworte", "HeaderMovies": "Filme", "HeaderAlbums": "Alben", diff --git a/MediaBrowser.Server.Implementations/Localization/JavaScript/fr.json b/MediaBrowser.Server.Implementations/Localization/JavaScript/fr.json index 5384b76cf9..e3e3b41871 100644 --- a/MediaBrowser.Server.Implementations/Localization/JavaScript/fr.json +++ b/MediaBrowser.Server.Implementations/Localization/JavaScript/fr.json @@ -469,7 +469,7 @@ "LabelResumePoint": "Point de reprise", "ValueOneMovie": "1 Film", "ValueMovieCount": "{0} films", - "ValueOneTrailer": "1 Bande Annonce", + "ValueOneTrailer": "1 bande-annonce", "ValueTrailerCount": "{0} bandes-annonces", "ValueOneSeries": "1 S\u00e9rie", "ValueSeriesCount": "{0} series", diff --git a/MediaBrowser.Server.Implementations/Localization/Server/fr.json b/MediaBrowser.Server.Implementations/Localization/Server/fr.json index f40f8acc96..4ae3a5d0db 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/fr.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/fr.json @@ -1020,7 +1020,7 @@ "LabelChannelDownloadPathHelp": "Sp\u00e9cifiez un chemin de t\u00e9l\u00e9chargements personnalis\u00e9 si besoin. Laissez vide pour t\u00e9l\u00e9charger dans un r\u00e9pertoire interne du programme.", "LabelChannelDownloadAge": "Supprimer le contenu apr\u00e8s : (jours)", "LabelChannelDownloadAgeHelp": "Le contenu t\u00e9l\u00e9charg\u00e9 plus vieux sera supprim\u00e9. Par contre, il sera toujours disponible par flux Internet (en ligne).", - "ChannelSettingsFormHelp": "Installer des cha\u00eenes comme \"Trailers\" and \"Vimeo\" par le catalogue de Plugins.", + "ChannelSettingsFormHelp": "Installer des cha\u00eenes comme \"Trailers\" et \"Vimeo\" dans le catalogue des plugins.", "LabelSelectCollection": "S\u00e9lectionner la collection :", "ButtonOptions": "Options", "ViewTypeMovies": "Films", diff --git a/MediaBrowser.Server.Implementations/Localization/Server/hr.json b/MediaBrowser.Server.Implementations/Localization/Server/hr.json index 7481a19642..949e2a5e78 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/hr.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/hr.json @@ -845,43 +845,43 @@ "OptionSpecialFeatures": "Specijalne opcije", "HeaderCollections": "Kolekcije", "LabelProfileCodecsHelp": "Odvojeno sa to\u010dka-zrezom. Ovo mo\u017ee ostaviti prazno kao bi bilo postavljeno za sve codecs.", - "LabelProfileContainersHelp": "Separated by comma. This can be left empty to apply to all containers.", - "HeaderResponseProfile": "Response Profile", - "LabelType": "Type:", + "LabelProfileContainersHelp": "Odvojeno sa to\u010dka-zrezom. Ovo mo\u017ee ostaviti prazno kao bi bilo postavljeno za sve spremnike.", + "HeaderResponseProfile": "Profil odziva", + "LabelType": "Tip:", "LabelPersonRole": "Role:", "LabelPersonRoleHelp": "Role is generally only applicable to actors.", - "LabelProfileContainer": "Container:", - "LabelProfileVideoCodecs": "Video codecs:", - "LabelProfileAudioCodecs": "Audio codecs:", - "LabelProfileCodecs": "Codecs:", - "HeaderDirectPlayProfile": "Direct Play Profile", - "HeaderTranscodingProfile": "Transcoding Profile", - "HeaderCodecProfile": "Codec Profile", - "HeaderCodecProfileHelp": "Codec profiles indicate the limitations of a device when playing specific codecs. If a limitation applies then the media will be transcoded, even if the codec is configured for direct play.", - "HeaderContainerProfile": "Container Profile", - "HeaderContainerProfileHelp": "Container profiles indicate the limitations of a device when playing specific formats. If a limitation applies then the media will be transcoded, even if the format is configured for direct play.", + "LabelProfileContainer": "Spremnik:", + "LabelProfileVideoCodecs": "Video kodek:", + "LabelProfileAudioCodecs": "Audio kodek:", + "LabelProfileCodecs": "Kodeki:", + "HeaderDirectPlayProfile": "Profil za direktnu reprodukciju", + "HeaderTranscodingProfile": "Profil transkodiranja", + "HeaderCodecProfile": "Profil kodeka", + "HeaderCodecProfileHelp": "Profili kodeka definiraju ograni\u010denja kada ure\u0111aji izvode sadr\u017eaj u specifi\u010dnom kodeku. Ako se ograni\u010denja podudaraju tada \u0107e sadr\u017eaj biti transkodiran, iako je kodek konfiguriran za direktno izvo\u0111enje.", + "HeaderContainerProfile": "Profil spremnika", + "HeaderContainerProfileHelp": "Profil spremnika definira ograni\u010denja za ure\u0111aje kada izvode specifi\u010dne formate. Ako se ograni\u010denja podudaraju tada \u0107e sadr\u017eaj biti transkodiran, iako je format konfiguriran za direktno izvo\u0111enje.", "OptionProfileVideo": "Video", "OptionProfileAudio": "Audio", "OptionProfileVideoAudio": "Video Audio", - "OptionProfilePhoto": "Photo", - "LabelUserLibrary": "User library:", - "LabelUserLibraryHelp": "Select which user library to display to the device. Leave empty to inherit the default setting.", - "OptionPlainStorageFolders": "Display all folders as plain storage folders", - "OptionPlainStorageFoldersHelp": "If enabled, all folders are represented in DIDL as \"object.container.storageFolder\" instead of a more specific type, such as \"object.container.person.musicArtist\".", - "OptionPlainVideoItems": "Display all videos as plain video items", - "OptionPlainVideoItemsHelp": "If enabled, all videos are represented in DIDL as \"object.item.videoItem\" instead of a more specific type, such as \"object.item.videoItem.movie\".", - "LabelSupportedMediaTypes": "Supported Media Types:", - "TabIdentification": "Identification", + "OptionProfilePhoto": "Slika", + "LabelUserLibrary": "Korisni\u010dka biblioteka:", + "LabelUserLibraryHelp": "Odaberite koju korisni\u010dku biblioteku \u0107e te prikazati ure\u0111aju. Ostavite prazno ako \u017eelite preuzeti definirane postavke.", + "OptionPlainStorageFolders": "Prika\u017ei sve mape kako jednostavne mape za skladi\u0161tenje", + "OptionPlainStorageFoldersHelp": "Ako je omogu\u0107eno, sve mape se prezentiraju u DIDL-u kao \"objekt.spremnik.skladi\u0161naMapa\" umjesto vi\u0161e specijaliziranog tipa kao \"objekt.spremnik.osoba.glazbaIzvo\u0111a\u010d\".", + "OptionPlainVideoItems": "Prika\u017ei sav video kao jednostavne video stavke.", + "OptionPlainVideoItemsHelp": "Ako je omogu\u0107eno, sav video se prezentira u DIDL-u kao \"objekt.stavka.videoStavka\" umjesto vi\u0161e specijaliziranog tipa kao \"objekt.stavka.videoStavka.film\".", + "LabelSupportedMediaTypes": "Podr\u017eani tipovi medija:", + "TabIdentification": "Identifikacija", "HeaderIdentification": "Identification", - "TabDirectPlay": "Direct Play", - "TabContainers": "Containers", - "TabCodecs": "Codecs", - "TabResponses": "Responses", - "HeaderProfileInformation": "Profile Information", - "LabelEmbedAlbumArtDidl": "Embed album art in Didl", - "LabelEmbedAlbumArtDidlHelp": "Some devices prefer this method for obtaining album art. Others may fail to play with this option enabled.", - "LabelAlbumArtPN": "Album art PN:", - "LabelAlbumArtHelp": "PN used for album art, within the dlna:profileID attribute on upnp:albumArtURI. Some clients require a specific value, regardless of the size of the image.", + "TabDirectPlay": "Direktna reprodukcija", + "TabContainers": "Spremnik", + "TabCodecs": "Kodek", + "TabResponses": "Odazivi", + "HeaderProfileInformation": "Informacija profila", + "LabelEmbedAlbumArtDidl": "Ugradi grafike albuma u Didl", + "LabelEmbedAlbumArtDidlHelp": "Neki ure\u0111aji podr\u017eavaju ovu metodu za prikaz grafike albuma. Drugi bi mogli imati problema sa ovom opcijom uklju\u010denom.", + "LabelAlbumArtPN": "Grafika albuma PN:", + "LabelAlbumArtHelp": "PN se koristi za grafiku albuma sa dlna:profilID atributom na upnp:albumGrafikaURI. Neki klijenti zahtijevaju specifi\u010dnu vrijednost bez obzira na veli\u010dinu slike.", "LabelAlbumArtMaxWidth": "Album art max width:", "LabelAlbumArtMaxWidthHelp": "Max resolution of album art exposed via upnp:albumArtURI.", "LabelAlbumArtMaxHeight": "Album art max height:", diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index f08a7771b9..17c5b108a4 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -218,6 +218,7 @@ namespace MediaBrowser.ServerApplication private IAuthenticationRepository AuthenticationRepository { get; set; } private ISyncRepository SyncRepository { get; set; } private ITVSeriesManager TVSeriesManager { get; set; } + private ICollectionManager CollectionManager { get; set; } private readonly StartupOptions _startupOptions; private readonly string _remotePackageName; @@ -494,8 +495,8 @@ namespace MediaBrowser.ServerApplication var connectionManager = new ConnectionManager(dlnaManager, ServerConfigurationManager, LogManager.GetLogger("UpnpConnectionManager"), HttpClient); RegisterSingleInstance(connectionManager); - var collectionManager = new CollectionManager(LibraryManager, FileSystemManager, LibraryMonitor, LogManager.GetLogger("CollectionManager")); - RegisterSingleInstance(collectionManager); + CollectionManager = new CollectionManager(LibraryManager, FileSystemManager, LibraryMonitor, LogManager.GetLogger("CollectionManager")); + RegisterSingleInstance(CollectionManager); var playlistManager = new PlaylistManager(LibraryManager, FileSystemManager, LibraryMonitor, LogManager.GetLogger("PlaylistManager"), UserManager); RegisterSingleInstance(playlistManager); @@ -700,6 +701,7 @@ namespace MediaBrowser.ServerApplication BaseItem.LiveTvManager = LiveTvManager; Folder.UserViewManager = UserViewManager; UserView.TVSeriesManager = TVSeriesManager; + BaseItem.CollectionManager = CollectionManager; } ///