diff options
44 files changed, 1329 insertions, 485 deletions
diff --git a/MediaBrowser.Api/Images/ImageService.cs b/MediaBrowser.Api/Images/ImageService.cs index 2213a5af1..2aa680061 100644 --- a/MediaBrowser.Api/Images/ImageService.cs +++ b/MediaBrowser.Api/Images/ImageService.cs @@ -39,8 +39,8 @@ namespace MediaBrowser.Api.Images [Route("/Items/{Id}/Images/{Type}", "GET")] [Route("/Items/{Id}/Images/{Type}/{Index}", "GET")] - [Route("/Items/{Id}/Images/{Type}/{Index}/{Tag}/{Format}/{MaxWidth}/{MaxHeight}", "GET")] - [Route("/Items/{Id}/Images/{Type}/{Index}/{Tag}/{Format}/{MaxWidth}/{MaxHeight}", "HEAD")] + [Route("/Items/{Id}/Images/{Type}/{Index}/{Tag}/{Format}/{MaxWidth}/{MaxHeight}/{PercentPlayed}", "GET")] + [Route("/Items/{Id}/Images/{Type}/{Index}/{Tag}/{Format}/{MaxWidth}/{MaxHeight}/{PercentPlayed}", "HEAD")] [Api(Description = "Gets an item image")] public class GetItemImage : ImageRequest { @@ -583,7 +583,7 @@ namespace MediaBrowser.Api.Images Width = request.Width, OutputFormat = request.Format, AddPlayedIndicator = request.AddPlayedIndicator, - PercentPlayed = request.PercentPlayed, + PercentPlayed = request.PercentPlayed ?? 0, UnplayedCount = request.UnplayedCount, BackgroundColor = request.BackgroundColor }; diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index 5cb9ebb1b..b0131b5c2 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -169,7 +169,7 @@ <PostBuildEvent> </PostBuildEvent> </PropertyGroup> - <Import Project="$(SolutionDir)\.nuget\NuGet.targets" /> + <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " /> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. Other similar extension points exist, see Microsoft.Common.targets. <Target Name="BeforeBuild"> diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 31a81de73..19f9db636 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -1516,6 +1516,7 @@ namespace MediaBrowser.Api.Playback state.RunTimeTicks = item.RunTimeTicks; state.RemoteHttpHeaders = mediaSource.RequiredHttpHeaders; state.InputBitrate = mediaSource.Bitrate; + state.InputFileSize = mediaSource.Size; mediaStreams = mediaSource.MediaStreams; } else @@ -1530,6 +1531,7 @@ namespace MediaBrowser.Api.Playback state.MediaPath = mediaSource.Path; state.InputProtocol = mediaSource.Protocol; state.InputContainer = mediaSource.Container; + state.InputFileSize = mediaSource.Size; state.InputBitrate = mediaSource.Bitrate; if (item is Video) diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index 997cc7ca4..f0ad6ce5c 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Common.IO; +using System.Linq; +using MediaBrowser.Common.IO; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; @@ -153,7 +154,25 @@ namespace MediaBrowser.Api.Playback.Progressive using (state) { - var throttleLimit = state.InputBitrate.HasValue ? (state.InputBitrate.Value / 8) : 0; + var limits = new List<long>(); + if (state.InputBitrate.HasValue) + { + // Bytes per second + limits.Add((state.InputBitrate.Value / 8)); + } + if (state.InputFileSize.HasValue && state.RunTimeTicks.HasValue) + { + var totalSeconds = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds; + + if (totalSeconds > 1) + { + var timeBasedLimit = state.InputFileSize.Value / totalSeconds; + limits.Add(Convert.ToInt64(timeBasedLimit)); + } + } + + // Take the greater of the above to methods, just to be safe + var throttleLimit = limits.Count > 0 ? limits.Max() : 0; return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions { @@ -166,8 +185,8 @@ namespace MediaBrowser.Api.Playback.Progressive // Pad by 20% to play it safe ThrottleLimit = Convert.ToInt64(1.2 * throttleLimit), - // Three minutes - MinThrottlePosition = throttleLimit * 180 + // 3.5 minutes + MinThrottlePosition = throttleLimit * 210 }); } } diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index 139a78c80..d6933b755 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/MediaBrowser.Api/Playback/StreamState.cs @@ -69,6 +69,7 @@ namespace MediaBrowser.Api.Playback public long? RunTimeTicks; public long? InputBitrate { get; set; } + public long? InputFileSize { get; set; } public string OutputAudioSync = "1"; public string OutputVideoSync = "vfr"; diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs index c32e67216..20a3d3837 100644 --- a/MediaBrowser.Api/TvShowsService.cs +++ b/MediaBrowser.Api/TvShowsService.cs @@ -6,6 +6,7 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; +using MediaBrowser.Controller.TV; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using ServiceStack; @@ -26,7 +27,7 @@ namespace MediaBrowser.Api /// </summary> /// <value>The user id.</value> [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid UserId { get; set; } + public string UserId { get; set; } /// <summary> /// Skips over a given number of items within the results. Use for paging. @@ -195,6 +196,7 @@ namespace MediaBrowser.Api private readonly IItemRepository _itemRepo; private readonly IDtoService _dtoService; + private readonly ITVSeriesManager _tvSeriesManager; /// <summary> /// Initializes a new instance of the <see cref="TvShowsService" /> class. @@ -202,13 +204,14 @@ namespace MediaBrowser.Api /// <param name="userManager">The user manager.</param> /// <param name="userDataManager">The user data repository.</param> /// <param name="libraryManager">The library manager.</param> - public TvShowsService(IUserManager userManager, IUserDataManager userDataManager, ILibraryManager libraryManager, IItemRepository itemRepo, IDtoService dtoService) + public TvShowsService(IUserManager userManager, IUserDataManager userDataManager, ILibraryManager libraryManager, IItemRepository itemRepo, IDtoService dtoService, ITVSeriesManager tvSeriesManager) { _userManager = userManager; _userDataManager = userDataManager; _libraryManager = libraryManager; _itemRepo = itemRepo; _dtoService = dtoService; + _tvSeriesManager = tvSeriesManager; } /// <summary> @@ -270,129 +273,26 @@ namespace MediaBrowser.Api /// <returns>System.Object.</returns> public object Get(GetNextUpEpisodes request) { - var user = _userManager.GetUserById(request.UserId); - - var itemsList = GetNextUpEpisodes(request) - .ToList(); + var result = _tvSeriesManager.GetNextUp(new NextUpQuery + { + Limit = request.Limit, + ParentId = request.ParentId, + SeriesId = request.SeriesId, + StartIndex = request.StartIndex, + UserId = request.UserId + }); - var pagedItems = ApplyPaging(itemsList, request.StartIndex, request.Limit); + var user = _userManager.GetUserById(new Guid(request.UserId)); var fields = request.GetItemFields().ToList(); - var returnItems = pagedItems.Select(i => _dtoService.GetBaseItemDto(i, fields, user)).ToArray(); + var returnItems = result.Items.Select(i => _dtoService.GetBaseItemDto(i, fields, user)).ToArray(); - var result = new ItemsResult + return ToOptimizedSerializedResultUsingCache(new ItemsResult { - TotalRecordCount = itemsList.Count, + TotalRecordCount = result.TotalRecordCount, Items = returnItems - }; - - return ToOptimizedSerializedResultUsingCache(result); - } - - public IEnumerable<Episode> GetNextUpEpisodes(GetNextUpEpisodes request) - { - var items = GetAllLibraryItems(request.UserId, _userManager, _libraryManager, request.ParentId) - .OfType<Series>(); - - // Avoid implicitly captured closure - return GetNextUpEpisodes(request, items); - } - - public IEnumerable<Episode> GetNextUpEpisodes(GetNextUpEpisodes request, IEnumerable<Series> series) - { - var user = _userManager.GetUserById(request.UserId); - - // Avoid implicitly captured closure - var currentUser = user; - - return FilterSeries(request, series) - .AsParallel() - .Select(i => GetNextUp(i, currentUser)) - .Where(i => i.Item1 != null) - .OrderByDescending(i => - { - var episode = i.Item1; - - var seriesUserData = _userDataManager.GetUserData(user.Id, episode.Series.GetUserDataKey()); - - if (seriesUserData.IsFavorite) - { - return 2; - } - - if (seriesUserData.Likes.HasValue) - { - return seriesUserData.Likes.Value ? 1 : -1; - } - - return 0; - }) - .ThenByDescending(i => i.Item2) - .ThenByDescending(i => i.Item1.PremiereDate ?? DateTime.MinValue) - .Select(i => i.Item1); - } - - /// <summary> - /// Gets the next up. - /// </summary> - /// <param name="series">The series.</param> - /// <param name="user">The user.</param> - /// <returns>Task{Episode}.</returns> - private Tuple<Episode, DateTime> GetNextUp(Series series, User user) - { - // Get them in display order, then reverse - var allEpisodes = series.GetSeasons(user, true, true) - .SelectMany(i => i.GetEpisodes(user, true, true)) - .Reverse() - .ToList(); - - Episode lastWatched = null; - var lastWatchedDate = DateTime.MinValue; - Episode nextUp = null; - - // Go back starting with the most recent episodes - foreach (var episode in allEpisodes) - { - var userData = _userDataManager.GetUserData(user.Id, episode.GetUserDataKey()); - - if (userData.Played) - { - if (lastWatched != null || nextUp == null) - { - break; - } - - lastWatched = episode; - lastWatchedDate = userData.LastPlayedDate ?? DateTime.MinValue; - } - else - { - if (episode.LocationType != LocationType.Virtual) - { - nextUp = episode; - } - } - } - - if (lastWatched != null) - { - return new Tuple<Episode, DateTime>(nextUp, lastWatchedDate); - } - - return new Tuple<Episode, DateTime>(null, lastWatchedDate); - } - - private IEnumerable<Series> FilterSeries(GetNextUpEpisodes request, IEnumerable<Series> items) - { - if (!string.IsNullOrWhiteSpace(request.SeriesId)) - { - var id = new Guid(request.SeriesId); - - items = items.Where(i => i.Id == id); - } - - return items; + }); } /// <summary> diff --git a/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj b/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj index 4b0c95616..74c812226 100644 --- a/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj +++ b/MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj @@ -122,7 +122,7 @@ </ItemGroup> <ItemGroup /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> - <Import Project="$(SolutionDir)\.nuget\NuGet.targets" /> + <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " /> <PropertyGroup> <PostBuildEvent Condition=" '$(ConfigurationName)' != 'Release Mono' ">if '$(ConfigurationName)' == 'Release' ( xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\" /y /d /r /i diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj index 661073c89..fd41c7e9e 100644 --- a/MediaBrowser.Common/MediaBrowser.Common.csproj +++ b/MediaBrowser.Common/MediaBrowser.Common.csproj @@ -116,7 +116,7 @@ </ItemGroup> <ItemGroup /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> - <Import Project="$(SolutionDir)\.nuget\NuGet.targets" /> + <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " /> <PropertyGroup> <PostBuildEvent Condition=" '$(ConfigurationName)' != 'Release Mono' ">if '$(ConfigurationName)' == 'Release' ( xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\" /y /d /r /i diff --git a/MediaBrowser.Controller/Channels/Channel.cs b/MediaBrowser.Controller/Channels/Channel.cs index df38259b4..602918b19 100644 --- a/MediaBrowser.Controller/Channels/Channel.cs +++ b/MediaBrowser.Controller/Channels/Channel.cs @@ -1,6 +1,10 @@ using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Channels; +using MediaBrowser.Model.Querying; using System; using System.Linq; +using System.Threading; +using System.Threading.Tasks; namespace MediaBrowser.Controller.Channels { @@ -17,5 +21,31 @@ namespace MediaBrowser.Controller.Channels return base.IsVisible(user); } + + public override async Task<QueryResult<BaseItem>> GetUserItems(UserItemsQuery query) + { + try + { + // Don't blow up here because it could cause parent screens with other content to fail + return await ChannelManager.GetChannelItemsInternal(new ChannelItemQuery + { + ChannelId = Id.ToString("N"), + Limit = query.Limit, + StartIndex = query.StartIndex, + UserId = query.User.Id.ToString("N"), + SortBy = query.SortBy, + SortOrder = query.SortOrder + + }, CancellationToken.None); + } + catch + { + // Already logged at lower levels + return new QueryResult<BaseItem> + { + + }; + } + } } } diff --git a/MediaBrowser.Controller/Channels/ChannelFolderItem.cs b/MediaBrowser.Controller/Channels/ChannelFolderItem.cs index 077138f3c..a7cc80104 100644 --- a/MediaBrowser.Controller/Channels/ChannelFolderItem.cs +++ b/MediaBrowser.Controller/Channels/ChannelFolderItem.cs @@ -1,6 +1,9 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Channels; using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.Querying; +using System.Threading; +using System.Threading.Tasks; namespace MediaBrowser.Controller.Channels { @@ -33,5 +36,32 @@ namespace MediaBrowser.Controller.Channels { return ExternalId; } + + public override async Task<QueryResult<BaseItem>> GetUserItems(UserItemsQuery query) + { + try + { + // Don't blow up here because it could cause parent screens with other content to fail + return await ChannelManager.GetChannelItemsInternal(new ChannelItemQuery + { + ChannelId = ChannelId, + FolderId = Id.ToString("N"), + Limit = query.Limit, + StartIndex = query.StartIndex, + UserId = query.User.Id.ToString("N"), + SortBy = query.SortBy, + SortOrder = query.SortOrder + + }, CancellationToken.None); + } + catch + { + // Already logged at lower levels + return new QueryResult<BaseItem> + { + + }; + } + } } } diff --git a/MediaBrowser.Controller/Channels/ChannelVideoItem.cs b/MediaBrowser.Controller/Channels/ChannelVideoItem.cs index 7a261bc58..10e486e71 100644 --- a/MediaBrowser.Controller/Channels/ChannelVideoItem.cs +++ b/MediaBrowser.Controller/Channels/ChannelVideoItem.cs @@ -11,8 +11,6 @@ namespace MediaBrowser.Controller.Channels { public class ChannelVideoItem : Video, IChannelMediaItem { - public static IChannelManager ChannelManager { get; set; } - public string ExternalId { get; set; } public string ChannelId { get; set; } diff --git a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs index f4a76be00..fbbf21797 100644 --- a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs +++ b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs @@ -34,7 +34,7 @@ namespace MediaBrowser.Controller.Drawing public int? UnplayedCount { get; set; } - public double? PercentPlayed { get; set; } + public double PercentPlayed { get; set; } public string BackgroundColor { get; set; } @@ -52,7 +52,7 @@ namespace MediaBrowser.Controller.Drawing return (!Quality.HasValue || Quality.Value == 100) && IsOutputFormatDefault(originalImagePath) && !AddPlayedIndicator && - !PercentPlayed.HasValue && + PercentPlayed.Equals(0) && !UnplayedCount.HasValue && string.IsNullOrEmpty(BackgroundColor); } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 26b28ec72..3830fa1c1 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1,5 +1,6 @@ using MediaBrowser.Common.Extensions; using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; @@ -240,6 +241,7 @@ namespace MediaBrowser.Controller.Entities public static IFileSystem FileSystem { get; set; } public static IUserDataManager UserDataManager { get; set; } public static ILiveTvManager LiveTvManager { get; set; } + public static IChannelManager ChannelManager { get; set; } /// <summary> /// Returns a <see cref="System.String" /> that represents this instance. diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 356f2b603..3907217be 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -6,6 +6,8 @@ using MediaBrowser.Controller.Localization; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Querying; +using MoreLinq; using System; using System.Collections; using System.Collections.Generic; @@ -14,7 +16,6 @@ using System.Linq; using System.Runtime.Serialization; using System.Threading; using System.Threading.Tasks; -using MoreLinq; namespace MediaBrowser.Controller.Entities { @@ -24,6 +25,7 @@ namespace MediaBrowser.Controller.Entities public class Folder : BaseItem, IHasThemeMedia, IHasTags { public static IUserManager UserManager { get; set; } + public static IUserViewManager UserViewManager { get; set; } public List<Guid> ThemeSongIds { get; set; } public List<Guid> ThemeVideoIds { get; set; } @@ -770,6 +772,24 @@ namespace MediaBrowser.Controller.Entities return item; } + public virtual Task<QueryResult<BaseItem>> GetUserItems(UserItemsQuery query) + { + var user = query.User; + + var items = query.Recursive + ? GetRecursiveChildren(user) + : GetChildren(user, true); + + var result = SortAndFilter(items, query); + + return Task.FromResult(result); + } + + protected QueryResult<BaseItem> SortAndFilter(IEnumerable<BaseItem> items, UserItemsQuery query) + { + return UserViewBuilder.SortAndFilter(items, null, query, LibraryManager, UserDataManager); + } + /// <summary> /// Gets allowed children of an item /// </summary> @@ -944,7 +964,7 @@ namespace MediaBrowser.Controller.Entities .OfType<CollectionFolder>() .SelectMany(i => i.PhysicalLocations) .ToList(); - + return LinkedChildren .Select(i => { @@ -985,10 +1005,10 @@ namespace MediaBrowser.Controller.Entities /// Gets the linked children. /// </summary> /// <returns>IEnumerable{BaseItem}.</returns> - public IEnumerable<Tuple<LinkedChild,BaseItem>> GetLinkedChildrenInfos() + public IEnumerable<Tuple<LinkedChild, BaseItem>> GetLinkedChildrenInfos() { return LinkedChildren - .Select(i => new Tuple<LinkedChild,BaseItem>(i, GetLinkedChild(i))) + .Select(i => new Tuple<LinkedChild, BaseItem>(i, GetLinkedChild(i))) .Where(i => i.Item2 != null); } @@ -1183,7 +1203,7 @@ namespace MediaBrowser.Controller.Entities var isUnplayed = true; var itemUserData = UserDataManager.GetUserData(user.Id, child.GetUserDataKey()); - + // Incrememt totalPercentPlayed if (itemUserData != null) { diff --git a/MediaBrowser.Controller/Entities/UserItemsQuery.cs b/MediaBrowser.Controller/Entities/UserItemsQuery.cs new file mode 100644 index 000000000..ca1c0115d --- /dev/null +++ b/MediaBrowser.Controller/Entities/UserItemsQuery.cs @@ -0,0 +1,35 @@ +using MediaBrowser.Model.Entities; +using System; + +namespace MediaBrowser.Controller.Entities +{ + public class UserItemsQuery + { + public bool Recursive { get; set; } + + public int? StartIndex { get; set; } + + public int? Limit { get; set; } + + public string[] SortBy { get; set; } + + public SortOrder SortOrder { get; set; } + + public User User { get; set; } + + public Func<BaseItem, User, bool> Filter { get; set; } + + public bool? IsFolder { get; set; } + public bool? IsFavorite { get; set; } + public bool? IsPlayed { get; set; } + public bool? IsResumable { get; set; } + + public string[] MediaTypes { get; set; } + + public UserItemsQuery() + { + SortBy = new string[] { }; + MediaTypes = new string[] { }; + } + } +} diff --git a/MediaBrowser.Controller/Entities/UserRootFolder.cs b/MediaBrowser.Controller/Entities/UserRootFolder.cs index 6404e71ec..5c7cb7e23 100644 --- a/MediaBrowser.Controller/Entities/UserRootFolder.cs +++ b/MediaBrowser.Controller/Entities/UserRootFolder.cs @@ -1,5 +1,7 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Library; +using MediaBrowser.Model.Querying; using System; using System.Collections.Generic; using System.Linq; @@ -14,6 +16,17 @@ namespace MediaBrowser.Controller.Entities /// </summary> public class UserRootFolder : Folder { + public override async Task<QueryResult<BaseItem>> GetUserItems(UserItemsQuery query) + { + var result = await UserViewManager.GetUserViews(new UserViewQuery + { + UserId = query.User.Id.ToString("N") + + }, CancellationToken.None).ConfigureAwait(false); + + return SortAndFilter(result, query); + } + /// <summary> /// Get the children of this folder from the actual file system /// </summary> diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs index 2abc71752..9b26ce23c 100644 --- a/MediaBrowser.Controller/Entities/UserView.cs +++ b/MediaBrowser.Controller/Entities/UserView.cs @@ -1,12 +1,9 @@ -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.TV; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.LiveTv; +using MediaBrowser.Model.Querying; using System; using System.Collections.Generic; using System.Linq; -using System.Threading; using System.Threading.Tasks; namespace MediaBrowser.Controller.Entities @@ -14,56 +11,37 @@ namespace MediaBrowser.Controller.Entities public class UserView : Folder { public string ViewType { get; set; } - public static IUserViewManager UserViewManager { get; set; } + public Guid ParentId { get; set; } - public override IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren) + public static ITVSeriesManager TVSeriesManager; + + public override Task<QueryResult<BaseItem>> GetUserItems(UserItemsQuery query) { - var mediaFolders = GetMediaFolders(user); + return new UserViewBuilder(UserViewManager, LiveTvManager, ChannelManager, LibraryManager, Logger, UserDataManager, TVSeriesManager) + .GetUserItems(this, ViewType, query); + } - switch (ViewType) + public override IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren) + { + var result = GetUserItems(new UserItemsQuery { - case CollectionType.LiveTvChannels: - return LiveTvManager.GetInternalChannels(new LiveTvChannelQuery - { - UserId = user.Id.ToString("N") - - }, CancellationToken.None).Result.Items; - case CollectionType.LiveTvRecordingGroups: - return LiveTvManager.GetInternalRecordings(new RecordingQuery - { - UserId = user.Id.ToString("N"), - Status = RecordingStatus.Completed - - }, CancellationToken.None).Result.Items; - case CollectionType.LiveTv: - return GetLiveTvFolders(user).Result; - case CollectionType.Folders: - return user.RootFolder.GetChildren(user, includeLinkedChildren); - case CollectionType.Games: - return mediaFolders.SelectMany(i => i.GetRecursiveChildren(user, includeLinkedChildren)) - .OfType<GameSystem>(); - case CollectionType.BoxSets: - return mediaFolders.SelectMany(i => i.GetRecursiveChildren(user, includeLinkedChildren)) - .OfType<BoxSet>(); - case CollectionType.TvShows: - return mediaFolders.SelectMany(i => i.GetRecursiveChildren(user, includeLinkedChildren)) - .OfType<Series>(); - case CollectionType.Trailers: - return mediaFolders.SelectMany(i => i.GetRecursiveChildren(user, includeLinkedChildren)) - .OfType<Trailer>(); - default: - return mediaFolders.SelectMany(i => i.GetChildren(user, includeLinkedChildren)); - } + User = user + + }).Result; + + return result.Items; } - private async Task<IEnumerable<BaseItem>> GetLiveTvFolders(User user) + public override IEnumerable<BaseItem> GetRecursiveChildren(User user, bool includeLinkedChildren = true) { - var list = new List<BaseItem>(); + var result = GetUserItems(new UserItemsQuery + { + User = user, + Recursive = true - list.Add(await UserViewManager.GetUserView(CollectionType.LiveTvChannels, user, string.Empty, CancellationToken.None).ConfigureAwait(false)); - list.Add(await UserViewManager.GetUserView(CollectionType.LiveTvRecordingGroups, user, string.Empty, CancellationToken.None).ConfigureAwait(false)); + }).Result; - return list; + return result.Items; } protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user) @@ -71,16 +49,6 @@ namespace MediaBrowser.Controller.Entities return GetChildren(user, false); } - private IEnumerable<Folder> GetMediaFolders(User user) - { - var excludeFolderIds = user.Configuration.ExcludeFoldersFromGrouping.Select(i => new Guid(i)).ToList(); - - return user.RootFolder - .GetChildren(user, true, true) - .OfType<Folder>() - .Where(i => !excludeFolderIds.Contains(i.Id) && !IsExcludedFromGrouping(i)); - } - public static bool IsExcludedFromGrouping(Folder folder) { var standaloneTypes = new List<string> diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs new file mode 100644 index 000000000..f11ac70ca --- /dev/null +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -0,0 +1,614 @@ +using MediaBrowser.Controller.Channels; +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.TV; +using MediaBrowser.Model.Channels; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.LiveTv; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Querying; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Entities +{ + public class UserViewBuilder + { + private readonly IChannelManager _channelManager; + private readonly ILiveTvManager _liveTvManager; + private readonly IUserViewManager _userViewManager; + private readonly ILibraryManager _libraryManager; + private readonly ILogger _logger; + private readonly IUserDataManager _userDataManager; + private readonly ITVSeriesManager _tvSeriesManager; + + public UserViewBuilder(IUserViewManager userViewManager, ILiveTvManager liveTvManager, IChannelManager channelManager, ILibraryManager libraryManager, ILogger logger, IUserDataManager userDataManager, ITVSeriesManager tvSeriesManager) + { + _userViewManager = userViewManager; + _liveTvManager = liveTvManager; + _channelManager = channelManager; + _libraryManager = libraryManager; + _logger = logger; + _userDataManager = userDataManager; + _tvSeriesManager = tvSeriesManager; + } + + public async Task<QueryResult<BaseItem>> GetUserItems(Folder parent, string viewType, UserItemsQuery query) + { + var user = query.User; + + switch (viewType) + { + case CollectionType.Channels: + { + var result = await _channelManager.GetChannelsInternal(new ChannelQuery + { + UserId = user.Id.ToString("N"), + Limit = query.Limit, + StartIndex = query.StartIndex + + }, CancellationToken.None).ConfigureAwait(false); + + return GetResult(result); + } + + case CollectionType.LiveTvChannels: + { + var result = await _liveTvManager.GetInternalChannels(new LiveTvChannelQuery + { + UserId = query.User.Id.ToString("N"), + Limit = query.Limit, + StartIndex = query.StartIndex + + }, CancellationToken.None).ConfigureAwait(false); + + return GetResult(result); + } + + case CollectionType.LiveTvNowPlaying: + { + var result = await _liveTvManager.GetRecommendedProgramsInternal(new RecommendedProgramQuery + { + UserId = query.User.Id.ToString("N"), + Limit = query.Limit, + IsAiring = true + + }, CancellationToken.None).ConfigureAwait(false); + + return GetResult(result); + } + + case CollectionType.LiveTvRecordingGroups: + { + var result = await _liveTvManager.GetInternalRecordings(new RecordingQuery + { + UserId = query.User.Id.ToString("N"), + Status = RecordingStatus.Completed, + Limit = query.Limit, + StartIndex = query.StartIndex + + }, CancellationToken.None).ConfigureAwait(false); + + return GetResult(result); + } + + case CollectionType.LiveTv: + { + var result = await GetLiveTvFolders(user).ConfigureAwait(false); + + return GetResult(result, query); + } + + case CollectionType.Folders: + return GetResult(user.RootFolder.GetChildren(user, true), 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<BoxSet>(), query); + + case CollectionType.TvShows: + return await GetTvView(parent, user, query).ConfigureAwait(false); + + case CollectionType.Music: + return await GetMusicFolders(parent, user, query).ConfigureAwait(false); + + case CollectionType.Movies: + return await GetMovieFolders(parent, user, query).ConfigureAwait(false); + + case CollectionType.GameGenres: + return GetGameGenres(parent, user, query); + + case CollectionType.GameSystems: + return GetGameSystems(parent, user, query); + + case CollectionType.LatestGames: + return GetLatestGames(parent, user, query); + + case CollectionType.RecentlyPlayedGames: + return GetRecentlyPlayedGames(parent, user, query); + + case CollectionType.GameFavorites: + return GetFavoriteGames(parent, user, query); + + case CollectionType.TvSeries: + return GetTvSeries(parent, user, query); + + case CollectionType.TvGenres: + return GetTvGenres(parent, user, query); + + case CollectionType.TvResume: + return GetTvResume(parent, user, query); + + case CollectionType.TvNextUp: + return GetTvNextUp(parent, query); + + case CollectionType.TvLatest: + return GetTvLatest(parent, user, query); + + case CollectionType.MovieFavorites: + return GetFavoriteMovies(parent, user, query); + + case CollectionType.MovieLatest: + return GetMovieLatest(parent, user, query); + + case CollectionType.MovieGenres: + return GetMovieGenres(parent, user, query); + + case CollectionType.MovieResume: + return GetMovieResume(parent, user, query); + + case CollectionType.MovieMovies: + return GetMovieMovies(parent, user, query); + + case CollectionType.MovieCollections: + return GetMovieCollections(parent, user, query); + + default: + return GetResult(GetMediaFolders(user).SelectMany(i => i.GetChildren(user, true)), query); + } + } + + private int GetSpecialItemsLimit() + { + return 50; + } + + private async Task<QueryResult<BaseItem>> GetMusicFolders(Folder parent, User user, UserItemsQuery query) + { + if (query.Recursive) + { + return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Music }), query); + } + + return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Music }).OfType<MusicArtist>(), query); + } + + private async Task<QueryResult<BaseItem>> GetMovieFolders(Folder parent, User user, UserItemsQuery 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); + } + + var list = new List<BaseItem>(); + + list.Add(await GetUserView(CollectionType.MovieResume, user, "0", parent).ConfigureAwait(false)); + list.Add(await GetUserView(CollectionType.MovieLatest, user, "1", parent).ConfigureAwait(false)); + list.Add(await GetUserView(CollectionType.MovieMovies, user, "2", parent).ConfigureAwait(false)); + list.Add(await GetUserView(CollectionType.MovieCollections, user, "3", parent).ConfigureAwait(false)); + list.Add(await GetUserView(CollectionType.MovieFavorites, user, "4", parent).ConfigureAwait(false)); + list.Add(await GetUserView(CollectionType.MovieGenres, user, "5", parent).ConfigureAwait(false)); + + return GetResult(list, query); + } + + private QueryResult<BaseItem> GetFavoriteMovies(Folder parent, User user, UserItemsQuery query) + { + query.IsFavorite = true; + + return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty }).Where(i => i is Movie), query); + } + + private QueryResult<BaseItem> GetMovieMovies(Folder parent, User user, UserItemsQuery query) + { + return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty }).Where(i => i is Movie), query); + } + + private QueryResult<BaseItem> GetMovieCollections(Folder parent, User user, UserItemsQuery query) + { + return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty }).Where(i => i is BoxSet), query); + } + + private QueryResult<BaseItem> GetMovieLatest(Folder parent, User user, UserItemsQuery query) + { + 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); + } + + private QueryResult<BaseItem> GetMovieResume(Folder parent, User user, UserItemsQuery query) + { + query.SortBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.SortName }; + 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); + } + + private QueryResult<BaseItem> GetMovieGenres(Folder parent, User user, UserItemsQuery query) + { + var genres = GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty }) + .Where(i => i is Movie) + .SelectMany(i => i.Genres) + .Distinct(StringComparer.OrdinalIgnoreCase) + .Select(i => + { + try + { + return _libraryManager.GetGenre(i); + } + catch + { + // Full exception logged at lower levels + _logger.Error("Error getting genre"); + return null; + } + + }) + .Where(i => i != null); + + return GetResult(genres, query); + } + + private async Task<QueryResult<BaseItem>> GetTvView(Folder parent, User user, UserItemsQuery 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); + } + + var list = new List<BaseItem>(); + + list.Add(await GetUserView(CollectionType.TvResume, user, "0", parent).ConfigureAwait(false)); + list.Add(await GetUserView(CollectionType.TvNextUp, user, "1", parent).ConfigureAwait(false)); + list.Add(await GetUserView(CollectionType.TvLatest, user, "2", parent).ConfigureAwait(false)); + list.Add(await GetUserView(CollectionType.TvSeries, user, "3", parent).ConfigureAwait(false)); + //list.Add(await GetUserView(CollectionType.TvFavorites, user, "4", parent).ConfigureAwait(false)); + list.Add(await GetUserView(CollectionType.TvGenres, user, "5", parent).ConfigureAwait(false)); + + return GetResult(list, query); + } + + private async Task<QueryResult<BaseItem>> GetGameView(User user, Folder parent, UserItemsQuery query) + { + if (query.Recursive) + { + return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Games }), query); + } + + var list = new List<BaseItem>(); + + list.Add(await GetUserView(CollectionType.LatestGames, user, "0", parent).ConfigureAwait(false)); + list.Add(await GetUserView(CollectionType.RecentlyPlayedGames, user, "1", parent).ConfigureAwait(false)); + list.Add(await GetUserView(CollectionType.GameFavorites, user, "2", parent).ConfigureAwait(false)); + list.Add(await GetUserView(CollectionType.GameSystems, user, "3", parent).ConfigureAwait(false)); + list.Add(await GetUserView(CollectionType.GameGenres, user, "4", parent).ConfigureAwait(false)); + + return GetResult(list, query); + } + + private QueryResult<BaseItem> GetLatestGames(Folder parent, User user, UserItemsQuery query) + { + query.SortBy = new[] { ItemSortBy.DateCreated, ItemSortBy.SortName }; + query.SortOrder = SortOrder.Descending; + + return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Games }).OfType<Game>(), GetSpecialItemsLimit(), query); + } + + private QueryResult<BaseItem> GetRecentlyPlayedGames(Folder parent, User user, UserItemsQuery query) + { + query.IsPlayed = true; + query.SortBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.SortName }; + query.SortOrder = SortOrder.Descending; + + return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Games }).OfType<Game>(), GetSpecialItemsLimit(), query); + } + + private QueryResult<BaseItem> GetFavoriteGames(Folder parent, User user, UserItemsQuery query) + { + query.IsFavorite = true; + + return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Games }).OfType<Game>(), query); + } + + private QueryResult<BaseItem> GetTvLatest(Folder parent, User user, UserItemsQuery query) + { + query.SortBy = new[] { ItemSortBy.DateCreated, ItemSortBy.SortName }; + query.SortOrder = SortOrder.Descending; + + return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.TvShows, string.Empty }).OfType<Episode>(), GetSpecialItemsLimit(), query); + } + + private QueryResult<BaseItem> GetTvNextUp(Folder parent, UserItemsQuery query) + { + var parentFolders = GetMediaFolders(parent, query.User, new[] { CollectionType.TvShows, string.Empty }); + + var result = _tvSeriesManager.GetNextUp(new NextUpQuery + { + Limit = query.Limit, + StartIndex = query.StartIndex, + UserId = query.User.Id.ToString("N") + + }, parentFolders); + + return result; + } + + private QueryResult<BaseItem> GetTvResume(Folder parent, User user, UserItemsQuery query) + { + query.SortBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.SortName }; + query.SortOrder = SortOrder.Descending; + query.IsResumable = true; + + return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.TvShows, string.Empty }).OfType<Episode>(), GetSpecialItemsLimit(), query); + } + + private QueryResult<BaseItem> GetTvSeries(Folder parent, User user, UserItemsQuery query) + { + return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.TvShows, string.Empty }).OfType<Series>(), query); + } + + private QueryResult<BaseItem> GetTvGenres(Folder parent, User user, UserItemsQuery query) + { + var genres = GetRecursiveChildren(parent, user, new[] { CollectionType.TvShows, string.Empty }) + .OfType<Series>() + .SelectMany(i => i.Genres) + .Distinct(StringComparer.OrdinalIgnoreCase) + .Select(i => + { + try + { + return _libraryManager.GetGenre(i); + } + catch + { + // Full exception logged at lower levels + _logger.Error("Error getting genre"); + return null; + } + + }) + .Where(i => i != null); + + return GetResult(genres, query); + } + + private QueryResult<BaseItem> GetGameSystems(Folder parent, User user, UserItemsQuery query) + { + return GetResult(GetRecursiveChildren(parent, user, new[] { CollectionType.Games }).OfType<GameSystem>(), query); + } + + private QueryResult<BaseItem> GetGameGenres(Folder parent, User user, UserItemsQuery query) + { + var genres = GetRecursiveChildren(parent, user, new[] { CollectionType.Games }) + .OfType<Game>() + .SelectMany(i => i.Genres) + .Distinct(StringComparer.OrdinalIgnoreCase) + .Select(i => + { + try + { + return _libraryManager.GetGameGenre(i); + } + catch + { + // Full exception logged at lower levels + _logger.Error("Error getting game genre"); + return null; + } + + }) + .Where(i => i != null); + + return GetResult(genres, query); + } + + private QueryResult<BaseItem> GetResult<T>(QueryResult<T> result) + where T : BaseItem + { + return new QueryResult<BaseItem> + { + Items = result.Items, + TotalRecordCount = result.TotalRecordCount + }; + } + + private QueryResult<BaseItem> GetResult<T>(IEnumerable<T> items, + UserItemsQuery query) + where T : BaseItem + { + return GetResult(items, null, query); + } + + private QueryResult<BaseItem> GetResult<T>(IEnumerable<T> items, + int? totalRecordLimit, + UserItemsQuery query) + where T : BaseItem + { + return SortAndFilter(items, totalRecordLimit, query, _libraryManager, _userDataManager); + } + + public static QueryResult<BaseItem> SortAndFilter(IEnumerable<BaseItem> items, + int? totalRecordLimit, + UserItemsQuery query, + ILibraryManager libraryManager, + IUserDataManager userDataManager) + { + var user = query.User; + + items = items.Where(i => Filter(i, user, query, userDataManager)); + + return Sort(items, totalRecordLimit, query, libraryManager); + } + + public static QueryResult<BaseItem> Sort(IEnumerable<BaseItem> items, + int? totalRecordLimit, + UserItemsQuery query, + ILibraryManager libraryManager) + { + var user = query.User; + + items = libraryManager.ReplaceVideosWithPrimaryVersions(items); + + if (query.SortBy.Length > 0) + { + items = libraryManager.Sort(items, user, query.SortBy, query.SortOrder); + } + + var itemsArray = totalRecordLimit.HasValue ? items.Take(totalRecordLimit.Value).ToArray() : items.ToArray(); + var totalCount = itemsArray.Length; + + if (query.Limit.HasValue) + { + itemsArray = itemsArray.Skip(query.StartIndex ?? 0).Take(query.Limit.Value).ToArray(); + } + else if (query.StartIndex.HasValue) + { + itemsArray = itemsArray.Skip(query.StartIndex.Value).ToArray(); + } + + return new QueryResult<BaseItem> + { + TotalRecordCount = totalCount, + Items = itemsArray + }; + } + + private static bool Filter(BaseItem item, User user, UserItemsQuery query, IUserDataManager userDataManager) + { + if (query.MediaTypes.Length > 0 && !query.MediaTypes.Contains(item.MediaType ?? string.Empty, StringComparer.OrdinalIgnoreCase)) + { + return false; + } + + if (query.IsFolder.HasValue && query.IsFolder.Value != item.IsFolder) + { + return false; + } + + if (query.Filter != null && !query.Filter(item, user)) + { + return false; + } + + UserItemData userData = null; + + if (query.IsFavorite.HasValue) + { + userData = userData ?? userDataManager.GetUserData(user.Id, item.GetUserDataKey()); + + if (userData.IsFavorite != query.IsFavorite.Value) + { + return false; + } + } + + if (query.IsResumable.HasValue) + { + userData = userData ?? userDataManager.GetUserData(user.Id, item.GetUserDataKey()); + var isResumable = userData.PlaybackPositionTicks > 0; + + if (isResumable != query.IsResumable.Value) + { + return false; + } + } + + if (query.IsPlayed.HasValue) + { + if (item.IsPlayed(user) != query.IsPlayed.Value) + { + return false; + } + } + + return true; + } + + private IEnumerable<Folder> GetMediaFolders(User user) + { + var excludeFolderIds = user.Configuration.ExcludeFoldersFromGrouping.Select(i => new Guid(i)).ToList(); + + return user.RootFolder + .GetChildren(user, true, true) + .OfType<Folder>() + .Where(i => !excludeFolderIds.Contains(i.Id) && !UserView.IsExcludedFromGrouping(i)); + } + + private IEnumerable<Folder> GetMediaFolders(User user, string[] viewTypes) + { + return GetMediaFolders(user) + .Where(i => + { + var folder = i as ICollectionFolder; + + return folder != null && viewTypes.Contains(folder.CollectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase); + }); + } + + private IEnumerable<Folder> GetMediaFolders(Folder parent, User user, string[] viewTypes) + { + if (parent == null || parent is UserView) + { + return GetMediaFolders(user, viewTypes); + } + + return new[] { parent }; + } + + private IEnumerable<BaseItem> GetRecursiveChildren(Folder parent, User user, string[] viewTypes) + { + if (parent == null || parent is UserView) + { + return GetMediaFolders(user, viewTypes).SelectMany(i => i.GetRecursiveChildren(user)); + } + + return parent.GetRecursiveChildren(user); + } + + private async Task<IEnumerable<BaseItem>> GetLiveTvFolders(User user) + { + var list = new List<BaseItem>(); + + list.Add(await _userViewManager.GetUserView(CollectionType.LiveTvNowPlaying, user, "0", CancellationToken.None).ConfigureAwait(false)); + list.Add(await _userViewManager.GetUserView(CollectionType.LiveTvChannels, user, string.Empty, CancellationToken.None).ConfigureAwait(false)); + list.Add(await _userViewManager.GetUserView(CollectionType.LiveTvRecordingGroups, user, string.Empty, CancellationToken.None).ConfigureAwait(false)); + + return list; + } + + private async Task<UserView> GetUserView(string type, User user, string sortName, Folder parent) + { + var view = await _userViewManager.GetUserView(type, user, sortName, CancellationToken.None) + .ConfigureAwait(false); + + if (parent.Id != view.ParentId) + { + view.ParentId = parent.Id; + await view.UpdateToRepository(ItemUpdateType.MetadataImport, CancellationToken.None) + .ConfigureAwait(false); + } + + return view; + } + } +} diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index b1c6ebffc..370ac2e75 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -245,6 +245,15 @@ namespace MediaBrowser.Controller.LiveTv CancellationToken cancellationToken); /// <summary> + /// Gets the recommended programs internal. + /// </summary> + /// <param name="query">The query.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task<QueryResult<LiveTvProgram>>.</returns> + Task<QueryResult<LiveTvProgram>> GetRecommendedProgramsInternal(RecommendedProgramQuery query, + CancellationToken cancellationToken); + + /// <summary> /// Gets the live tv information. /// </summary> /// <param name="cancellationToken">The cancellation token.</param> diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 6a78fa5d9..2ad950a4f 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -157,7 +157,9 @@ <Compile Include="Entities\IHasAwards.cs" /> <Compile Include="Entities\Photo.cs" /> <Compile Include="Entities\PhotoAlbum.cs" /> + <Compile Include="Entities\UserItemsQuery.cs" /> <Compile Include="Entities\UserView.cs" /> + <Compile Include="Entities\UserViewBuilder.cs" /> <Compile Include="FileOrganization\IFileOrganizationService.cs" /> <Compile Include="Library\DeleteOptions.cs" /> <Compile Include="Library\ILibraryPostScanTask.cs" /> @@ -336,6 +338,7 @@ <Compile Include="Sync\ISyncRepository.cs" /> <Compile Include="Themes\IAppThemeManager.cs" /> <Compile Include="Themes\InternalThemeImage.cs" /> + <Compile Include="TV\ITVSeriesManager.cs" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj"> @@ -360,7 +363,7 @@ xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\" /y /d /r /i <PreBuildEvent> </PreBuildEvent> </PropertyGroup> - <Import Project="$(SolutionDir)\.nuget\NuGet.targets" /> + <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " /> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. Other similar extension points exist, see Microsoft.Common.targets. <Target Name="BeforeBuild"> diff --git a/MediaBrowser.Controller/TV/ITVSeriesManager.cs b/MediaBrowser.Controller/TV/ITVSeriesManager.cs new file mode 100644 index 000000000..3d6e87474 --- /dev/null +++ b/MediaBrowser.Controller/TV/ITVSeriesManager.cs @@ -0,0 +1,24 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Querying; +using System.Collections.Generic; + +namespace MediaBrowser.Controller.TV +{ + public interface ITVSeriesManager + { + /// <summary> + /// Gets the next up. + /// </summary> + /// <param name="query">The query.</param> + /// <returns>QueryResult<BaseItem>.</returns> + QueryResult<BaseItem> GetNextUp(NextUpQuery query); + + /// <summary> + /// Gets the next up. + /// </summary> + /// <param name="request">The request.</param> + /// <param name="parentsFolders">The parents folders.</param> + /// <returns>QueryResult<BaseItem>.</returns> + QueryResult<BaseItem> GetNextUp(NextUpQuery request, IEnumerable<Folder> parentsFolders); + } +} diff --git a/MediaBrowser.Dlna/ContentDirectory/ContentDirectory.cs b/MediaBrowser.Dlna/ContentDirectory/ContentDirectory.cs index f5731b893..f594b4471 100644 --- a/MediaBrowser.Dlna/ContentDirectory/ContentDirectory.cs +++ b/MediaBrowser.Dlna/ContentDirectory/ContentDirectory.cs @@ -1,5 +1,4 @@ using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Drawing; @@ -22,8 +21,6 @@ namespace MediaBrowser.Dlna.ContentDirectory private readonly IDlnaManager _dlna; private readonly IServerConfigurationManager _config; private readonly IUserManager _userManager; - private readonly IUserViewManager _userViewManager; - private readonly IChannelManager _channelManager; public ContentDirectory(IDlnaManager dlna, IUserDataManager userDataManager, @@ -32,7 +29,7 @@ namespace MediaBrowser.Dlna.ContentDirectory IServerConfigurationManager config, IUserManager userManager, ILogger logger, - IHttpClient httpClient, IUserViewManager userViewManager, IChannelManager channelManager) + IHttpClient httpClient) : base(logger, httpClient) { _dlna = dlna; @@ -41,8 +38,6 @@ namespace MediaBrowser.Dlna.ContentDirectory _libraryManager = libraryManager; _config = config; _userManager = userManager; - _userViewManager = userViewManager; - _channelManager = channelManager; } private int SystemUpdateId @@ -78,9 +73,7 @@ namespace MediaBrowser.Dlna.ContentDirectory _userDataManager, user, SystemUpdateId, - _config, - _userViewManager, - _channelManager) + _config) .ProcessControlRequest(request); } diff --git a/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs b/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs index 3d08159d6..34acb4479 100644 --- a/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs +++ b/MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs @@ -1,26 +1,18 @@ using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Playlists; using MediaBrowser.Dlna.Didl; using MediaBrowser.Dlna.Server; using MediaBrowser.Dlna.Service; -using MediaBrowser.Model.Channels; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Library; using MediaBrowser.Model.Logging; using MediaBrowser.Model.Querying; using System; using System.Collections.Generic; using System.Globalization; -using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -45,21 +37,17 @@ namespace MediaBrowser.Dlna.ContentDirectory private readonly DidlBuilder _didlBuilder; private readonly DeviceProfile _profile; - private readonly IUserViewManager _userViewManager; - private readonly IChannelManager _channelManager; - public ControlHandler(ILogger logger, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, IImageProcessor imageProcessor, IUserDataManager userDataManager, User user, int systemUpdateId, IServerConfigurationManager config, IUserViewManager userViewManager, IChannelManager channelManager) + public ControlHandler(ILogger logger, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, IImageProcessor imageProcessor, IUserDataManager userDataManager, User user, int systemUpdateId, IServerConfigurationManager config) : base(config, logger) { _libraryManager = libraryManager; _userDataManager = userDataManager; _user = user; _systemUpdateId = systemUpdateId; - _userViewManager = userViewManager; - _channelManager = channelManager; _profile = profile; - _didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress); + _didlBuilder = new DidlBuilder(profile, user, imageProcessor, serverAddress, userDataManager); } protected override IEnumerable<KeyValuePair<string, string>> GetResult(string methodName, Headers methodParams) @@ -202,7 +190,7 @@ namespace MediaBrowser.Dlna.ContentDirectory { - var childrenResult = (await GetChildrenSorted(folder, user, sortCriteria, start, requested).ConfigureAwait(false)); + var childrenResult = (await GetUserItems(folder, user, sortCriteria, start, requested).ConfigureAwait(false)); totalCount = childrenResult.TotalRecordCount; result.DocumentElement.AppendChild(_didlBuilder.GetFolderElement(result, folder, totalCount, filter, id)); @@ -213,7 +201,7 @@ namespace MediaBrowser.Dlna.ContentDirectory { var folder = (Folder)item; - var childrenResult = (await GetChildrenSorted(folder, user, sortCriteria, start, requested).ConfigureAwait(false)); + var childrenResult = (await GetUserItems(folder, user, sortCriteria, start, requested).ConfigureAwait(false)); totalCount = childrenResult.TotalRecordCount; provided = childrenResult.Items.Length; @@ -223,7 +211,7 @@ namespace MediaBrowser.Dlna.ContentDirectory if (i.IsFolder) { var f = (Folder)i; - var childCount = (await GetChildrenSorted(f, user, sortCriteria, null, 0).ConfigureAwait(false)) + var childCount = (await GetUserItems(f, user, sortCriteria, null, 0).ConfigureAwait(false)) .TotalRecordCount; result.DocumentElement.AppendChild(_didlBuilder.GetFolderElement(result, f, childCount, filter)); @@ -321,216 +309,97 @@ namespace MediaBrowser.Dlna.ContentDirectory private async Task<QueryResult<BaseItem>> GetChildrenSorted(Folder folder, User user, SearchCriteria search, SortCriteria sort, int? startIndex, int? limit) { - // TODO: Make a recursive version of GetChildrenSorted (although sorting isn't needed) - var result = folder.GetRecursiveChildren(user, true); + var sortOrders = new List<string>(); + if (!folder.IsPreSorted) + { + sortOrders.Add(ItemSortBy.SortName); + } - var items = FilterUnsupportedContent(result); + var mediaTypes = new List<string>(); + bool? isFolder = null; if (search.SearchType == SearchType.Audio) { - items = items.OfType<Audio>(); + mediaTypes.Add(MediaType.Audio); + isFolder = false; } else if (search.SearchType == SearchType.Video) { - items = items.OfType<Video>(); + mediaTypes.Add(MediaType.Video); + isFolder = false; } else if (search.SearchType == SearchType.Image) { - items = items.OfType<Photo>(); + mediaTypes.Add(MediaType.Photo); + isFolder = false; } else if (search.SearchType == SearchType.Playlist) { - items = items.OfType<Playlist>(); + //items = items.OfType<Playlist>(); + isFolder = true; } else if (search.SearchType == SearchType.MusicAlbum) { - items = items.OfType<MusicAlbum>(); + //items = items.OfType<MusicAlbum>(); + isFolder = true; } - - items = SortItems(items, user, sort); - - return ToResult(items, startIndex, limit); + + return await folder.GetUserItems(new UserItemsQuery + { + Limit = limit, + StartIndex = startIndex, + SortBy = sortOrders.ToArray(), + SortOrder = sort.SortOrder, + User = user, + Recursive = true, + Filter = FilterUnsupportedContent, + IsFolder = isFolder, + MediaTypes = mediaTypes.ToArray() + + }).ConfigureAwait(false); } - private async Task<QueryResult<BaseItem>> GetChildrenSorted(Folder folder, User user, SortCriteria sort, int? startIndex, int? limit) + private async Task<QueryResult<BaseItem>> GetUserItems(Folder folder, User user, SortCriteria sort, int? startIndex, int? limit) { - if (folder is UserRootFolder) - { - var result = await _userViewManager.GetUserViews(new UserViewQuery - { - UserId = user.Id.ToString("N") - - }, CancellationToken.None).ConfigureAwait(false); - - return ToResult(result, startIndex, limit); - } - - var view = folder as UserView; - - if (view != null) + var sortOrders = new List<string>(); + if (!folder.IsPreSorted) { - var result = await GetUserViewChildren(view, user, sort).ConfigureAwait(false); - - return ToResult(result, startIndex, limit); + sortOrders.Add(ItemSortBy.SortName); } - var channel = folder as Channel; - - if (channel != null) + return await folder.GetUserItems(new UserItemsQuery { - try - { - // Don't blow up here because it could cause parent screens with other content to fail - return await _channelManager.GetChannelItemsInternal(new ChannelItemQuery - { - ChannelId = channel.Id.ToString("N"), - Limit = limit, - StartIndex = startIndex, - UserId = user.Id.ToString("N") - - }, CancellationToken.None); - } - catch - { - // Already logged at lower levels - } - } + Limit = limit, + StartIndex = startIndex, + SortBy = sortOrders.ToArray(), + SortOrder = sort.SortOrder, + User = user, + Filter = FilterUnsupportedContent - var channelFolderItem = folder as ChannelFolderItem; - - if (channelFolderItem != null) - { - try - { - // Don't blow up here because it could cause parent screens with other content to fail - return await _channelManager.GetChannelItemsInternal(new ChannelItemQuery - { - ChannelId = channelFolderItem.ChannelId, - FolderId = channelFolderItem.Id.ToString("N"), - Limit = limit, - StartIndex = startIndex, - UserId = user.Id.ToString("N") - - }, CancellationToken.None); - } - catch - { - // Already logged at lower levels - } - } - - return ToResult(GetPlainFolderChildrenSorted(folder, user, sort), startIndex, limit); + }).ConfigureAwait(false); } - private QueryResult<BaseItem> ToResult(IEnumerable<BaseItem> items, int? startIndex, int? limit) + private bool FilterUnsupportedContent(BaseItem i, User user) { - var list = items.ToArray(); - var totalCount = list.Length; - - if (startIndex.HasValue) + // Unplayable + if (i.LocationType == LocationType.Virtual && !i.IsFolder) { - list = list.Skip(startIndex.Value).ToArray(); + return false; } - if (limit.HasValue) + // Unplayable + var supportsPlaceHolder = i as ISupportsPlaceHolders; + if (supportsPlaceHolder != null && supportsPlaceHolder.IsPlaceHolder) { - list = list.Take(limit.Value).ToArray(); + return false; } - return new QueryResult<BaseItem> + if (i is Game || i is Book) { - Items = list, - TotalRecordCount = totalCount - }; - } - - private async Task<IEnumerable<BaseItem>> GetUserViewChildren(UserView folder, User user, SortCriteria sort) - { - if (string.Equals(folder.ViewType, CollectionType.Channels, StringComparison.OrdinalIgnoreCase)) - { - var result = await _channelManager.GetChannelsInternal(new ChannelQuery() - { - UserId = user.Id.ToString("N") - - }, CancellationToken.None).ConfigureAwait(false); - - return result.Items; + //return false; } - if (string.Equals(folder.ViewType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) - { - return SortItems(folder.GetChildren(user, true).OfType<Series>(), user, sort); - } - if (string.Equals(folder.ViewType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase)) - { - return SortItems(folder.GetRecursiveChildren(user, true).OfType<Movie>(), user, sort); - } - if (string.Equals(folder.ViewType, CollectionType.Music, StringComparison.OrdinalIgnoreCase)) - { - return SortItems(folder.GetChildren(user, true).OfType<MusicArtist>(), user, sort); - } - if (string.Equals(folder.ViewType, CollectionType.Folders, StringComparison.OrdinalIgnoreCase)) - { - return SortItems(folder.GetChildren(user, true), user, sort); - } - if (string.Equals(folder.ViewType, CollectionType.LiveTv, StringComparison.OrdinalIgnoreCase)) - { - return SortItems(folder.GetChildren(user, true), user, sort); - } - if (string.Equals(folder.ViewType, CollectionType.LiveTvRecordingGroups, StringComparison.OrdinalIgnoreCase)) - { - return SortItems(folder.GetChildren(user, true), user, sort); - } - if (string.Equals(folder.ViewType, CollectionType.LiveTvChannels, StringComparison.OrdinalIgnoreCase)) - { - return SortItems(folder.GetChildren(user, true), user, sort); - } - - return GetPlainFolderChildrenSorted(folder, user, sort); - } - - private IEnumerable<BaseItem> GetPlainFolderChildrenSorted(Folder folder, User user, SortCriteria sort) - { - var items = folder.GetChildren(user, true); - - items = FilterUnsupportedContent(items); - - if (folder.IsPreSorted) - { - return items; - } - - return SortItems(items, user, sort); - } - - private IEnumerable<BaseItem> SortItems(IEnumerable<BaseItem> items, User user, SortCriteria sort) - { - return _libraryManager.Sort(items, user, new[] { ItemSortBy.SortName }, sort.SortOrder); - } - - private IEnumerable<BaseItem> FilterUnsupportedContent(IEnumerable<BaseItem> items) - { - return items.Where(i => - { - // Unplayable - if (i.LocationType == LocationType.Virtual && !i.IsFolder) - { - return false; - } - - // Unplayable - var supportsPlaceHolder = i as ISupportsPlaceHolders; - if (supportsPlaceHolder != null && supportsPlaceHolder.IsPlaceHolder) - { - return false; - } - - if (i is Game || i is Book) - { - return false; - } - return true; - }); + return true; } private BaseItem GetItemFromObjectId(string id, User user) diff --git a/MediaBrowser.Dlna/Didl/DidlBuilder.cs b/MediaBrowser.Dlna/Didl/DidlBuilder.cs index 53fac9a93..bcfd566a9 100644 --- a/MediaBrowser.Dlna/Didl/DidlBuilder.cs +++ b/MediaBrowser.Dlna/Didl/DidlBuilder.cs @@ -1,4 +1,4 @@ -using System.IO; +using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Drawing; @@ -6,16 +6,16 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Playlists; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Entities; using System; -using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Linq; using System.Xml; -using MediaBrowser.Common.Extensions; namespace MediaBrowser.Dlna.Didl { @@ -32,12 +32,14 @@ namespace MediaBrowser.Dlna.Didl private readonly IImageProcessor _imageProcessor; private readonly string _serverAddress; private readonly User _user; + private readonly IUserDataManager _userDataManager; - public DidlBuilder(DeviceProfile profile, User user, IImageProcessor imageProcessor, string serverAddress) + public DidlBuilder(DeviceProfile profile, User user, IImageProcessor imageProcessor, string serverAddress, IUserDataManager userDataManager) { _profile = profile; _imageProcessor = imageProcessor; _serverAddress = serverAddress; + _userDataManager = userDataManager; _user = user; } @@ -677,7 +679,20 @@ namespace MediaBrowser.Dlna.Didl var result = element.OwnerDocument; - var albumartUrlInfo = GetImageUrl(imageInfo, _profile.MaxAlbumArtWidth, _profile.MaxAlbumArtHeight, "jpg"); + var playbackPercentage = 0; + + if (item is Video) + { + var userData = _userDataManager.GetUserDataDto(item, _user); + + playbackPercentage = Convert.ToInt32(userData.PlayedPercentage ?? 0); + if (playbackPercentage >= 100) + { + playbackPercentage = 0; + } + } + + var albumartUrlInfo = GetImageUrl(imageInfo, _profile.MaxAlbumArtWidth, _profile.MaxAlbumArtHeight, playbackPercentage, "jpg"); var icon = result.CreateElement("upnp", "albumArtURI", NS_UPNP); var profile = result.CreateAttribute("dlna", "profileID", NS_DLNA); @@ -687,7 +702,7 @@ namespace MediaBrowser.Dlna.Didl element.AppendChild(icon); // TOOD: Remove these default values - var iconUrlInfo = GetImageUrl(imageInfo, _profile.MaxIconWidth ?? 48, _profile.MaxIconHeight ?? 48, "jpg"); + var iconUrlInfo = GetImageUrl(imageInfo, _profile.MaxIconWidth ?? 48, _profile.MaxIconHeight ?? 48, playbackPercentage, "jpg"); icon = result.CreateElement("upnp", "icon", NS_UPNP); icon.InnerText = iconUrlInfo.Url; element.AppendChild(icon); @@ -703,18 +718,19 @@ namespace MediaBrowser.Dlna.Didl } } - AddImageResElement(item, element, 4096, 4096, "jpg", "JPEG_LRG"); - AddImageResElement(item, element, 4096, 4096, "png", "PNG_LRG"); - AddImageResElement(item, element, 1024, 768, "jpg", "JPEG_MED"); - AddImageResElement(item, element, 640, 480, "jpg", "JPEG_SM"); - AddImageResElement(item, element, 160, 160, "jpg", "JPEG_TN"); - AddImageResElement(item, element, 160, 160, "png", "PNG_TN"); + AddImageResElement(item, element, 4096, 4096, playbackPercentage, "jpg", "JPEG_LRG"); + AddImageResElement(item, element, 4096, 4096, playbackPercentage, "png", "PNG_LRG"); + AddImageResElement(item, element, 1024, 768, playbackPercentage, "jpg", "JPEG_MED"); + AddImageResElement(item, element, 640, 480, playbackPercentage, "jpg", "JPEG_SM"); + AddImageResElement(item, element, 160, 160, playbackPercentage, "jpg", "JPEG_TN"); + AddImageResElement(item, element, 160, 160, playbackPercentage, "png", "PNG_TN"); } private void AddImageResElement(BaseItem item, XmlElement element, int maxWidth, int maxHeight, + int playbackPercentage, string format, string org_Pn) { @@ -727,7 +743,7 @@ namespace MediaBrowser.Dlna.Didl var result = element.OwnerDocument; - var albumartUrlInfo = GetImageUrl(imageInfo, maxWidth, maxHeight, format); + var albumartUrlInfo = GetImageUrl(imageInfo, maxWidth, maxHeight, playbackPercentage, format); var res = result.CreateElement(string.Empty, "res", NS_DIDL); @@ -849,16 +865,18 @@ namespace MediaBrowser.Dlna.Didl internal int? Height; } - private ImageUrlInfo GetImageUrl(ImageDownloadInfo info, int maxWidth, int maxHeight, string format) + private ImageUrlInfo GetImageUrl(ImageDownloadInfo info, int maxWidth, int maxHeight, int playbackPercentage, string format) { - var url = string.Format("{0}/Items/{1}/Images/{2}/0/{3}/{4}/{5}/{6}", + var url = string.Format("{0}/Items/{1}/Images/{2}/0/{3}/{4}/{5}/{6}/{7}", _serverAddress, info.ItemId, info.Type, info.ImageTag, format, - maxWidth, - maxHeight); + maxWidth.ToString(CultureInfo.InvariantCulture), + maxHeight.ToString(CultureInfo.InvariantCulture), + playbackPercentage.ToString(CultureInfo.InvariantCulture) + ); var width = info.Width; var height = info.Height; diff --git a/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs b/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs index 8914ae8bf..4d3e2272a 100644 --- a/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs +++ b/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs @@ -34,14 +34,15 @@ namespace MediaBrowser.Dlna.Main private readonly IUserManager _userManager; private readonly IDlnaManager _dlnaManager; private readonly IImageProcessor _imageProcessor; - + private readonly IUserDataManager _userDataManager; + private SsdpHandler _ssdpHandler; private DeviceDiscovery _deviceDiscovery; private readonly List<string> _registeredServerIds = new List<string>(); private bool _dlnaServerStarted; - public DlnaEntryPoint(IServerConfigurationManager config, ILogManager logManager, IServerApplicationHost appHost, INetworkManager network, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepo, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IImageProcessor imageProcessor) + public DlnaEntryPoint(IServerConfigurationManager config, ILogManager logManager, IServerApplicationHost appHost, INetworkManager network, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepo, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IImageProcessor imageProcessor, IUserDataManager userDataManager) { _config = config; _appHost = appHost; @@ -53,6 +54,7 @@ namespace MediaBrowser.Dlna.Main _userManager = userManager; _dlnaManager = dlnaManager; _imageProcessor = imageProcessor; + _userDataManager = userDataManager; _logger = logManager.GetLogger("Dlna"); } @@ -218,7 +220,8 @@ namespace MediaBrowser.Dlna.Main _imageProcessor, _deviceDiscovery, _httpClient, - _config); + _config, + _userDataManager); _manager.Start(); } diff --git a/MediaBrowser.Dlna/PlayTo/PlayToController.cs b/MediaBrowser.Dlna/PlayTo/PlayToController.cs index 9e8d8213f..acada6a11 100644 --- a/MediaBrowser.Dlna/PlayTo/PlayToController.cs +++ b/MediaBrowser.Dlna/PlayTo/PlayToController.cs @@ -32,6 +32,7 @@ namespace MediaBrowser.Dlna.PlayTo private readonly IDlnaManager _dlnaManager; private readonly IUserManager _userManager; private readonly IImageProcessor _imageProcessor; + private readonly IUserDataManager _userDataManager; private readonly DeviceDiscovery _deviceDiscovery; private readonly string _serverAddress; @@ -51,7 +52,7 @@ namespace MediaBrowser.Dlna.PlayTo private Timer _updateTimer; - public PlayToController(SessionInfo session, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, ILogger logger, IDlnaManager dlnaManager, IUserManager userManager, IImageProcessor imageProcessor, string serverAddress, DeviceDiscovery deviceDiscovery) + public PlayToController(SessionInfo session, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, ILogger logger, IDlnaManager dlnaManager, IUserManager userManager, IImageProcessor imageProcessor, string serverAddress, DeviceDiscovery deviceDiscovery, IUserDataManager userDataManager) { _session = session; _itemRepository = itemRepository; @@ -62,6 +63,7 @@ namespace MediaBrowser.Dlna.PlayTo _imageProcessor = imageProcessor; _serverAddress = serverAddress; _deviceDiscovery = deviceDiscovery; + _userDataManager = userDataManager; _logger = logger; } @@ -474,7 +476,7 @@ namespace MediaBrowser.Dlna.PlayTo playlistItem.StreamUrl = playlistItem.StreamInfo.ToUrl(serverAddress); - var itemXml = new DidlBuilder(profile, user, _imageProcessor, serverAddress).GetItemDidl(item, _session.DeviceId, new Filter(), playlistItem.StreamInfo); + var itemXml = new DidlBuilder(profile, user, _imageProcessor, serverAddress, _userDataManager).GetItemDidl(item, _session.DeviceId, new Filter(), playlistItem.StreamInfo); playlistItem.Didl = itemXml; diff --git a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs index a52c38086..891dcec87 100644 --- a/MediaBrowser.Dlna/PlayTo/PlayToManager.cs +++ b/MediaBrowser.Dlna/PlayTo/PlayToManager.cs @@ -29,10 +29,11 @@ namespace MediaBrowser.Dlna.PlayTo private readonly IImageProcessor _imageProcessor; private readonly IHttpClient _httpClient; private readonly IServerConfigurationManager _config; + private readonly IUserDataManager _userDataManager; private readonly DeviceDiscovery _deviceDiscovery; - public PlayToManager(ILogger logger, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, DeviceDiscovery deviceDiscovery, IHttpClient httpClient, IServerConfigurationManager config) + public PlayToManager(ILogger logger, ISessionManager sessionManager, IItemRepository itemRepository, ILibraryManager libraryManager, IUserManager userManager, IDlnaManager dlnaManager, IServerApplicationHost appHost, IImageProcessor imageProcessor, DeviceDiscovery deviceDiscovery, IHttpClient httpClient, IServerConfigurationManager config, IUserDataManager userDataManager) { _logger = logger; _sessionManager = sessionManager; @@ -45,6 +46,7 @@ namespace MediaBrowser.Dlna.PlayTo _deviceDiscovery = deviceDiscovery; _httpClient = httpClient; _config = config; + _userDataManager = userDataManager; } public void Start() @@ -103,7 +105,8 @@ namespace MediaBrowser.Dlna.PlayTo _userManager, _imageProcessor, serverAddress, - _deviceDiscovery); + _deviceDiscovery, + _userDataManager); controller.Init(device); diff --git a/MediaBrowser.Dlna/Ssdp/Datagram.cs b/MediaBrowser.Dlna/Ssdp/Datagram.cs index 2554d33c6..45f571b4b 100644 --- a/MediaBrowser.Dlna/Ssdp/Datagram.cs +++ b/MediaBrowser.Dlna/Ssdp/Datagram.cs @@ -22,6 +22,8 @@ namespace MediaBrowser.Dlna.Ssdp /// </summary> public int SendCount { get; private set; } + public bool HandleBindError { get; set; } + private readonly ILogger _logger; public Datagram(IPEndPoint toEndPoint, IPEndPoint fromEndPoint, ILogger logger, string message, int totalSendCount) @@ -42,7 +44,17 @@ namespace MediaBrowser.Dlna.Ssdp if (FromEndPoint != null) { - client.Bind(FromEndPoint); + try + { + client.Bind(FromEndPoint); + } + catch + { + if (!HandleBindError) + { + throw; + } + } } client.BeginSendTo(msg, 0, msg.Length, SocketFlags.None, ToEndPoint, result => diff --git a/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs b/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs index a237a0d67..bee4a3a56 100644 --- a/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs +++ b/MediaBrowser.Dlna/Ssdp/SsdpHandler.cs @@ -65,7 +65,7 @@ namespace MediaBrowser.Dlna.Ssdp string mx = null; args.Headers.TryGetValue("mx", out mx); int delaySeconds; - if (!string.IsNullOrWhiteSpace(mx) && + if (!string.IsNullOrWhiteSpace(mx) && int.TryParse(mx, NumberStyles.Any, CultureInfo.InvariantCulture, out delaySeconds) && delaySeconds > 0) { @@ -124,18 +124,23 @@ namespace MediaBrowser.Dlna.Ssdp IPEndPoint localAddress, int sendCount = 1) { - SendDatagram(header, values, _ssdpEndp, localAddress, sendCount); + SendDatagram(header, values, _ssdpEndp, localAddress, false, sendCount); } public void SendDatagram(string header, Dictionary<string, string> values, IPEndPoint endpoint, IPEndPoint localAddress, + bool handleBindError, int sendCount = 1) { var msg = new SsdpMessageBuilder().BuildMessage(header, values); - var dgram = new Datagram(endpoint, localAddress, _logger, msg, sendCount); + var dgram = new Datagram(endpoint, localAddress, _logger, msg, sendCount) + { + HandleBindError = handleBindError + }; + if (_messageQueue.Count == 0) { dgram.Send(); @@ -170,10 +175,7 @@ namespace MediaBrowser.Dlna.Ssdp values["ST"] = d.Type; values["USN"] = d.USN; - // Commenting this out because binding to the local ipendpoint often throws an error - //SendDatagram(header, values, endpoint, new IPEndPoint(d.Address, 0)); - - SendDatagram(header, values, endpoint, null); + SendDatagram(header, values, endpoint, new IPEndPoint(d.Address, 0), true); if (_config.GetDlnaConfiguration().EnableDebugLogging) { diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj index 22873d910..cc753298c 100644 --- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj +++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj @@ -91,7 +91,7 @@ </ItemGroup> <ItemGroup /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> - <Import Project="$(SolutionDir)\.nuget\NuGet.targets" /> + <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " /> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. Other similar extension points exist, see Microsoft.Common.targets. <Target Name="BeforeBuild"> diff --git a/MediaBrowser.Model/Entities/CollectionType.cs b/MediaBrowser.Model/Entities/CollectionType.cs index a97cc3b12..76af08e0c 100644 --- a/MediaBrowser.Model/Entities/CollectionType.cs +++ b/MediaBrowser.Model/Entities/CollectionType.cs @@ -26,7 +26,28 @@ public const string Playlists = "playlists"; public const string Folders = "folders"; + public const string LiveTvNowPlaying = "LiveTvNowPlaying"; public const string LiveTvChannels = "LiveTvChannels"; public const string LiveTvRecordingGroups = "LiveTvRecordingGroups"; + + public const string TvSeries = "TvSeries"; + public const string TvGenres = "TvGenres"; + public const string TvLatest = "TvLatest"; + public const string TvNextUp = "TvNextUp"; + public const string TvResume = "TvResume"; + public const string TvFavorites = "TvFavorites"; + + public const string MovieLatest = "MovieLatest"; + public const string MovieResume = "MovieResume"; + public const string MovieMovies = "MovieMovies"; + public const string MovieCollections = "MovieCollections"; + public const string MovieFavorites = "MovieFavorites"; + public const string MovieGenres = "MovieGenres"; + + public const string LatestGames = "LatestGames"; + public const string RecentlyPlayedGames = "RecentlyPlayedGames"; + public const string GameSystems = "GameSystems"; + public const string GameGenres = "GameGenres"; + public const string GameFavorites = "GameFavorites"; } } diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index a760cba05..082f64bd9 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -372,7 +372,7 @@ xcopy "$(TargetPath)" "$(SolutionDir)\Nuget\dlls\net45\" /y /d /r /i )</PostBuildEvent> </PropertyGroup> - <Import Project="$(SolutionDir)\.nuget\NuGet.targets" /> + <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " /> <Import Project="Fody.targets" /> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. Other similar extension points exist, see Microsoft.Common.targets. diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index b69a4bc2f..995547ce6 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -211,7 +211,7 @@ </ItemGroup> <ItemGroup /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> - <Import Project="$(SolutionDir)\.nuget\NuGet.targets" /> + <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " /> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. Other similar extension points exist, see Microsoft.Common.targets. <Target Name="BeforeBuild"> diff --git a/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs b/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs index 504814fe0..924ae12da 100644 --- a/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs +++ b/MediaBrowser.Server.Implementations/Connect/ConnectManager.cs @@ -198,7 +198,7 @@ namespace MediaBrowser.Server.Implementations.Connect private string GetConnectUrl(string handler) { - return "http://mb3admin.com/admin/connect/" + handler; + return "http://mediabrowser.tv:8095/" + handler; } } } diff --git a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs index e66899efa..d222c584e 100644 --- a/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs +++ b/MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs @@ -196,7 +196,7 @@ namespace MediaBrowser.Server.Implementations.Drawing try { - var hasPostProcessing = !string.IsNullOrEmpty(options.BackgroundColor) || options.UnplayedCount.HasValue || options.AddPlayedIndicator || options.PercentPlayed.HasValue; + var hasPostProcessing = !string.IsNullOrEmpty(options.BackgroundColor) || options.UnplayedCount.HasValue || options.AddPlayedIndicator || options.PercentPlayed > 0; using (var fileStream = _fileSystem.GetFileStream(originalImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true)) { @@ -308,7 +308,7 @@ namespace MediaBrowser.Server.Implementations.Drawing /// <param name="options">The options.</param> private void DrawIndicator(Graphics graphics, int imageWidth, int imageHeight, ImageProcessingOptions options) { - if (!options.AddPlayedIndicator && !options.UnplayedCount.HasValue && !options.PercentPlayed.HasValue) + if (!options.AddPlayedIndicator && !options.UnplayedCount.HasValue && options.PercentPlayed.Equals(0)) { return; } @@ -328,11 +328,11 @@ namespace MediaBrowser.Server.Implementations.Drawing new UnplayedCountIndicator().DrawUnplayedCountIndicator(graphics, currentImageSize, options.UnplayedCount.Value); } - if (options.PercentPlayed.HasValue) + if (options.PercentPlayed >= 0) { var currentImageSize = new Size(imageWidth, imageHeight); - new PercentPlayedDrawer().Process(graphics, currentImageSize, options.PercentPlayed.Value); + new PercentPlayedDrawer().Process(graphics, currentImageSize, options.PercentPlayed); } } catch (Exception ex) @@ -437,7 +437,7 @@ namespace MediaBrowser.Server.Implementations.Drawing /// <summary> /// Gets the cache file path based on a set of parameters /// </summary> - private string GetCacheFilePath(string originalPath, ImageSize outputSize, int quality, DateTime dateModified, ImageOutputFormat format, bool addPlayedIndicator, double? percentPlayed, int? unwatchedCount, string backgroundColor) + private string GetCacheFilePath(string originalPath, ImageSize outputSize, int quality, DateTime dateModified, ImageOutputFormat format, bool addPlayedIndicator, double percentPlayed, int? unwatchedCount, string backgroundColor) { var filename = originalPath; @@ -462,9 +462,9 @@ namespace MediaBrowser.Server.Implementations.Drawing hasIndicator = true; } - if (percentPlayed.HasValue) + if (percentPlayed > 0) { - filename += "p=" + percentPlayed.Value; + filename += "p=" + percentPlayed; hasIndicator = true; } diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index d072217af..d2a89e947 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -726,7 +726,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv return result; } - public async Task<QueryResult<ProgramInfoDto>> GetRecommendedPrograms(RecommendedProgramQuery query, CancellationToken cancellationToken) + public async Task<QueryResult<LiveTvProgram>> GetRecommendedProgramsInternal(RecommendedProgramQuery query, CancellationToken cancellationToken) { IEnumerable<LiveTvProgram> programs = _programs.Values; @@ -771,7 +771,24 @@ namespace MediaBrowser.Server.Implementations.LiveTv await RefreshIfNeeded(programList, cancellationToken).ConfigureAwait(false); - var returnArray = programList + var returnArray = programList.ToArray(); + + var result = new QueryResult<LiveTvProgram> + { + Items = returnArray, + TotalRecordCount = returnArray.Length + }; + + return result; + } + + public async Task<QueryResult<ProgramInfoDto>> GetRecommendedPrograms(RecommendedProgramQuery query, CancellationToken cancellationToken) + { + var internalResult = await GetRecommendedProgramsInternal(query, cancellationToken).ConfigureAwait(false); + + var user = _userManager.GetUserById(new Guid(query.UserId)); + + var returnArray = internalResult.Items .Select(i => { var channel = GetChannel(i); @@ -785,7 +802,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv var result = new QueryResult<ProgramInfoDto> { Items = returnArray, - TotalRecordCount = returnArray.Length + TotalRecordCount = internalResult.TotalRecordCount }; return result; diff --git a/MediaBrowser.Server.Implementations/Localization/Server/server.json b/MediaBrowser.Server.Implementations/Localization/Server/server.json index 2ed67ccf0..1020038f4 100644 --- a/MediaBrowser.Server.Implementations/Localization/Server/server.json +++ b/MediaBrowser.Server.Implementations/Localization/Server/server.json @@ -841,6 +841,24 @@ "ViewTypeBoxSets": "Collections", "ViewTypeChannels": "Channels", "ViewTypeLiveTV": "Live TV", + "ViewTypeLiveTvNowPlaying": "Now Airing", + "ViewTypeLatestGames": "Latest Games", + "ViewTypeRecentlyPlayedGames": "Recently Played", + "ViewTypeGameFavorites": "Favorites", + "ViewTypeGameSystems": "Game Systems", + "ViewTypeGameGenres": "Genres", + "ViewTypeTvResume": "Resume", + "ViewTypeTvNextUp": "Next Up", + "ViewTypeTvLatest": "Latest", + "ViewTypeTvSeries": "Series", + "ViewTypeTvGenres": "Genres", + "ViewTypeTvFavorites": "Favorites", + "ViewTypeMovieResume": "Resume", + "ViewTypeMovieLatest": "Latest", + "ViewTypeMovieMovies": "Movies", + "ViewTypeMovieCollections": "Collections", + "ViewTypeMovieFavorites": "Favorites", + "ViewTypeMovieGenres": "Genres", "HeaderOtherDisplaySettings": "Display Settings", "HeaderMyViews": "My Views", "LabelSelectFolderGroups": "Automatically group content from the following folders into views such as Movies, Music and TV:", diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 683f74cc3..a03cae78c 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -293,6 +293,7 @@ <Compile Include="Sync\SyncManager.cs" /> <Compile Include="Sync\SyncRepository.cs" /> <Compile Include="Themes\AppThemeManager.cs" /> + <Compile Include="TV\TVSeriesManager.cs" /> <Compile Include="Udp\UdpMessageReceivedEventArgs.cs" /> <Compile Include="Udp\UdpServer.cs" /> </ItemGroup> @@ -500,7 +501,7 @@ </ItemGroup> <ItemGroup /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> - <Import Project="$(SolutionDir)\.nuget\NuGet.targets" /> + <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " /> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. Other similar extension points exist, see Microsoft.Common.targets. <Target Name="BeforeBuild"> @@ -508,4 +509,4 @@ <Target Name="AfterBuild"> </Target> --> -</Project> +</Project>
\ No newline at end of file diff --git a/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs b/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs new file mode 100644 index 000000000..e71a5d514 --- /dev/null +++ b/MediaBrowser.Server.Implementations/TV/TVSeriesManager.cs @@ -0,0 +1,202 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.TV; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.TV; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Querying; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MediaBrowser.Server.Implementations.TV +{ + public class TVSeriesManager : ITVSeriesManager + { + private readonly IUserManager _userManager; + private readonly IUserDataManager _userDataManager; + private readonly ILibraryManager _libraryManager; + + public TVSeriesManager(IUserManager userManager, IUserDataManager userDataManager, ILibraryManager libraryManager) + { + _userManager = userManager; + _userDataManager = userDataManager; + _libraryManager = libraryManager; + } + + public QueryResult<BaseItem> GetNextUp(NextUpQuery request) + { + var user = _userManager.GetUserById(new Guid(request.UserId)); + + if (user == null) + { + throw new ArgumentException("User not found"); + } + + var parentIds = string.IsNullOrEmpty(request.ParentId) + ? new string[] { } + : new[] { request.ParentId }; + + var items = GetAllLibraryItems(user, parentIds) + .OfType<Series>(); + + // Avoid implicitly captured closure + var episodes = GetNextUpEpisodes(request, user, items); + + return GetResult(episodes, null, request); + } + + public QueryResult<BaseItem> GetNextUp(NextUpQuery request, IEnumerable<Folder> parentsFolders) + { + var user = _userManager.GetUserById(new Guid(request.UserId)); + + if (user == null) + { + throw new ArgumentException("User not found"); + } + + var items = parentsFolders.SelectMany(i => i.GetRecursiveChildren(user)) + .OfType<Series>(); + + // Avoid implicitly captured closure + var episodes = GetNextUpEpisodes(request, user, items); + + return GetResult(episodes, null, request); + } + + private IEnumerable<BaseItem> GetAllLibraryItems(User user, string[] parentIds) + { + if (parentIds.Length > 0) + { + return parentIds.SelectMany(i => + { + var folder = (Folder)_libraryManager.GetItemById(new Guid(i)); + + return folder.GetRecursiveChildren(user); + + }); + } + + if (user == null) + { + throw new ArgumentException("User not found"); + } + + return user.RootFolder.GetRecursiveChildren(user); + } + + public IEnumerable<Episode> GetNextUpEpisodes(NextUpQuery request, User user, IEnumerable<Series> series) + { + // Avoid implicitly captured closure + var currentUser = user; + + return FilterSeries(request, series) + .AsParallel() + .Select(i => GetNextUp(i, currentUser)) + .Where(i => i.Item1 != null) + .OrderByDescending(i => + { + var episode = i.Item1; + + var seriesUserData = _userDataManager.GetUserData(user.Id, episode.Series.GetUserDataKey()); + + if (seriesUserData.IsFavorite) + { + return 2; + } + + if (seriesUserData.Likes.HasValue) + { + return seriesUserData.Likes.Value ? 1 : -1; + } + + return 0; + }) + .ThenByDescending(i => i.Item2) + .ThenByDescending(i => i.Item1.PremiereDate ?? DateTime.MinValue) + .Select(i => i.Item1); + } + + /// <summary> + /// Gets the next up. + /// </summary> + /// <param name="series">The series.</param> + /// <param name="user">The user.</param> + /// <returns>Task{Episode}.</returns> + private Tuple<Episode, DateTime> GetNextUp(Series series, User user) + { + // Get them in display order, then reverse + var allEpisodes = series.GetSeasons(user, true, true) + .SelectMany(i => i.GetEpisodes(user, true, true)) + .Reverse() + .ToList(); + + Episode lastWatched = null; + var lastWatchedDate = DateTime.MinValue; + Episode nextUp = null; + + // Go back starting with the most recent episodes + foreach (var episode in allEpisodes) + { + var userData = _userDataManager.GetUserData(user.Id, episode.GetUserDataKey()); + + if (userData.Played) + { + if (lastWatched != null || nextUp == null) + { + break; + } + + lastWatched = episode; + lastWatchedDate = userData.LastPlayedDate ?? DateTime.MinValue; + } + else + { + if (episode.LocationType != LocationType.Virtual) + { + nextUp = episode; + } + } + } + + if (lastWatched != null) + { + return new Tuple<Episode, DateTime>(nextUp, lastWatchedDate); + } + + return new Tuple<Episode, DateTime>(null, lastWatchedDate); + } + + private IEnumerable<Series> FilterSeries(NextUpQuery request, IEnumerable<Series> items) + { + if (!string.IsNullOrWhiteSpace(request.SeriesId)) + { + var id = new Guid(request.SeriesId); + + items = items.Where(i => i.Id == id); + } + + return items; + } + + private QueryResult<BaseItem> GetResult(IEnumerable<BaseItem> items, int? totalRecordLimit, NextUpQuery query) + { + var itemsArray = totalRecordLimit.HasValue ? items.Take(totalRecordLimit.Value).ToArray() : items.ToArray(); + var totalCount = itemsArray.Length; + + if (query.Limit.HasValue) + { + itemsArray = itemsArray.Skip(query.StartIndex ?? 0).Take(query.Limit.Value).ToArray(); + } + else if (query.StartIndex.HasValue) + { + itemsArray = itemsArray.Skip(query.StartIndex.Value).ToArray(); + } + + return new QueryResult<BaseItem> + { + TotalRecordCount = totalCount, + Items = itemsArray + }; + } + } +} diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 51dccddff..e97c21204 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -38,6 +38,7 @@ using MediaBrowser.Controller.Sorting; using MediaBrowser.Controller.Subtitles; using MediaBrowser.Controller.Sync; using MediaBrowser.Controller.Themes; +using MediaBrowser.Controller.TV; using MediaBrowser.Dlna; using MediaBrowser.Dlna.ConnectionManager; using MediaBrowser.Dlna.ContentDirectory; @@ -79,6 +80,7 @@ using MediaBrowser.Server.Implementations.ServerManager; using MediaBrowser.Server.Implementations.Session; using MediaBrowser.Server.Implementations.Sync; using MediaBrowser.Server.Implementations.Themes; +using MediaBrowser.Server.Implementations.TV; using MediaBrowser.ServerApplication.FFMpeg; using MediaBrowser.ServerApplication.IO; using MediaBrowser.ServerApplication.Native; @@ -213,10 +215,11 @@ namespace MediaBrowser.ServerApplication private ISubtitleManager SubtitleManager { get; set; } private IChapterManager ChapterManager { get; set; } - private IUserViewManager UserViewManager { get; set; } + internal IUserViewManager UserViewManager { get; set; } private IAuthenticationRepository AuthenticationRepository { get; set; } private ISyncRepository SyncRepository { get; set; } + private ITVSeriesManager TVSeriesManager { get; set; } /// <summary> /// Initializes a new instance of the <see cref="ApplicationHost" /> class. @@ -466,6 +469,9 @@ namespace MediaBrowser.ServerApplication ChannelManager = new ChannelManager(UserManager, DtoService, LibraryManager, Logger, ServerConfigurationManager, FileSystemManager, UserDataManager, JsonSerializer, LocalizationManager); RegisterSingleInstance(ChannelManager); + TVSeriesManager = new TVSeriesManager(UserManager, UserDataManager, LibraryManager); + RegisterSingleInstance(TVSeriesManager); + var appThemeManager = new AppThemeManager(ApplicationPaths, FileSystemManager, JsonSerializer, Logger); RegisterSingleInstance<IAppThemeManager>(appThemeManager); @@ -487,7 +493,7 @@ namespace MediaBrowser.ServerApplication UserViewManager = new UserViewManager(LibraryManager, LocalizationManager, FileSystemManager, UserManager, ChannelManager, LiveTvManager, ApplicationPaths, playlistManager); RegisterSingleInstance(UserViewManager); - var contentDirectory = new ContentDirectory(dlnaManager, UserDataManager, ImageProcessor, LibraryManager, ServerConfigurationManager, UserManager, LogManager.GetLogger("UpnpContentDirectory"), HttpClient, UserViewManager, ChannelManager); + var contentDirectory = new ContentDirectory(dlnaManager, UserDataManager, ImageProcessor, LibraryManager, ServerConfigurationManager, UserManager, LogManager.GetLogger("UpnpContentDirectory"), HttpClient); RegisterSingleInstance<IContentDirectory>(contentDirectory); NotificationManager = new NotificationManager(LogManager, UserManager, ServerConfigurationManager); @@ -682,9 +688,10 @@ namespace MediaBrowser.ServerApplication Folder.UserManager = UserManager; BaseItem.FileSystem = FileSystemManager; BaseItem.UserDataManager = UserDataManager; - ChannelVideoItem.ChannelManager = ChannelManager; + BaseItem.ChannelManager = ChannelManager; BaseItem.LiveTvManager = LiveTvManager; - UserView.UserViewManager = UserViewManager; + Folder.UserViewManager = UserViewManager; + UserView.TVSeriesManager = TVSeriesManager; } /// <summary> diff --git a/MediaBrowser.ServerApplication/LibraryViewer.cs b/MediaBrowser.ServerApplication/LibraryViewer.cs index 26cf243da..e89cbd0c0 100644 --- a/MediaBrowser.ServerApplication/LibraryViewer.cs +++ b/MediaBrowser.ServerApplication/LibraryViewer.cs @@ -1,6 +1,9 @@ -using MediaBrowser.Controller.Entities; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Library; using MediaBrowser.Model.Querying; using MediaBrowser.Model.Serialization; using System; @@ -16,17 +19,17 @@ namespace MediaBrowser.ServerApplication { private readonly IJsonSerializer _jsonSerializer; private readonly ILibraryManager _libraryManager; - private readonly IItemRepository _itemRepository; + private readonly IUserViewManager _userViewManager; private User _currentUser; - public LibraryViewer(IJsonSerializer jsonSerializer, IUserManager userManager, ILibraryManager libraryManager, IItemRepository itemRepo) + public LibraryViewer(IJsonSerializer jsonSerializer, IUserManager userManager, ILibraryManager libraryManager, IUserViewManager userViewManager) { InitializeComponent(); _jsonSerializer = jsonSerializer; _libraryManager = libraryManager; - _itemRepository = itemRepo; + _userViewManager = userViewManager; foreach (var user in userManager.Users) selectUser.Items.Add(user); @@ -72,23 +75,50 @@ namespace MediaBrowser.ServerApplication LoadTree(); } + private IEnumerable<BaseItem> GetItems(Folder parent, User user) + { + if (parent == null) + { + var task = _userViewManager.GetUserViews(new UserViewQuery + { + UserId = user.Id.ToString("N") + + }, CancellationToken.None); + + task.RunSynchronously(); + + return task.Result; + } + else + { + var task = parent.GetUserItems(new UserItemsQuery + { + User = user, + SortBy = new[] { ItemSortBy.SortName } + + }); + + task.RunSynchronously(); + + return task.Result.Items; + } + } + private void LoadTree() { treeView1.Nodes.Clear(); var isPhysical = _currentUser.Name == "Physical"; - IEnumerable<BaseItem> children = isPhysical ? new[] { _libraryManager.RootFolder } : _libraryManager.RootFolder.GetChildren(_currentUser, true); - children = OrderByName(children, _currentUser); + IEnumerable<BaseItem> children = isPhysical ? new[] { _libraryManager.RootFolder } : GetItems(null, _currentUser); - foreach (Folder folder in children) + foreach (var folder in children.OfType<Folder>()) { - var currentFolder = folder; var node = new TreeNode { Tag = currentFolder }; - var subChildren = isPhysical ? currentFolder.Children : currentFolder.GetChildren(_currentUser, true); - subChildren = OrderByName(subChildren, _currentUser); + var subChildren = isPhysical ? currentFolder.Children.OrderBy(i => i.SortName) : GetItems(currentFolder, _currentUser); + AddChildren(node, subChildren, _currentUser, isPhysical); node.Text = currentFolder.Name + " (" + node.Nodes.Count + ")"; @@ -111,9 +141,9 @@ namespace MediaBrowser.ServerApplication var subFolder = item as Folder; if (subFolder != null) { - var subChildren = isPhysical ? subFolder.Children : subFolder.GetChildren(_currentUser, true); + var subChildren = isPhysical ? subFolder.Children.OrderBy(i => i.SortName) : GetItems(subFolder, _currentUser); - AddChildren(node, OrderBy(subChildren, user, ItemSortBy.SortName), user, isPhysical); + AddChildren(node, subChildren, user, isPhysical); node.Text = item.Name + " (" + node.Nodes.Count + ")"; } else @@ -125,28 +155,6 @@ namespace MediaBrowser.ServerApplication } /// <summary> - /// Orders the name of the by. - /// </summary> - /// <param name="items">The items.</param> - /// <param name="user">The user.</param> - /// <returns>IEnumerable{BaseItem}.</returns> - private IEnumerable<BaseItem> OrderByName(IEnumerable<BaseItem> items, User user) - { - return OrderBy(items, user, ItemSortBy.SortName); - } - - /// <summary> - /// Orders the name of the by. - /// </summary> - /// <param name="items">The items.</param> - /// <param name="user">The user.</param> - /// <returns>IEnumerable{BaseItem}.</returns> - private IEnumerable<BaseItem> OrderBy(IEnumerable<BaseItem> items, User user, string order) - { - return _libraryManager.Sort(items, user, new[] { order }, SortOrder.Ascending); - } - - /// <summary> /// The INDEN t_ STRING /// </summary> private const string INDENT_STRING = " "; diff --git a/MediaBrowser.ServerApplication/MainStartup.cs b/MediaBrowser.ServerApplication/MainStartup.cs index 81c04060a..6e6377a64 100644 --- a/MediaBrowser.ServerApplication/MainStartup.cs +++ b/MediaBrowser.ServerApplication/MainStartup.cs @@ -249,7 +249,7 @@ namespace MediaBrowser.ServerApplication { //Application.EnableVisualStyles(); //Application.SetCompatibleTextRenderingDefault(false); - _serverNotifyIcon = new ServerNotifyIcon(_appHost.LogManager, _appHost, _appHost.ServerConfigurationManager, _appHost.UserManager, _appHost.LibraryManager, _appHost.JsonSerializer, _appHost.ItemRepository, _appHost.LocalizationManager); + _serverNotifyIcon = new ServerNotifyIcon(_appHost.LogManager, _appHost, _appHost.ServerConfigurationManager, _appHost.UserManager, _appHost.LibraryManager, _appHost.JsonSerializer, _appHost.LocalizationManager, _appHost.UserViewManager); Application.Run(); } diff --git a/MediaBrowser.ServerApplication/ServerNotifyIcon.cs b/MediaBrowser.ServerApplication/ServerNotifyIcon.cs index 47a4be8e3..8740b707b 100644 --- a/MediaBrowser.ServerApplication/ServerNotifyIcon.cs +++ b/MediaBrowser.ServerApplication/ServerNotifyIcon.cs @@ -39,7 +39,7 @@ namespace MediaBrowser.ServerApplication private readonly IUserManager _userManager; private readonly ILibraryManager _libraryManager; private readonly IJsonSerializer _jsonSerializer; - private readonly IItemRepository _itemRepository; + private readonly IUserViewManager _userViewManager; private readonly ILocalizationManager _localization; private LogForm _logForm; @@ -61,11 +61,11 @@ namespace MediaBrowser.ServerApplication IServerConfigurationManager configurationManager, IUserManager userManager, ILibraryManager libraryManager, IJsonSerializer jsonSerializer, - IItemRepository itemRepo, ILocalizationManager localization) + ILocalizationManager localization, IUserViewManager userViewManager) { _logger = logManager.GetLogger("MainWindow"); - _itemRepository = itemRepo; _localization = localization; + _userViewManager = userViewManager; _appHost = appHost; _logManager = logManager; _configurationManager = configurationManager; @@ -318,7 +318,7 @@ namespace MediaBrowser.ServerApplication void cmdLibraryExplorer_Click(object sender, EventArgs e) { - new LibraryViewer(_jsonSerializer, _userManager, _libraryManager, _itemRepository).Show(); + new LibraryViewer(_jsonSerializer, _userManager, _libraryManager, _userViewManager).Show(); } void cmdRestart_Click(object sender, EventArgs e) diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 32cb88218..864a2f70f 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -2136,7 +2136,7 @@ <PostBuildEvent>
</PostBuildEvent>
</PropertyGroup>
- <Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
+ <Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition=" '$(ConfigurationName)' != 'Release Mono' " />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
|
