diff --git a/Emby.Drawing/ImageMagick/StripCollageBuilder.cs b/Emby.Drawing/ImageMagick/StripCollageBuilder.cs index a50a75ccd1..7ed0f36e2c 100644 --- a/Emby.Drawing/ImageMagick/StripCollageBuilder.cs +++ b/Emby.Drawing/ImageMagick/StripCollageBuilder.cs @@ -285,14 +285,14 @@ namespace Emby.Drawing.ImageMagick private MagickWand BuildThumbCollageWand(List paths, int width, int height) { - var inputPaths = ImageHelpers.ProjectPaths(paths, 8); + var inputPaths = ImageHelpers.ProjectPaths(paths, 4); using (var wandImages = new MagickWand(inputPaths.ToArray())) { var wand = new MagickWand(width, height); wand.OpenImage("gradient:#111111-#111111"); using (var draw = new DrawingWand()) { - var iSlice = Convert.ToInt32(width * .1166666667); + var iSlice = Convert.ToInt32(width * .1166666667 * 2); int iTrans = Convert.ToInt32(height * .25); int iHeight = Convert.ToInt32(height * .62); var horizontalImagePadding = Convert.ToInt32(width * 0.0125); diff --git a/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs index 2ba4f5aab7..970b463cd1 100644 --- a/Emby.Drawing/ImageProcessor.cs +++ b/Emby.Drawing/ImageProcessor.cs @@ -189,7 +189,7 @@ namespace Emby.Drawing dateModified = tuple.Item2; } - var originalImageSize = GetImageSize(originalImagePath, dateModified); + var originalImageSize = GetImageSize(originalImagePath, dateModified, true); // Determine the output size based on incoming parameters var newSize = DrawingUtils.Resize(originalImageSize, options.Width, options.Height, options.MaxWidth, options.MaxHeight); @@ -363,12 +363,12 @@ namespace Emby.Drawing /// ImageSize. public ImageSize GetImageSize(string path) { - return GetImageSize(path, File.GetLastWriteTimeUtc(path)); + return GetImageSize(path, File.GetLastWriteTimeUtc(path), false); } public ImageSize GetImageSize(ItemImageInfo info) { - return GetImageSize(info.Path, info.DateModified); + return GetImageSize(info.Path, info.DateModified, false); } /// @@ -376,9 +376,10 @@ namespace Emby.Drawing /// /// The path. /// The image date modified. + /// if set to true [allow slow method]. /// ImageSize. /// path - private ImageSize GetImageSize(string path, DateTime imageDateModified) + private ImageSize GetImageSize(string path, DateTime imageDateModified, bool allowSlowMethod) { if (string.IsNullOrEmpty(path)) { @@ -393,7 +394,7 @@ namespace Emby.Drawing if (!_cachedImagedSizes.TryGetValue(cacheHash, out size)) { - size = GetImageSizeInternal(path); + size = GetImageSizeInternal(path, allowSlowMethod); _cachedImagedSizes.AddOrUpdate(cacheHash, size, (keyName, oldValue) => size); } @@ -405,8 +406,9 @@ namespace Emby.Drawing /// Gets the image size internal. /// /// The path. + /// if set to true [allow slow method]. /// ImageSize. - private ImageSize GetImageSizeInternal(string path) + private ImageSize GetImageSizeInternal(string path, bool allowSlowMethod) { ImageSize size; @@ -416,7 +418,11 @@ namespace Emby.Drawing } catch { - _logger.Info("Failed to read image header for {0}. Doing it the slow way.", path); + if (!allowSlowMethod) + { + throw; + } + //_logger.Info("Failed to read image header for {0}. Doing it the slow way.", path); CheckDisposed(); diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs index 54c28d390a..c2b406190f 100644 --- a/MediaBrowser.Api/ApiEntryPoint.cs +++ b/MediaBrowser.Api/ApiEntryPoint.cs @@ -336,12 +336,6 @@ namespace MediaBrowser.Api if (job.Type != TranscodingJobType.Progressive) { timerDuration = 1800000; - - // We can really reduce the timeout for apps that are using the newer api - if (!string.IsNullOrWhiteSpace(job.PlaySessionId)) - { - timerDuration = 300000; - } } job.PingTimeout = timerDuration; diff --git a/MediaBrowser.Api/ConfigurationService.cs b/MediaBrowser.Api/ConfigurationService.cs index 9f6c07dd21..f266180a49 100644 --- a/MediaBrowser.Api/ConfigurationService.cs +++ b/MediaBrowser.Api/ConfigurationService.cs @@ -7,7 +7,6 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Serialization; using ServiceStack; -using ServiceStack.Text.Controller; using ServiceStack.Web; using System.Collections.Generic; using System.IO; @@ -26,7 +25,7 @@ namespace MediaBrowser.Api } [Route("/System/Configuration/{Key}", "GET", Summary = "Gets a named configuration")] - [Authenticated] + [Authenticated(AllowBeforeStartupWizard = true)] public class GetNamedConfiguration { [ApiMember(Name = "Key", Description = "Key", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] diff --git a/MediaBrowser.Api/EnvironmentService.cs b/MediaBrowser.Api/EnvironmentService.cs index 73b6573a6c..457b4709bf 100644 --- a/MediaBrowser.Api/EnvironmentService.cs +++ b/MediaBrowser.Api/EnvironmentService.cs @@ -221,7 +221,9 @@ namespace MediaBrowser.Api /// IEnumerable{FileSystemEntryInfo}. private IEnumerable GetFileSystemEntries(GetDirectoryContents request) { - var entries = new DirectoryInfo(request.Path).EnumerateFileSystemInfos().Where(i => + // using EnumerateFileSystemInfos doesn't handle reparse points (symlinks) + var entries = new DirectoryInfo(request.Path).EnumerateDirectories("*", SearchOption.TopDirectoryOnly) + .Concat(new DirectoryInfo(request.Path).EnumerateFiles("*", SearchOption.TopDirectoryOnly)).Where(i => { if (!request.IncludeHidden && i.Attributes.HasFlag(FileAttributes.Hidden)) { diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index e8340a1cb5..49dd121baa 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -1,14 +1,20 @@ -using MediaBrowser.Controller.Activity; +using MediaBrowser.Api.Movies; +using MediaBrowser.Api.Music; +using MediaBrowser.Controller.Activity; +using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Dto; 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.LiveTv; using MediaBrowser.Controller.Localization; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; +using MediaBrowser.Controller.TV; using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Channels; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; @@ -225,6 +231,17 @@ namespace MediaBrowser.Api.Library public string TvdbId { get; set; } } + [Route("/Library/Movies/Added", "POST", Summary = "Reports that new movies have been added by an external source")] + [Route("/Library/Movies/Updated", "POST", Summary = "Reports that new movies have been added by an external source")] + [Authenticated] + public class PostUpdatedMovies : IReturnVoid + { + [ApiMember(Name = "TmdbId", Description = "Tmdb Id", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] + public string TmdbId { get; set; } + [ApiMember(Name = "ImdbId", Description = "Imdb Id", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] + public string ImdbId { get; set; } + } + [Route("/Items/{Id}/Download", "GET", Summary = "Downloads item media")] [Authenticated(Roles = "download")] public class GetDownload @@ -237,6 +254,12 @@ namespace MediaBrowser.Api.Library public string Id { get; set; } } + [Route("/Items/{Id}/Similar", "GET", Summary = "Gets similar items")] + [Authenticated] + public class GetSimilarItems : BaseGetSimilarItemsFromItem + { + } + /// /// Class LibraryService /// @@ -255,12 +278,16 @@ namespace MediaBrowser.Api.Library private readonly IAuthorizationContext _authContext; private readonly IActivityManager _activityManager; private readonly ILocalizationManager _localization; + private readonly ILiveTvManager _liveTv; + private readonly IChannelManager _channelManager; + private readonly ITVSeriesManager _tvManager; + private readonly ILibraryMonitor _libraryMonitor; /// /// Initializes a new instance of the class. /// public LibraryService(IItemRepository itemRepo, ILibraryManager libraryManager, IUserManager userManager, - IDtoService dtoService, IUserDataManager userDataManager, IAuthorizationContext authContext, IActivityManager activityManager, ILocalizationManager localization) + IDtoService dtoService, IUserDataManager userDataManager, IAuthorizationContext authContext, IActivityManager activityManager, ILocalizationManager localization, ILiveTvManager liveTv, IChannelManager channelManager, ITVSeriesManager tvManager, ILibraryMonitor libraryMonitor) { _itemRepo = itemRepo; _libraryManager = libraryManager; @@ -270,6 +297,117 @@ namespace MediaBrowser.Api.Library _authContext = authContext; _activityManager = activityManager; _localization = localization; + _liveTv = liveTv; + _channelManager = channelManager; + _tvManager = tvManager; + _libraryMonitor = libraryMonitor; + } + + public object Get(GetSimilarItems request) + { + var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null; + + var item = string.IsNullOrEmpty(request.Id) ? + (!string.IsNullOrWhiteSpace(request.UserId) ? user.RootFolder : + _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id); + + if (item is Game) + { + return new GamesService(_userManager, _userDataManager, _libraryManager, _itemRepo, _dtoService) + { + AuthorizationContext = AuthorizationContext, + Logger = Logger, + Request = Request, + SessionContext = SessionContext, + ResultFactory = ResultFactory + + }.Get(new GetSimilarGames + { + Fields = request.Fields, + Id = request.Id, + Limit = request.Limit, + UserId = request.UserId + }); + } + if (item is MusicAlbum) + { + return new AlbumsService(_userManager, _userDataManager, _libraryManager, _itemRepo, _dtoService) + { + AuthorizationContext = AuthorizationContext, + Logger = Logger, + Request = Request, + SessionContext = SessionContext, + ResultFactory = ResultFactory + + }.Get(new GetSimilarAlbums + { + Fields = request.Fields, + Id = request.Id, + Limit = request.Limit, + UserId = request.UserId + }); + } + if (item is MusicArtist) + { + return new AlbumsService(_userManager, _userDataManager, _libraryManager, _itemRepo, _dtoService) + { + AuthorizationContext = AuthorizationContext, + Logger = Logger, + Request = Request, + SessionContext = SessionContext, + ResultFactory = ResultFactory + + }.Get(new GetSimilarArtists + { + Fields = request.Fields, + Id = request.Id, + Limit = request.Limit, + UserId = request.UserId + }); + } + + var program = item as IHasProgramAttributes; + var channelItem = item as ChannelVideoItem; + + if (item is Movie || (program != null && program.IsMovie) || (channelItem != null && channelItem.ContentType == ChannelMediaContentType.Movie) || (channelItem != null && channelItem.ContentType == ChannelMediaContentType.MovieExtra)) + { + return new MoviesService(_userManager, _userDataManager, _libraryManager, _itemRepo, _dtoService, _channelManager) + { + AuthorizationContext = AuthorizationContext, + Logger = Logger, + Request = Request, + SessionContext = SessionContext, + ResultFactory = ResultFactory + + }.Get(new GetSimilarMovies + { + Fields = request.Fields, + Id = request.Id, + Limit = request.Limit, + UserId = request.UserId + }); + } + + if (item is Series || (program != null && program.IsSeries) || (channelItem != null && channelItem.ContentType == ChannelMediaContentType.Episode)) + { + return new TvShowsService(_userManager, _userDataManager, _libraryManager, _itemRepo, _dtoService, _tvManager) + { + AuthorizationContext = AuthorizationContext, + Logger = Logger, + Request = Request, + SessionContext = SessionContext, + ResultFactory = ResultFactory + + }.Get(new GetSimilarShows + { + Fields = request.Fields, + Id = request.Id, + Limit = request.Limit, + UserId = request.UserId + }); + } + + return new ItemsResult(); } public object Get(GetMediaFolders request) @@ -297,7 +435,59 @@ namespace MediaBrowser.Api.Library public void Post(PostUpdatedSeries request) { - Task.Run(() => _libraryManager.ValidateMediaLibrary(new Progress(), CancellationToken.None)); + var series = _libraryManager.GetItems(new InternalItemsQuery + { + IncludeItemTypes = new[] { typeof(Series).Name } + + }).Items; + + series = series.Where(i => string.Equals(request.TvdbId, i.GetProviderId(MetadataProviders.Tvdb), StringComparison.OrdinalIgnoreCase)).ToArray(); + + if (series.Length > 0) + { + foreach (var item in series) + { + _libraryMonitor.ReportFileSystemChanged(item.Path); + } + } + else + { + Task.Run(() => _libraryManager.ValidateMediaLibrary(new Progress(), CancellationToken.None)); + } + } + + public void Post(PostUpdatedMovies request) + { + var movies = _libraryManager.GetItems(new InternalItemsQuery + { + IncludeItemTypes = new[] { typeof(Movie).Name } + + }).Items; + + if (!string.IsNullOrWhiteSpace(request.ImdbId)) + { + movies = movies.Where(i => string.Equals(request.ImdbId, i.GetProviderId(MetadataProviders.Imdb), StringComparison.OrdinalIgnoreCase)).ToArray(); + } + else if (!string.IsNullOrWhiteSpace(request.TmdbId)) + { + movies = movies.Where(i => string.Equals(request.TmdbId, i.GetProviderId(MetadataProviders.Tmdb), StringComparison.OrdinalIgnoreCase)).ToArray(); + } + else + { + movies = new BaseItem[] { }; + } + + if (movies.Length > 0) + { + foreach (var item in movies) + { + _libraryMonitor.ReportFileSystemChanged(item.Path); + } + } + else + { + Task.Run(() => _libraryManager.ValidateMediaLibrary(new Progress(), CancellationToken.None)); + } } public object Get(GetDownload request) @@ -524,7 +714,6 @@ namespace MediaBrowser.Api.Library public void Delete(DeleteItem request) { var item = _libraryManager.GetItemById(request.Id); - var auth = _authContext.GetAuthorizationInfo(Request); var user = _userManager.GetUserById(auth.UserId); @@ -533,9 +722,16 @@ namespace MediaBrowser.Api.Library throw new UnauthorizedAccessException(); } - var task = _libraryManager.DeleteItem(item); - - Task.WaitAll(task); + if (item is ILiveTvRecording) + { + var task = _liveTv.DeleteRecording(request.Id); + Task.WaitAll(task); + } + else + { + var task = _libraryManager.DeleteItem(item); + Task.WaitAll(task); + } } /// diff --git a/MediaBrowser.Api/LiveTv/LiveTvService.cs b/MediaBrowser.Api/LiveTv/LiveTvService.cs index c474642d5d..07e64c98d4 100644 --- a/MediaBrowser.Api/LiveTv/LiveTvService.cs +++ b/MediaBrowser.Api/LiveTv/LiveTvService.cs @@ -1,4 +1,6 @@ -using MediaBrowser.Controller.Dto; +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Net; @@ -8,6 +10,7 @@ using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Querying; using ServiceStack; using System; +using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading; @@ -26,7 +29,7 @@ namespace MediaBrowser.Api.LiveTv [Route("/LiveTv/Channels", "GET", Summary = "Gets available live tv channels.")] [Authenticated] - public class GetChannels : IReturn> + public class GetChannels : IReturn>, IHasDtoOptions { [ApiMember(Name = "Type", Description = "Optional filter by channel type.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public ChannelType? Type { get; set; } @@ -59,6 +62,30 @@ namespace MediaBrowser.Api.LiveTv [ApiMember(Name = "EnableFavoriteSorting", Description = "Incorporate favorite and like status into channel sorting.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] public bool EnableFavoriteSorting { get; set; } + + [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] + public bool? EnableImages { get; set; } + + [ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? ImageTypeLimit { get; set; } + + [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string EnableImageTypes { get; set; } + + /// + /// Fields to return within the items, in addition to basic information + /// + /// The fields. + [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] + public string Fields { get; set; } + + [ApiMember(Name = "AddCurrentProgram", Description = "Optional. Adds current program info to each channel", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public bool AddCurrentProgram { get; set; } + + public GetChannels() + { + AddCurrentProgram = true; + } } [Route("/LiveTv/Channels/{Id}", "GET", Summary = "Gets a live tv channel")] @@ -78,7 +105,7 @@ namespace MediaBrowser.Api.LiveTv [Route("/LiveTv/Recordings", "GET", Summary = "Gets live tv recordings")] [Authenticated] - public class GetRecordings : IReturn> + public class GetRecordings : IReturn>, IHasDtoOptions { [ApiMember(Name = "ChannelId", Description = "Optional filter by channel id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string ChannelId { get; set; } @@ -103,6 +130,22 @@ namespace MediaBrowser.Api.LiveTv [ApiMember(Name = "SeriesTimerId", Description = "Optional filter by recordings belonging to a series timer", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string SeriesTimerId { get; set; } + + [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] + public bool? EnableImages { get; set; } + + [ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? ImageTypeLimit { get; set; } + + [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string EnableImageTypes { get; set; } + + /// + /// Fields to return within the items, in addition to basic information + /// + /// The fields. + [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] + public string Fields { get; set; } } [Route("/LiveTv/Recordings/Groups", "GET", Summary = "Gets live tv recording groups")] @@ -161,7 +204,7 @@ namespace MediaBrowser.Api.LiveTv [Route("/LiveTv/Programs", "GET,POST", Summary = "Gets available live tv epgs..")] [Authenticated] - public class GetPrograms : IReturn> + public class GetPrograms : IReturn>, IHasDtoOptions { [ApiMember(Name = "ChannelIds", Description = "The channels to return guide information for.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] public string ChannelIds { get; set; } @@ -187,6 +230,9 @@ namespace MediaBrowser.Api.LiveTv [ApiMember(Name = "IsMovie", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] public bool? IsMovie { get; set; } + [ApiMember(Name = "IsKids", Description = "Optional filter for kids.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] + public bool? IsKids { get; set; } + [ApiMember(Name = "IsSports", Description = "Optional filter for sports.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET,POST")] public bool? IsSports { get; set; } @@ -204,11 +250,27 @@ namespace MediaBrowser.Api.LiveTv [ApiMember(Name = "Genres", Description = "The genres to return guide information for.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] public string Genres { get; set; } + + [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] + public bool? EnableImages { get; set; } + + [ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? ImageTypeLimit { get; set; } + + [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string EnableImageTypes { get; set; } + + /// + /// Fields to return within the items, in addition to basic information + /// + /// The fields. + [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] + public string Fields { get; set; } } [Route("/LiveTv/Programs/Recommended", "GET", Summary = "Gets available live tv epgs..")] [Authenticated] - public class GetRecommendedPrograms : IReturn> + public class GetRecommendedPrograms : IReturn>, IHasDtoOptions { [ApiMember(Name = "UserId", Description = "Optional filter by user id.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET,POST")] public string UserId { get; set; } @@ -227,6 +289,25 @@ namespace MediaBrowser.Api.LiveTv [ApiMember(Name = "IsMovie", Description = "Optional filter for movies.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] public bool? IsMovie { get; set; } + + [ApiMember(Name = "IsKids", Description = "Optional filter for kids.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool? IsKids { get; set; } + + [ApiMember(Name = "EnableImages", Description = "Optional, include image information in output", IsRequired = false, DataType = "boolean", ParameterType = "query", Verb = "GET")] + public bool? EnableImages { get; set; } + + [ApiMember(Name = "ImageTypeLimit", Description = "Optional, the max number of images to return, per image type", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? ImageTypeLimit { get; set; } + + [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string EnableImageTypes { get; set; } + + /// + /// Fields to return within the items, in addition to basic information + /// + /// The fields. + [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] + public string Fields { get; set; } } [Route("/LiveTv/Programs/{Id}", "GET", Summary = "Gets a live tv program")] @@ -330,15 +411,108 @@ namespace MediaBrowser.Api.LiveTv public string UserId { get; set; } } + [Route("/LiveTv/TunerHosts", "POST", Summary = "Adds a tuner host")] + [Authenticated] + public class AddTunerHost : TunerHostInfo, IReturn + { + } + + [Route("/LiveTv/TunerHosts", "DELETE", Summary = "Deletes a tuner host")] + [Authenticated] + public class DeleteTunerHost : IReturnVoid + { + [ApiMember(Name = "Id", Description = "Tuner host id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "DELETE")] + public string Id { get; set; } + } + + [Route("/LiveTv/ListingProviders", "POST", Summary = "Adds a listing provider")] + [Authenticated(AllowBeforeStartupWizard = true)] + public class AddListingProvider : ListingsProviderInfo, IReturn + { + public bool ValidateLogin { get; set; } + public bool ValidateListings { get; set; } + } + + [Route("/LiveTv/ListingProviders", "DELETE", Summary = "Deletes a listing provider")] + [Authenticated(AllowBeforeStartupWizard = true)] + public class DeleteListingProvider : IReturnVoid + { + [ApiMember(Name = "Id", Description = "Provider id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "DELETE")] + public string Id { get; set; } + } + + [Route("/LiveTv/ListingProviders/Lineups", "GET", Summary = "Gets available lineups")] + [Authenticated(AllowBeforeStartupWizard = true)] + public class GetLineups : IReturn> + { + [ApiMember(Name = "Id", Description = "Provider id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string Id { get; set; } + + [ApiMember(Name = "Type", Description = "Provider Type", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string Type { get; set; } + + [ApiMember(Name = "Location", Description = "Location", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string Location { get; set; } + + [ApiMember(Name = "Country", Description = "Country", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string Country { get; set; } + } + + [Route("/LiveTv/ListingProviders/SchedulesDirect/Countries", "GET", Summary = "Gets available lineups")] + [Authenticated(AllowBeforeStartupWizard = true)] + public class GetSchedulesDirectCountries + { + } + + [Route("/LiveTv/Registration", "GET")] + [Authenticated] + public class GetLiveTvRegistrationInfo : IReturn + { + [ApiMember(Name = "ChannelId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string ChannelId { get; set; } + + [ApiMember(Name = "ProgramId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string ProgramId { get; set; } + + [ApiMember(Name = "Feature", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string Feature { get; set; } + } + public class LiveTvService : BaseApiService { private readonly ILiveTvManager _liveTvManager; private readonly IUserManager _userManager; + private readonly IConfigurationManager _config; + private readonly IHttpClient _httpClient; - public LiveTvService(ILiveTvManager liveTvManager, IUserManager userManager) + public LiveTvService(ILiveTvManager liveTvManager, IUserManager userManager, IConfigurationManager config, IHttpClient httpClient) { _liveTvManager = liveTvManager; _userManager = userManager; + _config = config; + _httpClient = httpClient; + } + + public async Task Get(GetLiveTvRegistrationInfo request) + { + var result = await _liveTvManager.GetRegistrationInfo(request.ChannelId, request.ProgramId, request.Feature).ConfigureAwait(false); + + return ToOptimizedResult(result); + } + + public async Task Get(GetSchedulesDirectCountries request) + { + // https://json.schedulesdirect.org/20141201/available/countries + + var response = await _httpClient.Get(new HttpRequestOptions + { + Url = "https://json.schedulesdirect.org/20141201/available/countries", + CacheLength = TimeSpan.FromDays(1), + CacheMode = CacheMode.Unconditional + + }).ConfigureAwait(false); + + return ResultFactory.GetResult(response, "application/json"); } private void AssertUserCanManageLiveTv() @@ -356,6 +530,48 @@ namespace MediaBrowser.Api.LiveTv } } + public async Task Post(AddListingProvider request) + { + var result = await _liveTvManager.SaveListingProvider(request, request.ValidateLogin, request.ValidateListings).ConfigureAwait(false); + return ToOptimizedResult(result); + } + + public void Delete(DeleteListingProvider request) + { + var config = GetConfiguration(); + + config.ListingProviders = config.ListingProviders.Where(i => !string.Equals(request.Id, i.Id, StringComparison.OrdinalIgnoreCase)).ToList(); + + _config.SaveConfiguration("livetv", config); + } + + public async Task Post(AddTunerHost request) + { + var result = await _liveTvManager.SaveTunerHost(request).ConfigureAwait(false); + return ToOptimizedResult(result); + } + + public void Delete(DeleteTunerHost request) + { + var config = GetConfiguration(); + + config.TunerHosts = config.TunerHosts.Where(i => !string.Equals(request.Id, i.Id, StringComparison.OrdinalIgnoreCase)).ToList(); + + _config.SaveConfiguration("livetv", config); + } + + private LiveTvOptions GetConfiguration() + { + return _config.GetConfiguration("livetv"); + } + + public async Task Get(GetLineups request) + { + var info = await _liveTvManager.GetLineups(request.Type, request.Id, request.Country, request.Location).ConfigureAwait(false); + + return ToOptimizedSerializedResultUsingCache(info); + } + public async Task Get(GetLiveTvInfo request) { var info = await _liveTvManager.GetLiveTvInfo(CancellationToken.None).ConfigureAwait(false); @@ -374,9 +590,10 @@ namespace MediaBrowser.Api.LiveTv IsFavorite = request.IsFavorite, IsLiked = request.IsLiked, IsDisliked = request.IsDisliked, - EnableFavoriteSorting = request.EnableFavoriteSorting + EnableFavoriteSorting = request.EnableFavoriteSorting, + AddCurrentProgram = request.AddCurrentProgram - }, CancellationToken.None).ConfigureAwait(false); + }, GetDtoOptions(request), CancellationToken.None).ConfigureAwait(false); return ToOptimizedSerializedResultUsingCache(result); } @@ -429,10 +646,11 @@ namespace MediaBrowser.Api.LiveTv query.SortBy = (request.SortBy ?? String.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); query.SortOrder = request.SortOrder; query.IsMovie = request.IsMovie; + query.IsKids = request.IsKids; query.IsSports = request.IsSports; query.Genres = (request.Genres ?? String.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - var result = await _liveTvManager.GetPrograms(query, CancellationToken.None).ConfigureAwait(false); + var result = await _liveTvManager.GetPrograms(query, GetDtoOptions(request), CancellationToken.None).ConfigureAwait(false); return ToOptimizedResult(result); } @@ -446,10 +664,11 @@ namespace MediaBrowser.Api.LiveTv Limit = request.Limit, HasAired = request.HasAired, IsMovie = request.IsMovie, + IsKids = request.IsKids, IsSports = request.IsSports }; - var result = await _liveTvManager.GetRecommendedPrograms(query, CancellationToken.None).ConfigureAwait(false); + var result = await _liveTvManager.GetRecommendedPrograms(query, GetDtoOptions(request), CancellationToken.None).ConfigureAwait(false); return ToOptimizedResult(result); } @@ -461,7 +680,7 @@ namespace MediaBrowser.Api.LiveTv public async Task Get(GetRecordings request) { - var options = new DtoOptions(); + var options = GetDtoOptions(request); options.DeviceId = AuthorizationContext.GetAuthorizationInfo(Request).DeviceId; var result = await _liveTvManager.GetRecordings(new RecordingQuery diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index e79163d803..295cc78e93 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -84,22 +84,26 @@ + + + + - - - - - + + + + + diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs index 97e9aa9c8f..fe8bae1a51 100644 --- a/MediaBrowser.Api/Movies/MoviesService.cs +++ b/MediaBrowser.Api/Movies/MoviesService.cs @@ -28,6 +28,14 @@ namespace MediaBrowser.Api.Movies { } + /// + /// Class GetSimilarTrailers + /// + [Route("/Trailers/{Id}/Similar", "GET", Summary = "Finds movies and trailers similar to a given trailer.")] + public class GetSimilarTrailers : BaseGetSimilarItemsFromItem + { + } + [Route("/Movies/Recommendations", "GET", Summary = "Gets movie recommendations")] public class GetMovieRecommendations : IReturn, IHasItemFields { @@ -117,6 +125,17 @@ namespace MediaBrowser.Api.Movies return ToOptimizedSerializedResultUsingCache(result); } + public async Task Get(GetSimilarTrailers request) + { + var result = await GetSimilarItemsResult( + // Strip out secondary versions + request, item => (item is Movie) && !((Video)item).PrimaryVersionId.HasValue, + + SimilarItemsHelper.GetSimiliarityScore).ConfigureAwait(false); + + return ToOptimizedSerializedResultUsingCache(result); + } + public async Task Get(GetMovieRecommendations request) { var user = _userManager.GetUserById(request.UserId); @@ -126,7 +145,7 @@ namespace MediaBrowser.Api.Movies movies = _libraryManager.ReplaceVideosWithPrimaryVersions(movies); var listEligibleForCategories = new List(); - var listEligibleForSuggestion = new List (); + var listEligibleForSuggestion = new List(); var list = movies.ToList(); @@ -159,7 +178,7 @@ namespace MediaBrowser.Api.Movies var dtoOptions = GetDtoOptions(request); dtoOptions.Fields = request.GetItemFields().ToList(); - + var result = GetRecommendationCategories(user, listEligibleForCategories, listEligibleForSuggestion, request.CategoryLimit, request.ItemLimit, dtoOptions); return ToOptimizedResult(result); @@ -174,14 +193,14 @@ namespace MediaBrowser.Api.Movies _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id); Func filter = i => i.Id != item.Id && includeInSearch(i); - + var inputItems = user == null ? _libraryManager.RootFolder.GetRecursiveChildren(filter) : user.RootFolder.GetRecursiveChildren(user, filter); var list = inputItems.ToList(); - if (item is Movie && user != null && user.Configuration.IncludeTrailersInSuggestions) + if (user != null && user.Configuration.IncludeTrailersInSuggestions) { var trailerResult = await _channelManager.GetAllMediaInternal(new AllChannelMediaQuery { @@ -224,7 +243,7 @@ namespace MediaBrowser.Api.Movies } var dtoOptions = GetDtoOptions(request); - + var result = new ItemsResult { Items = _dtoService.GetBaseItemDtos(returnItems, dtoOptions, user).ToArray(), diff --git a/MediaBrowser.Api/Movies/TrailersService.cs b/MediaBrowser.Api/Movies/TrailersService.cs index c807bfde65..ed197911a0 100644 --- a/MediaBrowser.Api/Movies/TrailersService.cs +++ b/MediaBrowser.Api/Movies/TrailersService.cs @@ -2,15 +2,12 @@ using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; -using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Channels; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using ServiceStack; -using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -18,23 +15,9 @@ using System.Threading.Tasks; namespace MediaBrowser.Api.Movies { - /// - /// Class GetSimilarTrailers - /// - [Route("/Trailers/{Id}/Similar", "GET", Summary = "Finds movies and trailers similar to a given trailer.")] - public class GetSimilarTrailers : BaseGetSimilarItemsFromItem - { - } - [Route("/Trailers", "GET", Summary = "Finds movies and trailers similar to a given trailer.")] public class Getrailers : BaseItemsRequest, IReturn { - /// - /// Gets or sets the user id. - /// - /// The user id. - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] - public string UserId { get; set; } } /// @@ -57,7 +40,6 @@ namespace MediaBrowser.Api.Movies /// private readonly ILibraryManager _libraryManager; - private readonly IItemRepository _itemRepo; private readonly IDtoService _dtoService; private readonly IChannelManager _channelManager; @@ -67,40 +49,15 @@ namespace MediaBrowser.Api.Movies /// The user manager. /// The user data repository. /// The library manager. - public TrailersService(IUserManager userManager, IUserDataManager userDataRepository, ILibraryManager libraryManager, IItemRepository itemRepo, IDtoService dtoService, IChannelManager channelManager) + public TrailersService(IUserManager userManager, IUserDataManager userDataRepository, ILibraryManager libraryManager, IDtoService dtoService, IChannelManager channelManager) { _userManager = userManager; _userDataRepository = userDataRepository; _libraryManager = libraryManager; - _itemRepo = itemRepo; _dtoService = dtoService; _channelManager = channelManager; } - /// - /// Gets the specified request. - /// - /// The request. - /// System.Object. - public object Get(GetSimilarTrailers request) - { - var dtoOptions = GetDtoOptions(request); - - var result = SimilarItemsHelper.GetSimilarItemsResult(dtoOptions, _userManager, - _itemRepo, - _libraryManager, - _userDataRepository, - _dtoService, - Logger, - - // Strip out secondary versions - request, item => (item is Movie) && !((Video)item).PrimaryVersionId.HasValue, - - SimilarItemsHelper.GetSimiliarityScore); - - return ToOptimizedSerializedResultUsingCache(result); - } - public async Task Get(Getrailers request) { var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null; diff --git a/MediaBrowser.Api/Music/AlbumsService.cs b/MediaBrowser.Api/Music/AlbumsService.cs index ea87c3ad37..548598d429 100644 --- a/MediaBrowser.Api/Music/AlbumsService.cs +++ b/MediaBrowser.Api/Music/AlbumsService.cs @@ -4,6 +4,7 @@ using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Querying; using ServiceStack; using System; using System.Collections.Generic; @@ -16,6 +17,11 @@ namespace MediaBrowser.Api.Music { } + [Route("/Artists/{Id}/Similar", "GET", Summary = "Finds albums similar to a given album.")] + public class GetSimilarArtists : BaseGetSimilarItemsFromItem + { + } + [Authenticated] public class AlbumsService : BaseApiService { @@ -44,6 +50,17 @@ namespace MediaBrowser.Api.Music _dtoService = dtoService; } + public object Get(GetSimilarArtists request) + { + var result = GetSimilarItemsResult( + + request, + + SimilarItemsHelper.GetSimiliarityScore); + + return ToOptimizedSerializedResultUsingCache(result); + } + /// /// Gets the specified request. /// @@ -65,6 +82,39 @@ namespace MediaBrowser.Api.Music return ToOptimizedSerializedResultUsingCache(result); } + private ItemsResult GetSimilarItemsResult(BaseGetSimilarItemsFromItem request, Func, List, BaseItem, int> getSimilarityScore) + { + var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null; + + var item = string.IsNullOrEmpty(request.Id) ? + (!string.IsNullOrWhiteSpace(request.UserId) ? user.RootFolder : + _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id); + + var inputItems = _libraryManager.GetArtists(user.RootFolder.GetRecursiveChildren(user, i => i is IHasArtist).OfType()); + + var list = inputItems.ToList(); + + var items = SimilarItemsHelper.GetSimilaritems(item, _libraryManager, list, getSimilarityScore).ToList(); + + IEnumerable returnItems = items; + + if (request.Limit.HasValue) + { + returnItems = returnItems.Take(request.Limit.Value); + } + + var dtoOptions = GetDtoOptions(request); + + var result = new ItemsResult + { + Items = _dtoService.GetBaseItemDtos(returnItems, dtoOptions, user).ToArray(), + + TotalRecordCount = items.Count + }; + + return result; + } + /// /// Gets the album similarity score. /// diff --git a/MediaBrowser.Api/Music/InstantMixService.cs b/MediaBrowser.Api/Music/InstantMixService.cs index 506b7bc3a0..d2a4aa60cc 100644 --- a/MediaBrowser.Api/Music/InstantMixService.cs +++ b/MediaBrowser.Api/Music/InstantMixService.cs @@ -54,6 +54,11 @@ namespace MediaBrowser.Api.Music public string Id { get; set; } } + [Route("/Items/{Id}/InstantMix", "GET", Summary = "Creates an instant playlist based on a given item")] + public class GetInstantMixFromItem : BaseGetSimilarItemsFromItem + { + } + [Authenticated] public class InstantMixService : BaseApiService { @@ -71,6 +76,17 @@ namespace MediaBrowser.Api.Music _libraryManager = libraryManager; } + public object Get(GetInstantMixFromItem request) + { + var item = _libraryManager.GetItemById(request.Id); + + var user = _userManager.GetUserById(request.UserId); + + var items = _musicManager.GetInstantMixFromItem(item, user); + + return GetResult(items, user, request); + } + public object Get(GetInstantMixFromArtistId request) { var item = _libraryManager.GetItemById(request.Id); @@ -138,8 +154,9 @@ namespace MediaBrowser.Api.Music public object Get(GetInstantMixFromArtist request) { var user = _userManager.GetUserById(request.UserId); + var artist = _libraryManager.GetArtist(request.Name); - var items = _musicManager.GetInstantMixFromArtist(request.Name, user); + var items = _musicManager.GetInstantMixFromArtist(artist, user); return GetResult(items, user, request); } diff --git a/MediaBrowser.Api/PackageService.cs b/MediaBrowser.Api/PackageService.cs index 5ef8b09871..4f9efad50d 100644 --- a/MediaBrowser.Api/PackageService.cs +++ b/MediaBrowser.Api/PackageService.cs @@ -190,7 +190,7 @@ namespace MediaBrowser.Api /// System.Object. public async Task Get(GetPackages request) { - var packages = await _installationManager.GetAvailablePackages(CancellationToken.None, request.PackageType, _appHost.ApplicationVersion).ConfigureAwait(false); + var packages = await _installationManager.GetAvailablePackages(CancellationToken.None, false, request.PackageType, _appHost.ApplicationVersion).ConfigureAwait(false); if (!string.IsNullOrEmpty(request.TargetSystems)) { diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index dc5858e863..531d67eedd 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -263,38 +263,27 @@ namespace MediaBrowser.Api.Playback return returnFirstIfNoIndex ? streams.FirstOrDefault() : null; } - protected EncodingQuality GetQualitySetting() - { - var quality = ApiEntryPoint.Instance.GetEncodingOptions().EncodingQuality; - - if (quality == EncodingQuality.Auto) - { - var cpuCount = Environment.ProcessorCount; - - if (cpuCount >= 4) - { - //return EncodingQuality.HighQuality; - } - - return EncodingQuality.HighSpeed; - } - - return quality; - } - /// /// Gets the number of threads. /// /// System.Int32. protected int GetNumberOfThreads(StreamState state, bool isWebm) { + var threads = ApiEntryPoint.Instance.GetEncodingOptions().EncodingThreadCount; + if (isWebm) { // Recommended per docs return Math.Max(Environment.ProcessorCount - 1, 2); } - return 0; + // Automatic + if (threads == -1) + { + return 0; + } + + return threads; } protected string H264Encoder @@ -326,77 +315,31 @@ namespace MediaBrowser.Api.Playback var isVc1 = state.VideoStream != null && string.Equals(state.VideoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase); - var qualitySetting = GetQualitySetting(); - if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase)) { param = "-preset superfast"; - switch (qualitySetting) - { - case EncodingQuality.HighSpeed: - param += " -crf 23"; - break; - case EncodingQuality.HighQuality: - param += " -crf 20"; - break; - case EncodingQuality.MaxQuality: - param += " -crf 18"; - break; - } + param += " -crf 23"; } else if (string.Equals(videoCodec, "libx265", StringComparison.OrdinalIgnoreCase)) { param = "-preset fast"; - switch (qualitySetting) - { - case EncodingQuality.HighSpeed: - param += " -crf 28"; - break; - case EncodingQuality.HighQuality: - param += " -crf 25"; - break; - case EncodingQuality.MaxQuality: - param += " -crf 21"; - break; - } + param += " -crf 28"; } // h264 (h264_qsv) else if (string.Equals(videoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)) { - switch (qualitySetting) - { - case EncodingQuality.HighSpeed: - param = "-preset 7"; - break; - case EncodingQuality.HighQuality: - param = "-preset 4"; - break; - case EncodingQuality.MaxQuality: - param = "-preset 1"; - break; - } + param = "-preset 7"; } // h264 (libnvenc) else if (string.Equals(videoCodec, "libnvenc", StringComparison.OrdinalIgnoreCase)) { - switch (qualitySetting) - { - case EncodingQuality.HighSpeed: - param = "-preset high-performance"; - break; - case EncodingQuality.HighQuality: - param = ""; - break; - case EncodingQuality.MaxQuality: - param = "-preset high-quality"; - break; - } + param = "-preset high-performance"; } // webm @@ -409,20 +352,7 @@ namespace MediaBrowser.Api.Playback var qmin = "0"; var qmax = "50"; - switch (qualitySetting) - { - case EncodingQuality.HighSpeed: - crf = "10"; - break; - case EncodingQuality.HighQuality: - crf = "6"; - break; - case EncodingQuality.MaxQuality: - crf = "4"; - break; - default: - throw new ArgumentException("Unrecognized quality setting"); - } + crf = "10"; if (isVc1) { @@ -689,7 +619,7 @@ namespace MediaBrowser.Api.Playback // TODO: Perhaps also use original_size=1920x800 ?? return string.Format("subtitles=filename='{0}'{1},setpts=PTS -{2}/TB", - subtitlePath.Replace('\\', '/').Replace("'", "\\'").Replace(":/", "\\:/"), + MediaEncoder.EscapeSubtitleFilterPath(subtitlePath), charsetParam, seconds.ToString(UsCulture)); } @@ -697,7 +627,7 @@ namespace MediaBrowser.Api.Playback var mediaPath = state.MediaPath ?? string.Empty; return string.Format("subtitles='{0}:si={1}',setpts=PTS -{2}/TB", - mediaPath.Replace('\\', '/').Replace("'", "\\'").Replace(":/", "\\:/"), + MediaEncoder.EscapeSubtitleFilterPath(mediaPath), state.InternalSubtitleStreamOffset.ToString(UsCulture), seconds.ToString(UsCulture)); } @@ -794,7 +724,7 @@ namespace MediaBrowser.Api.Playback var channelLimit = codec.IndexOf("mp3", StringComparison.OrdinalIgnoreCase) != -1 ? 2 - : 5; + : 6; // If we don't have any media info then limit it to 5 to prevent encoding errors due to asking for too many channels return Math.Min(request.MaxAudioChannels.Value, channelLimit); @@ -819,11 +749,11 @@ namespace MediaBrowser.Api.Playback /// /// Gets the audio encoder. /// - /// The request. + /// The state. /// System.String. - protected string GetAudioEncoder(StreamRequest request) + protected string GetAudioEncoder(StreamState state) { - var codec = request.AudioCodec; + var codec = state.OutputAudioCodec; if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase)) { @@ -848,11 +778,11 @@ namespace MediaBrowser.Api.Playback /// /// Gets the name of the output video codec /// - /// The request. + /// The state. /// System.String. - protected string GetVideoEncoder(VideoStreamRequest request) + protected string GetVideoEncoder(StreamState state) { - var codec = request.VideoCodec; + var codec = state.OutputVideoCodec; if (!string.IsNullOrEmpty(codec)) { @@ -924,7 +854,7 @@ namespace MediaBrowser.Api.Playback state.IsoMount = await IsoManager.Mount(state.MediaPath, cancellationTokenSource.Token).ConfigureAwait(false); } - if (state.MediaSource.RequiresOpening) + if (state.MediaSource.RequiresOpening && string.IsNullOrWhiteSpace(state.Request.LiveStreamId)) { var liveStreamResponse = await MediaSourceManager.OpenLiveStream(new LiveStreamRequest { @@ -1704,11 +1634,6 @@ namespace MediaBrowser.Api.Playback private void TryStreamCopy(StreamState state, VideoStreamRequest videoRequest) { - if (!EnableStreamCopy) - { - return; - } - if (state.VideoStream != null && CanStreamCopyVideo(videoRequest, state.VideoStream)) { state.OutputVideoCodec = "copy"; @@ -1720,14 +1645,6 @@ namespace MediaBrowser.Api.Playback } } - protected virtual bool EnableStreamCopy - { - get - { - return true; - } - } - private void AttachMediaSourceInfo(StreamState state, MediaSourceInfo mediaSource, VideoStreamRequest videoRequest, @@ -1811,13 +1728,18 @@ namespace MediaBrowser.Api.Playback state.MediaSource = mediaSource; } - private bool CanStreamCopyVideo(VideoStreamRequest request, MediaStream videoStream) + protected virtual bool CanStreamCopyVideo(VideoStreamRequest request, MediaStream videoStream) { if (videoStream.IsInterlaced) { return false; } + if (videoStream.IsAnamorphic ?? false) + { + return false; + } + // Can't stream copy if we're burning in subtitles if (request.SubtitleStreamIndex.HasValue) { @@ -1954,7 +1876,7 @@ namespace MediaBrowser.Api.Playback return Array.FindIndex(list.ToArray(), t => string.Equals(t, profile, StringComparison.OrdinalIgnoreCase)); } - private bool CanStreamCopyAudio(VideoStreamRequest request, MediaStream audioStream, List supportedAudioCodecs) + protected virtual bool CanStreamCopyAudio(VideoStreamRequest request, MediaStream audioStream, List supportedAudioCodecs) { // Source and target codecs must match if (string.IsNullOrEmpty(audioStream.Codec) || !supportedAudioCodecs.Contains(audioStream.Codec, StringComparer.OrdinalIgnoreCase)) diff --git a/MediaBrowser.Api/Playback/Dash/MpegDashService.cs b/MediaBrowser.Api/Playback/Dash/MpegDashService.cs index 47eb38b2d5..c201ffd587 100644 --- a/MediaBrowser.Api/Playback/Dash/MpegDashService.cs +++ b/MediaBrowser.Api/Playback/Dash/MpegDashService.cs @@ -378,7 +378,7 @@ namespace MediaBrowser.Api.Playback.Dash protected override string GetAudioArguments(StreamState state) { - var codec = GetAudioEncoder(state.Request); + var codec = GetAudioEncoder(state); if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase)) { @@ -408,7 +408,7 @@ namespace MediaBrowser.Api.Playback.Dash protected override string GetVideoArguments(StreamState state) { - var codec = GetVideoEncoder(state.VideoRequest); + var codec = GetVideoEncoder(state); var args = "-codec:v:0 " + codec; diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index b2ffeca3db..5d377366a0 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -4,6 +4,7 @@ using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Dlna; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.Extensions; using MediaBrowser.Model.IO; using MediaBrowser.Model.Net; @@ -310,5 +311,35 @@ namespace MediaBrowser.Api.Playback.Hls { return 0; } + + protected override bool CanStreamCopyVideo(VideoStreamRequest request, MediaStream videoStream) + { + if (videoStream.KeyFrames == null || videoStream.KeyFrames.Count == 0) + { + Logger.Debug("Cannot stream copy video due to missing keyframe info"); + return false; + } + + var previousSegment = 0; + foreach (var frame in videoStream.KeyFrames) + { + var length = frame - previousSegment; + + // Don't allow really long segments because this could result in long download times + if (length > 10000) + { + Logger.Debug("Cannot stream copy video due to long segment length of {0}ms", length); + return false; + } + previousSegment = frame; + } + + return base.CanStreamCopyVideo(request, videoStream); + } + + protected override bool CanStreamCopyAudio(VideoStreamRequest request, MediaStream audioStream, List supportedAudioCodecs) + { + return false; + } } } diff --git a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs index 0a432a5802..cbea1ca0ca 100644 --- a/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs @@ -13,7 +13,6 @@ using MediaBrowser.Model.IO; using MediaBrowser.Model.Serialization; using ServiceStack; using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -161,7 +160,6 @@ namespace MediaBrowser.Api.Playback.Hls var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8"); var segmentPath = GetSegmentPath(state, playlistPath, requestedIndex); - var segmentLength = state.SegmentLength; var segmentExtension = GetSegmentFileExtension(state); @@ -170,7 +168,7 @@ namespace MediaBrowser.Api.Playback.Hls if (File.Exists(segmentPath)) { job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); - return await GetSegmentResult(playlistPath, segmentPath, requestedIndex, segmentLength, job, cancellationToken).ConfigureAwait(false); + return await GetSegmentResult(state, playlistPath, segmentPath, requestedIndex, job, cancellationToken).ConfigureAwait(false); } await ApiEntryPoint.Instance.TranscodingStartLock.WaitAsync(cancellationTokenSource.Token).ConfigureAwait(false); @@ -179,7 +177,7 @@ namespace MediaBrowser.Api.Playback.Hls if (File.Exists(segmentPath)) { job = ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); - return await GetSegmentResult(playlistPath, segmentPath, requestedIndex, segmentLength, job, cancellationToken).ConfigureAwait(false); + return await GetSegmentResult(state, playlistPath, segmentPath, requestedIndex, job, cancellationToken).ConfigureAwait(false); } else { @@ -210,14 +208,12 @@ namespace MediaBrowser.Api.Playback.Hls { ApiEntryPoint.Instance.KillTranscodingJobs(request.DeviceId, request.PlaySessionId, p => false); - await ReadSegmentLengths(playlistPath).ConfigureAwait(false); - if (currentTranscodingIndex.HasValue) { DeleteLastFile(playlistPath, segmentExtension, 0); } - request.StartTimeTicks = GetSeekPositionTicks(state, playlistPath, requestedIndex); + request.StartTimeTicks = GetStartPositionTicks(state, requestedIndex); job = await StartFfMpeg(state, playlistPath, cancellationTokenSource).ConfigureAwait(false); } @@ -252,84 +248,76 @@ namespace MediaBrowser.Api.Playback.Hls Logger.Info("returning {0}", segmentPath); job = job ?? ApiEntryPoint.Instance.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); - return await GetSegmentResult(playlistPath, segmentPath, requestedIndex, segmentLength, job, cancellationToken).ConfigureAwait(false); + return await GetSegmentResult(state, playlistPath, segmentPath, requestedIndex, job, cancellationToken).ConfigureAwait(false); } - private static readonly ConcurrentDictionary SegmentLengths = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - private async Task ReadSegmentLengths(string playlist) - { - try - { - using (var fileStream = GetPlaylistFileStream(playlist)) - { - using (var reader = new StreamReader(fileStream)) - { - double duration = -1; - - while (!reader.EndOfStream) - { - var text = await reader.ReadLineAsync().ConfigureAwait(false); + // 256k + private const int BufferSize = 262144; - if (text.StartsWith("#EXTINF", StringComparison.OrdinalIgnoreCase)) - { - var parts = text.Split(new[] { ':' }, 2); - if (parts.Length == 2) - { - var time = parts[1].Trim(new[] { ',' }).Trim(); - double timeValue; - if (double.TryParse(time, NumberStyles.Any, CultureInfo.InvariantCulture, out timeValue)) - { - duration = timeValue; - continue; - } - } - } - else if (duration != -1) - { - SegmentLengths.AddOrUpdate(text, duration, (k, v) => duration); - Logger.Debug("Added segment length of {0} for {1}", duration, text); - } + private long GetStartPositionTicks(StreamState state, int requestedIndex) + { + double startSeconds = 0; + var lengths = GetSegmentLengths(state); - duration = -1; - } - } - } - } - catch (DirectoryNotFoundException) + for (var i = 0; i < requestedIndex; i++) { - + startSeconds += lengths[requestedIndex]; } - catch (FileNotFoundException) - { - } + var position = TimeSpan.FromSeconds(startSeconds).Ticks; + return position; } - private long GetSeekPositionTicks(StreamState state, string playlist, int requestedIndex) + private long GetEndPositionTicks(StreamState state, int requestedIndex) { double startSeconds = 0; + var lengths = GetSegmentLengths(state); - for (var i = 0; i < requestedIndex; i++) + for (var i = 0; i <= requestedIndex; i++) { - var segmentPath = GetSegmentPath(state, playlist, i); - - //double length; - //if (SegmentLengths.TryGetValue(Path.GetFileName(segmentPath), out length)) - //{ - // Logger.Debug("Found segment length of {0} for index {1}", length, i); - // startSeconds += length; - //} - //else - //{ - // startSeconds += state.SegmentLength; - //} - startSeconds += state.SegmentLength; + startSeconds += lengths[requestedIndex]; } var position = TimeSpan.FromSeconds(startSeconds).Ticks; return position; } + private double[] GetSegmentLengths(StreamState state) + { + var result = new List(); + var encoder = GetVideoEncoder(state); + + if (string.Equals(encoder, "copy", StringComparison.OrdinalIgnoreCase)) + { + var videoStream = state.VideoStream; + if (videoStream.KeyFrames != null && videoStream.KeyFrames.Count > 0) + { + foreach (var frame in videoStream.KeyFrames) + { + var seconds = TimeSpan.FromMilliseconds(frame).TotalSeconds; + seconds -= result.Sum(); + result.Add(seconds); + } + return result.ToArray(); + } + } + + var ticks = state.RunTimeTicks ?? 0; + + var segmentLengthTicks = TimeSpan.FromSeconds(state.SegmentLength).Ticks; + + while (ticks > 0) + { + var length = ticks >= segmentLengthTicks ? segmentLengthTicks : ticks; + + result.Add(TimeSpan.FromTicks(length).TotalSeconds); + + ticks -= length; + } + + return result.ToArray(); + } + public int? GetCurrentTranscodingIndex(string playlist, string segmentExtension) { var job = ApiEntryPoint.Instance.GetTranscodingJob(playlist, TranscodingJobType); @@ -434,17 +422,16 @@ namespace MediaBrowser.Api.Playback.Hls return Path.Combine(folder, filename + index.ToString(UsCulture) + GetSegmentFileExtension(state)); } - private async Task GetSegmentResult(string playlistPath, + private async Task GetSegmentResult(StreamState state, string playlistPath, string segmentPath, int segmentIndex, - int segmentLength, TranscodingJob transcodingJob, CancellationToken cancellationToken) { // If all transcoding has completed, just return immediately if (transcodingJob != null && transcodingJob.HasExited && File.Exists(segmentPath)) { - return GetSegmentResult(segmentPath, segmentIndex, segmentLength, transcodingJob); + return GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob); } var segmentFilename = Path.GetFileName(segmentPath); @@ -455,21 +442,18 @@ namespace MediaBrowser.Api.Playback.Hls { using (var fileStream = GetPlaylistFileStream(playlistPath)) { - using (var reader = new StreamReader(fileStream)) + using (var reader = new StreamReader(fileStream, Encoding.UTF8, true, BufferSize)) { - while (!reader.EndOfStream) - { - var text = await reader.ReadLineAsync().ConfigureAwait(false); + var text = await reader.ReadToEndAsync().ConfigureAwait(false); - // If it appears in the playlist, it's done - if (text.IndexOf(segmentFilename, StringComparison.OrdinalIgnoreCase) != -1) + // If it appears in the playlist, it's done + if (text.IndexOf(segmentFilename, StringComparison.OrdinalIgnoreCase) != -1) + { + if (File.Exists(segmentPath)) { - if (File.Exists(segmentPath)) - { - return GetSegmentResult(segmentPath, segmentIndex, segmentLength, transcodingJob); - } - break; + return GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob); } + //break; } } } @@ -518,13 +502,12 @@ namespace MediaBrowser.Api.Playback.Hls //} cancellationToken.ThrowIfCancellationRequested(); - return GetSegmentResult(segmentPath, segmentIndex, segmentLength, transcodingJob); + return GetSegmentResult(state, segmentPath, segmentIndex, transcodingJob); } - private object GetSegmentResult(string segmentPath, int index, int segmentLength, TranscodingJob transcodingJob) + private object GetSegmentResult(StreamState state, string segmentPath, int index, TranscodingJob transcodingJob) { - var segmentEndingSeconds = (1 + index) * segmentLength; - var segmentEndingPositionTicks = TimeSpan.FromSeconds(segmentEndingSeconds).Ticks; + var segmentEndingPositionTicks = GetEndPositionTicks(state, index); return ResultFactory.GetStaticFileResult(Request, new StaticFileResultOptions { @@ -751,25 +734,23 @@ namespace MediaBrowser.Api.Playback.Hls { var state = await GetState(request, CancellationToken.None).ConfigureAwait(false); + var segmentLengths = GetSegmentLengths(state); + var builder = new StringBuilder(); builder.AppendLine("#EXTM3U"); builder.AppendLine("#EXT-X-VERSION:3"); - builder.AppendLine("#EXT-X-TARGETDURATION:" + (state.SegmentLength).ToString(UsCulture)); + builder.AppendLine("#EXT-X-TARGETDURATION:" + Math.Ceiling((segmentLengths.Length > 0 ? segmentLengths.Max() : state.SegmentLength)).ToString(UsCulture)); builder.AppendLine("#EXT-X-MEDIA-SEQUENCE:0"); var queryStringIndex = Request.RawUrl.IndexOf('?'); var queryString = queryStringIndex == -1 ? string.Empty : Request.RawUrl.Substring(queryStringIndex); - var seconds = TimeSpan.FromTicks(state.RunTimeTicks ?? 0).TotalSeconds; - var index = 0; - while (seconds > 0) + foreach (var length in segmentLengths) { - var length = seconds >= state.SegmentLength ? state.SegmentLength : seconds; - - builder.AppendLine("#EXTINF:" + length.ToString(UsCulture) + ","); + builder.AppendLine("#EXTINF:" + length.ToString("0.000000", UsCulture) + ","); builder.AppendLine(string.Format("hlsdynamic/{0}/{1}{2}{3}", @@ -778,7 +759,6 @@ namespace MediaBrowser.Api.Playback.Hls GetSegmentFileExtension(isOutputVideo), queryString)); - seconds -= state.SegmentLength; index++; } @@ -791,7 +771,7 @@ namespace MediaBrowser.Api.Playback.Hls protected override string GetAudioArguments(StreamState state) { - var codec = GetAudioEncoder(state.Request); + var codec = GetAudioEncoder(state); if (!state.IsOutputVideo) { @@ -856,7 +836,7 @@ namespace MediaBrowser.Api.Playback.Hls return string.Empty; } - var codec = GetVideoEncoder(state.VideoRequest); + var codec = GetVideoEncoder(state); var args = "-codec:v:0 " + codec; @@ -877,7 +857,7 @@ namespace MediaBrowser.Api.Playback.Hls } else { - var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})", + var keyFrameArg = string.Format(" -force_key_frames \"expr:gte(t,n_forced*{0})\"", state.SegmentLength.ToString(UsCulture)); var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream; @@ -889,7 +869,7 @@ namespace MediaBrowser.Api.Playback.Hls // Add resolution params, if specified if (!hasGraphicalSubs) { - args += GetOutputSizeParam(state, codec, false); + args += GetOutputSizeParam(state, codec, EnableCopyTs(state)); } // This is for internal graphical subs @@ -898,17 +878,17 @@ namespace MediaBrowser.Api.Playback.Hls args += GetGraphicalSubtitleParam(state, codec); } - args += " -flags +loop-global_header -sc_threshold 0"; - } - - if (!EnableSplitTranscoding(state)) - { - //args += " -copyts"; + args += " -flags -global_header -sc_threshold 0"; } return args; } + private bool EnableCopyTs(StreamState state) + { + return state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream; + } + protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding) { var threads = GetNumberOfThreads(state, false); @@ -921,22 +901,7 @@ namespace MediaBrowser.Api.Playback.Hls var toTimeParam = string.Empty; var timestampOffsetParam = string.Empty; - if (EnableSplitTranscoding(state)) - { - var startTime = state.Request.StartTimeTicks ?? 0; - var durationSeconds = ApiEntryPoint.Instance.GetEncodingOptions().ThrottleThresholdInSeconds; - - var endTime = startTime + TimeSpan.FromSeconds(durationSeconds).Ticks; - endTime = Math.Min(endTime, state.RunTimeTicks.Value); - - if (endTime < state.RunTimeTicks.Value) - { - //toTimeParam = " -to " + MediaEncoder.GetTimeParameter(endTime); - toTimeParam = " -t " + MediaEncoder.GetTimeParameter(TimeSpan.FromSeconds(durationSeconds).Ticks); - } - } - - if (state.IsOutputVideo && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) && (state.Request.StartTimeTicks ?? 0) > 0) + if (state.IsOutputVideo && !EnableCopyTs(state) && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase) && (state.Request.StartTimeTicks ?? 0) > 0) { timestampOffsetParam = " -output_ts_offset " + MediaEncoder.GetTimeParameter(state.Request.StartTimeTicks ?? 0).ToString(CultureInfo.InvariantCulture); } @@ -978,36 +943,7 @@ namespace MediaBrowser.Api.Playback.Hls protected override bool EnableThrottling(StreamState state) { - return !EnableSplitTranscoding(state); - } - - private bool EnableSplitTranscoding(StreamState state) - { - return false; - if (string.Equals(Request.QueryString["EnableSplitTranscoding"], "false", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - if (string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase)) - { - return false; - } - - return state.RunTimeTicks.HasValue && state.IsOutputVideo; - } - - protected override bool EnableStreamCopy - { - get - { - return false; - } + return true; } /// diff --git a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs index d8e3423fcf..2d1abf7e9d 100644 --- a/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/VideoHlsService.cs @@ -48,7 +48,7 @@ namespace MediaBrowser.Api.Playback.Hls /// System.String. protected override string GetAudioArguments(StreamState state) { - var codec = GetAudioEncoder(state.Request); + var codec = GetAudioEncoder(state); if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase)) { @@ -83,7 +83,7 @@ namespace MediaBrowser.Api.Playback.Hls /// System.String. protected override string GetVideoArguments(StreamState state) { - var codec = GetVideoEncoder(state.VideoRequest); + var codec = GetVideoEncoder(state); var args = "-codec:v:0 " + codec; @@ -100,7 +100,7 @@ namespace MediaBrowser.Api.Playback.Hls args; } - var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})", + var keyFrameArg = string.Format(" -force_key_frames \"expr:gte(t,n_forced*{0})\"", state.SegmentLength.ToString(UsCulture)); var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream; diff --git a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs index 6a3443f359..adedd94616 100644 --- a/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs +++ b/MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs @@ -91,6 +91,9 @@ namespace MediaBrowser.Api.Playback.Progressive private readonly IFileSystem _fileSystem; private readonly TranscodingJob _job; + // 256k + private const int BufferSize = 262144; + private long _bytesWritten = 0; public ProgressiveFileCopier(IFileSystem fileSystem, TranscodingJob job) @@ -108,7 +111,7 @@ namespace MediaBrowser.Api.Playback.Progressive { while (eofCount < 15) { - CopyToInternal(fs, outputStream, 81920); + CopyToInternal(fs, outputStream, BufferSize); var fsPosition = fs.Position; diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs index ebd72b2ce1..84ae26248f 100644 --- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs +++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs @@ -89,7 +89,7 @@ namespace MediaBrowser.Api.Playback.Progressive protected override string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding) { // Get the output codec name - var videoCodec = GetVideoEncoder(state.VideoRequest); + var videoCodec = GetVideoEncoder(state); var format = string.Empty; var keyFrame = string.Empty; @@ -183,7 +183,7 @@ namespace MediaBrowser.Api.Playback.Progressive } // Get the output codec name - var codec = GetAudioEncoder(state.Request); + var codec = GetAudioEncoder(state); var args = "-codec:a:0 " + codec; diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index 02b7720a4a..34dc5ea12f 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/MediaBrowser.Api/Playback/StreamState.cs @@ -185,7 +185,7 @@ namespace MediaBrowser.Api.Playback private async void DisposeLiveStream() { - if (MediaSource.RequiresClosing && string.IsNullOrWhiteSpace(Request.LiveStreamId)) + if ((MediaSource.RequiresClosing) && string.IsNullOrWhiteSpace(Request.LiveStreamId)) { try { diff --git a/MediaBrowser.Api/Reports/Activities/ReportActivitiesBuilder.cs b/MediaBrowser.Api/Reports/Activities/ReportActivitiesBuilder.cs new file mode 100644 index 0000000000..83816c6e3b --- /dev/null +++ b/MediaBrowser.Api/Reports/Activities/ReportActivitiesBuilder.cs @@ -0,0 +1,259 @@ +using MediaBrowser.Model.Activity; +using MediaBrowser.Model.Querying; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MediaBrowser.Controller.Library; +namespace MediaBrowser.Api.Reports +{ + /// A report activities builder. + /// + public class ReportActivitiesBuilder : ReportBuilderBase + { + #region [Constructors] + + /// + /// Initializes a new instance of the MediaBrowser.Api.Reports.ReportActivitiesBuilder class. + /// Manager for library. + /// Manager for user. + public ReportActivitiesBuilder(ILibraryManager libraryManager, IUserManager userManager) + : base(libraryManager) + { + _userManager = userManager; + } + + #endregion + + #region [Private Fields] + + private readonly IUserManager _userManager; ///< Manager for user + + #endregion + + #region [Public Methods] + + /// Gets a result. + /// The query result. + /// The request. + /// The result. + public ReportResult GetResult(QueryResult queryResult, IReportsQuery request) + { + ReportDisplayType displayType = ReportHelper.GetReportDisplayType(request.DisplayType); + List> options = this.GetReportOptions(request, + () => this.GetDefaultHeaderMetadata(), + (hm) => this.GetOption(hm)).Where(x => this.DisplayTypeVisible(x.Header.DisplayType, displayType)).ToList(); + + var headers = GetHeaders(options); + var rows = GetReportRows(queryResult.Items, options); + + ReportResult result = new ReportResult { Headers = headers }; + HeaderMetadata groupBy = ReportHelper.GetHeaderMetadataType(request.GroupBy); + int i = headers.FindIndex(x => x.FieldName == groupBy); + if (groupBy != HeaderMetadata.None && i >= 0) + { + var rowsGroup = rows.SelectMany(x => x.Columns[i].Name.Split(';'), (x, g) => new { Group = g.Trim(), Rows = x }) + .GroupBy(x => x.Group) + .OrderBy(x => x.Key) + .Select(x => new ReportGroup { Name = x.Key, Rows = x.Select(r => r.Rows).ToList() }); + + result.Groups = rowsGroup.ToList(); + result.IsGrouped = true; + } + else + { + result.Rows = rows; + result.IsGrouped = false; + } + + return result; + } + + #endregion + + #region [Protected Internal Methods] + + /// Gets the headers. + /// Type of the header. + /// The request. + /// The headers. + /// + protected internal override List GetHeaders(H request) + { + return this.GetHeaders(request, () => this.GetDefaultHeaderMetadata(), (hm) => this.GetOption(hm)); + } + + #endregion + + #region [Private Methods] + + /// Gets default header metadata. + /// The default header metadata. + private List GetDefaultHeaderMetadata() + { + return new List + { + HeaderMetadata.UserPrimaryImage, + HeaderMetadata.Date, + HeaderMetadata.User, + HeaderMetadata.Type, + HeaderMetadata.Severity, + HeaderMetadata.Name, + HeaderMetadata.ShortOverview, + HeaderMetadata.Overview, + //HeaderMetadata.UserId + //HeaderMetadata.Item, + }; + } + + /// Gets an option. + /// The header. + /// The sort field. + /// The option. + private ReportOptions GetOption(HeaderMetadata header, string sortField = "") + { + HeaderMetadata internalHeader = header; + + ReportOptions option = new ReportOptions() + { + Header = new ReportHeader + { + HeaderFieldType = ReportFieldType.String, + SortField = sortField, + Type = "", + ItemViewType = ItemViewType.None + } + }; + + switch (header) + { + case HeaderMetadata.Name: + option.Column = (i, r) => i.Name; + option.Header.SortField = ""; + break; + case HeaderMetadata.Overview: + option.Column = (i, r) => i.Overview; + option.Header.SortField = ""; + option.Header.CanGroup = false; + break; + + case HeaderMetadata.ShortOverview: + option.Column = (i, r) => i.ShortOverview; + option.Header.SortField = ""; + option.Header.CanGroup = false; + break; + + case HeaderMetadata.Type: + option.Column = (i, r) => i.Type; + option.Header.SortField = ""; + break; + + case HeaderMetadata.Date: + option.Column = (i, r) => i.Date; + option.Header.SortField = ""; + option.Header.HeaderFieldType = ReportFieldType.DateTime; + option.Header.Type = ""; + break; + + case HeaderMetadata.UserPrimaryImage: + //option.Column = (i, r) => i.UserPrimaryImageTag; + option.Header.DisplayType = ReportDisplayType.Screen; + option.Header.ItemViewType = ItemViewType.UserPrimaryImage; + option.Header.ShowHeaderLabel = false; + internalHeader = HeaderMetadata.User; + option.Header.CanGroup = false; + option.Column = (i, r) => + { + if (!string.IsNullOrEmpty(i.UserId)) + { + MediaBrowser.Controller.Entities.User user = _userManager.GetUserById(i.UserId); + if (user != null) + { + var dto = _userManager.GetUserDto(user); + return dto.PrimaryImageTag; + } + } + return string.Empty; + }; + option.Header.SortField = ""; + break; + case HeaderMetadata.Severity: + option.Column = (i, r) => i.Severity; + option.Header.SortField = ""; + break; + case HeaderMetadata.Item: + option.Column = (i, r) => i.ItemId; + option.Header.SortField = ""; + break; + case HeaderMetadata.User: + option.Column = (i, r) => + { + if (!string.IsNullOrEmpty(i.UserId)) + { + MediaBrowser.Controller.Entities.User user = _userManager.GetUserById(i.UserId); + if (user != null) + return user.Name; + } + return string.Empty; + }; + option.Header.SortField = ""; + break; + case HeaderMetadata.UserId: + option.Column = (i, r) => i.UserId; + option.Header.SortField = ""; + break; + } + + option.Header.Name = GetLocalizedHeader(internalHeader); + option.Header.FieldName = header; + + return option; + } + + /// Gets report rows. + /// The items. + /// Options for controlling the operation. + /// The report rows. + private List GetReportRows(IEnumerable items, List> options) + { + var rows = new List(); + + foreach (ActivityLogEntry item in items) + { + ReportRow rRow = GetRow(item); + foreach (ReportOptions option in options) + { + object itemColumn = option.Column != null ? option.Column(item, rRow) : ""; + object itemId = option.ItemID != null ? option.ItemID(item) : ""; + ReportItem rItem = new ReportItem + { + Name = ReportHelper.ConvertToString(itemColumn, option.Header.HeaderFieldType), + Id = ReportHelper.ConvertToString(itemId, ReportFieldType.Object) + }; + rRow.Columns.Add(rItem); + } + + rows.Add(rRow); + } + + return rows; + } + + /// Gets a row. + /// The item. + /// The row. + private ReportRow GetRow(ActivityLogEntry item) + { + ReportRow rRow = new ReportRow + { + Id = item.Id, + UserId = item.UserId + }; + return rRow; + } + + #endregion + + } +} diff --git a/MediaBrowser.Api/Reports/Common/HeaderActivitiesMetadata.cs b/MediaBrowser.Api/Reports/Common/HeaderActivitiesMetadata.cs new file mode 100644 index 0000000000..69c0ff2344 --- /dev/null +++ b/MediaBrowser.Api/Reports/Common/HeaderActivitiesMetadata.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MediaBrowser.Api.Reports +{ + public enum HeaderActivitiesMetadata + { + None, + Name, + Overview, + ShortOverview, + Type, + Date, + UserPrimaryImageTag, + Severity, + Item, + User + } +} diff --git a/MediaBrowser.Api/Reports/Common/HeaderMetadata.cs b/MediaBrowser.Api/Reports/Common/HeaderMetadata.cs index 3cb8f722d2..af5abf46aa 100644 --- a/MediaBrowser.Api/Reports/Common/HeaderMetadata.cs +++ b/MediaBrowser.Api/Reports/Common/HeaderMetadata.cs @@ -35,13 +35,39 @@ namespace MediaBrowser.Api.Reports Subtitles, Genres, Countries, - StatusImage, + Status, Tracks, EpisodeSeries, EpisodeSeason, AudioAlbumArtist, MusicArtist, AudioAlbum, - Status + Locked, + Unidentified, + ImagePrimary, + ImageBackdrop, + ImageLogo, + Actor, + Studios, + Composer, + Director, + GuestStar, + Producer, + Writer, + Artist, + Years, + ParentalRatings, + CommunityRatings, + + //Activity logs + Overview, + ShortOverview, + Type, + Date, + UserPrimaryImage, + Severity, + Item, + User, + UserId } } diff --git a/MediaBrowser.Api/Reports/Common/ItemViewType.cs b/MediaBrowser.Api/Reports/Common/ItemViewType.cs index 3e09a290dc..5126457cf9 100644 --- a/MediaBrowser.Api/Reports/Common/ItemViewType.cs +++ b/MediaBrowser.Api/Reports/Common/ItemViewType.cs @@ -4,17 +4,23 @@ using System.Linq; namespace MediaBrowser.Api.Reports { - public enum ItemViewType - { - None, - Detail, - Edit, - List, - ItemByNameDetails, - StatusImage, - EmbeddedImage, - SubtitleImage, - TrailersImage, - SpecialsImage - } + public enum ItemViewType + { + None, + Detail, + Edit, + List, + ItemByNameDetails, + StatusImage, + EmbeddedImage, + SubtitleImage, + TrailersImage, + SpecialsImage, + LockDataImage, + UnidentifiedImage, + TagsPrimaryImage, + TagsBackdropImage, + TagsLogoImage, + UserPrimaryImage + } } diff --git a/MediaBrowser.Api/Reports/Common/ReportBuilderBase.cs b/MediaBrowser.Api/Reports/Common/ReportBuilderBase.cs index af6dc997c2..6e1dd8d35f 100644 --- a/MediaBrowser.Api/Reports/Common/ReportBuilderBase.cs +++ b/MediaBrowser.Api/Reports/Common/ReportBuilderBase.cs @@ -13,217 +13,351 @@ using System.Threading.Tasks; namespace MediaBrowser.Api.Reports { - /// A report builder base. - public class ReportBuilderBase - { - /// - /// Initializes a new instance of the MediaBrowser.Api.Reports.ReportBuilderBase class. - /// Manager for library. - public ReportBuilderBase(ILibraryManager libraryManager) - { - _libraryManager = libraryManager; - } - - /// Manager for library. - protected readonly ILibraryManager _libraryManager; - - /// Gets audio stream. - /// The item. - /// The audio stream. - protected string GetAudioStream(BaseItem item) - { - var stream = GetStream(item, MediaStreamType.Audio); - if (stream != null) - return stream.Codec.ToUpper() == "DCA" ? stream.Profile : stream.Codec. - ToUpper(); - - return string.Empty; - } - - /// Gets an episode. - /// The item. - /// The episode. - protected string GetEpisode(BaseItem item) - { - - if (item.GetClientTypeName() == ChannelMediaContentType.Episode.ToString() && item.ParentIndexNumber != null) - return "Season " + item.ParentIndexNumber; - else - return item.Name; - } - - /// Gets a genre. - /// The name. - /// The genre. - protected Genre GetGenre(string name) - { - if (string.IsNullOrEmpty(name)) - return null; - return _libraryManager.GetGenre(name); - } - - /// Gets genre identifier. - /// The name. - /// The genre identifier. - protected string GetGenreID(string name) - { - if (string.IsNullOrEmpty(name)) - return string.Empty; - return string.Format("{0:N}", - GetGenre(name).Id); - } - - /// Gets list as string. - /// The items. - /// The list as string. - protected string GetListAsString(List items) - { - return String.Join("; ", items); - } - - /// Gets media source information. - /// The item. - /// The media source information. - protected MediaSourceInfo GetMediaSourceInfo(BaseItem item) - { - var mediaSource = item as IHasMediaSources; - if (mediaSource != null) - return mediaSource.GetMediaSources(false).FirstOrDefault(n => n.Type == MediaSourceType.Default); - - return null; - } - - /// Gets an object. - /// Generic type parameter. - /// Type of the r. - /// The item. - /// The function. - /// The default value. - /// The object. - protected R GetObject(BaseItem item, Func function, R defaultValue = default(R)) where T : class - { - var value = item as T; - if (value != null && function != null) - return function(value); - else - return defaultValue; - } - - /// Gets a person. - /// The name. - /// The person. - protected Person GetPerson(string name) - { - if (string.IsNullOrEmpty(name)) - return null; - return _libraryManager.GetPerson(name); - } - - /// Gets person identifier. - /// The name. - /// The person identifier. - protected string GetPersonID(string name) - { - if (string.IsNullOrEmpty(name)) - return string.Empty; - return string.Format("{0:N}", - GetPerson(name).Id); - } - - /// Gets runtime date time. - /// The runtime. - /// The runtime date time. - protected double? GetRuntimeDateTime(long? runtime) - { - if (runtime.HasValue) + /// A report builder base. + public abstract class ReportBuilderBase + { + + #region [Constructors] + + /// + /// Initializes a new instance of the MediaBrowser.Api.Reports.ReportBuilderBase class. + /// Manager for library. + public ReportBuilderBase(ILibraryManager libraryManager) + { + _libraryManager = libraryManager; + } + + #endregion + + #region [Protected Fields] + + /// Manager for library. + protected readonly ILibraryManager _libraryManager; ///< Manager for library + + protected Func GetBoolString = s => s == true ? "x" : ""; ///< . + + #endregion + + #region [Protected Internal Methods] + + /// Gets the headers. + /// Type of the header. + /// The request. + /// The headers. + protected internal abstract List GetHeaders(H request) where H : IReportsHeader; + + #endregion + + #region [Protected Methods] + + /// Gets active headers. + /// Generic type parameter. + /// Options for controlling the operation. + /// The active headers. + protected List GetActiveHeaders(List> options, ReportDisplayType displayType) + { + List headers = new List(); + foreach (ReportOptions option in options.Where(x => this.DisplayTypeVisible(x.Header.DisplayType, displayType))) + { + headers.Add(option.Header); + } + + return headers; + } + + /// Gets audio stream. + /// The item. + /// The audio stream. + protected string GetAudioStream(BaseItem item) + { + var stream = GetStream(item, MediaStreamType.Audio); + if (stream != null) + return stream.Codec.ToUpper() == "DCA" ? stream.Profile : stream.Codec. + ToUpper(); + + return string.Empty; + } + + /// Gets an episode. + /// The item. + /// The episode. + protected string GetEpisode(BaseItem item) + { + + if (item.GetClientTypeName() == ChannelMediaContentType.Episode.ToString() && item.ParentIndexNumber != null) + return "Season " + item.ParentIndexNumber; + else + return item.Name; + } + + /// Gets a genre. + /// The name. + /// The genre. + protected Genre GetGenre(string name) + { + if (string.IsNullOrEmpty(name)) + return null; + return _libraryManager.GetGenre(name); + } + + /// Gets genre identifier. + /// The name. + /// The genre identifier. + protected string GetGenreID(string name) + { + if (string.IsNullOrEmpty(name)) + return string.Empty; + return string.Format("{0:N}", + GetGenre(name).Id); + } + + /// Gets the headers. + /// Generic type parameter. + /// Options for controlling the operation. + /// The headers. + protected List GetHeaders(List> options) + { + List headers = new List(); + foreach (ReportOptions option in options) + { + headers.Add(option.Header); + } + + return headers; + } + + /// Gets the headers. + /// Generic type parameter. + /// The request. + /// The get headers metadata. + /// Options for controlling the get. + /// The headers. + protected List GetHeaders(IReportsHeader request, Func> getHeadersMetadata, Func> getOptions) + { + List> options = this.GetReportOptions(request, getHeadersMetadata, getOptions); + return this.GetHeaders(options); + } + + /// Gets list as string. + /// The items. + /// The list as string. + protected string GetListAsString(List items) + { + return String.Join("; ", items); + } + + /// Gets localized header. + /// The internal header. + /// The localized header. + protected static string GetLocalizedHeader(HeaderMetadata internalHeader) + { + string headerName = ""; + if (internalHeader != HeaderMetadata.None) + { + string localHeader = "Header" + internalHeader.ToString(); + headerName = ReportHelper.GetCoreLocalizedString(localHeader); + } + return headerName; + } + + /// Gets media source information. + /// The item. + /// The media source information. + protected MediaSourceInfo GetMediaSourceInfo(BaseItem item) + { + var mediaSource = item as IHasMediaSources; + if (mediaSource != null) + return mediaSource.GetMediaSources(false).FirstOrDefault(n => n.Type == MediaSourceType.Default); + + return null; + } + + /// Gets an object. + /// Generic type parameter. + /// Type of the r. + /// The item. + /// The function. + /// The default value. + /// The object. + protected R GetObject(BaseItem item, Func function, R defaultValue = default(R)) where T : class + { + var value = item as T; + if (value != null && function != null) + return function(value); + else + return defaultValue; + } + + /// Gets a person. + /// The name. + /// The person. + protected Person GetPerson(string name) + { + if (string.IsNullOrEmpty(name)) + return null; + return _libraryManager.GetPerson(name); + } + + /// Gets person identifier. + /// The name. + /// The person identifier. + protected string GetPersonID(string name) + { + if (string.IsNullOrEmpty(name)) + return string.Empty; + return string.Format("{0:N}", + GetPerson(name).Id); + } + + /// Gets report options. + /// Generic type parameter. + /// The request. + /// The get headers metadata. + /// Options for controlling the get. + /// The report options. + protected List> GetReportOptions(IReportsHeader request, Func> getHeadersMetadata, Func> getOptions) + { + List headersMetadata = getHeadersMetadata(); + List> options = new List>(); + ReportDisplayType displayType = ReportHelper.GetReportDisplayType(request.DisplayType); + foreach (HeaderMetadata header in headersMetadata) + { + ReportOptions headerOptions = getOptions(header); + if (this.DisplayTypeVisible(headerOptions.Header.DisplayType, displayType)) + options.Add(headerOptions); + } + + if (request != null && !string.IsNullOrEmpty(request.ReportColumns)) + { + List headersMetadataFiltered = ReportHelper.GetFilteredReportHeaderMetadata(request.ReportColumns, () => headersMetadata); + foreach (ReportHeader header in options.Select(x => x.Header)) + { + + if (this.DisplayTypeVisible(header.DisplayType, displayType)) + { + + if (!headersMetadataFiltered.Contains(header.FieldName) && displayType != ReportDisplayType.Export) + { + header.DisplayType = ReportDisplayType.None; + } + } + else + header.DisplayType = ReportDisplayType.None; + } + } + + return options; + } + + /// Gets runtime date time. + /// The runtime. + /// The runtime date time. + protected double? GetRuntimeDateTime(long? runtime) + { + if (runtime.HasValue) return Math.Ceiling(new TimeSpan(runtime.Value).TotalMinutes); - return null; - } - - /// Gets series production year. - /// The item. - /// The series production year. - protected string GetSeriesProductionYear(BaseItem item) - { - - string productionYear = item.ProductionYear.ToString(); - var series = item as Series; - if (series == null) - { - if (item.ProductionYear == null || item.ProductionYear == 0) - return string.Empty; - return productionYear; - } - - if (series.Status == SeriesStatus.Continuing) - return productionYear += "-Present"; - - if (series.EndDate != null && series.EndDate.Value.Year != series.ProductionYear) - return productionYear += "-" + series.EndDate.Value.Year; - - return productionYear; - } - - /// Gets a stream. - /// The item. - /// Type of the stream. - /// The stream. - protected MediaStream GetStream(BaseItem item, MediaStreamType streamType) - { - var itemInfo = GetMediaSourceInfo(item); - if (itemInfo != null) - return itemInfo.MediaStreams.FirstOrDefault(n => n.Type == streamType); - - return null; - } - - /// Gets a studio. - /// The name. - /// The studio. - protected Studio GetStudio(string name) - { - if (string.IsNullOrEmpty(name)) - return null; - return _libraryManager.GetStudio(name); - } - - /// Gets studio identifier. - /// The name. - /// The studio identifier. - protected string GetStudioID(string name) - { - if (string.IsNullOrEmpty(name)) - return string.Empty; - return string.Format("{0:N}", - GetStudio(name).Id); - } - - /// Gets video resolution. - /// The item. - /// The video resolution. - protected string GetVideoResolution(BaseItem item) - { - var stream = GetStream(item, - MediaStreamType.Video); - if (stream != null && stream.Width != null) - return string.Format("{0} * {1}", - stream.Width, - (stream.Height != null ? stream.Height.ToString() : "-")); - - return string.Empty; - } - - /// Gets video stream. - /// The item. - /// The video stream. - protected string GetVideoStream(BaseItem item) - { - var stream = GetStream(item, MediaStreamType.Video); - if (stream != null) - return stream.Codec.ToUpper(); - - return string.Empty; - } - - } + return null; + } + + /// Gets series production year. + /// The item. + /// The series production year. + protected string GetSeriesProductionYear(BaseItem item) + { + + string productionYear = item.ProductionYear.ToString(); + var series = item as Series; + if (series == null) + { + if (item.ProductionYear == null || item.ProductionYear == 0) + return string.Empty; + return productionYear; + } + + if (series.Status == SeriesStatus.Continuing) + return productionYear += "-Present"; + + if (series.EndDate != null && series.EndDate.Value.Year != series.ProductionYear) + return productionYear += "-" + series.EndDate.Value.Year; + + return productionYear; + } + + /// Gets a stream. + /// The item. + /// Type of the stream. + /// The stream. + protected MediaStream GetStream(BaseItem item, MediaStreamType streamType) + { + var itemInfo = GetMediaSourceInfo(item); + if (itemInfo != null) + return itemInfo.MediaStreams.FirstOrDefault(n => n.Type == streamType); + + return null; + } + + /// Gets a studio. + /// The name. + /// The studio. + protected Studio GetStudio(string name) + { + if (string.IsNullOrEmpty(name)) + return null; + return _libraryManager.GetStudio(name); + } + + /// Gets studio identifier. + /// The name. + /// The studio identifier. + protected string GetStudioID(string name) + { + if (string.IsNullOrEmpty(name)) + return string.Empty; + return string.Format("{0:N}", + GetStudio(name).Id); + } + + /// Gets video resolution. + /// The item. + /// The video resolution. + protected string GetVideoResolution(BaseItem item) + { + var stream = GetStream(item, + MediaStreamType.Video); + if (stream != null && stream.Width != null) + return string.Format("{0} * {1}", + stream.Width, + (stream.Height != null ? stream.Height.ToString() : "-")); + + return string.Empty; + } + + /// Gets video stream. + /// The item. + /// The video stream. + protected string GetVideoStream(BaseItem item) + { + var stream = GetStream(item, MediaStreamType.Video); + if (stream != null) + return stream.Codec.ToUpper(); + + return string.Empty; + } + + /// Displays a type visible. + /// Type of the header display. + /// Type of the display. + /// true if it succeeds, false if it fails. + protected bool DisplayTypeVisible(ReportDisplayType headerDisplayType, ReportDisplayType displayType) + { + if (headerDisplayType == ReportDisplayType.None) + return false; + + bool rval = headerDisplayType == displayType || headerDisplayType == ReportDisplayType.ScreenExport && (displayType == ReportDisplayType.Screen || displayType == ReportDisplayType.Export); + return rval; + } + + #endregion + + } } diff --git a/MediaBrowser.Api/Reports/Common/ReportDisplayType.cs b/MediaBrowser.Api/Reports/Common/ReportDisplayType.cs new file mode 100644 index 0000000000..69d6d71eec --- /dev/null +++ b/MediaBrowser.Api/Reports/Common/ReportDisplayType.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MediaBrowser.Api.Reports +{ + public enum ReportDisplayType + { + None, + Screen, + Export, + ScreenExport + } +} diff --git a/MediaBrowser.Api/Reports/Common/ReportHelper.cs b/MediaBrowser.Api/Reports/Common/ReportHelper.cs index a557248c61..9dc4fbd51e 100644 --- a/MediaBrowser.Api/Reports/Common/ReportHelper.cs +++ b/MediaBrowser.Api/Reports/Common/ReportHelper.cs @@ -2,100 +2,137 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace MediaBrowser.Api.Reports { - public class ReportHelper - { - /// Gets java script localized string. - /// The phrase. - /// The java script localized string. - public static string GetJavaScriptLocalizedString(string phrase) - { - var dictionary = BaseItem.LocalizationManager.GetJavaScriptLocalizationDictionary(BaseItem.ConfigurationManager.Configuration.UICulture); - - string value; - - if (dictionary.TryGetValue(phrase, out value)) - { - return value; - } - - return phrase; - } - - /// Gets server localized string. - /// The phrase. - /// The server localized string. - public static string GetServerLocalizedString(string phrase) - { - return BaseItem.LocalizationManager.GetLocalizedString(phrase, BaseItem.ConfigurationManager.Configuration.UICulture); - } - - /// Gets row type. - /// The type. - /// The row type. - public static ReportViewType GetRowType(string rowType) - { - if (string.IsNullOrEmpty(rowType)) - return ReportViewType.BaseItem; - - ReportViewType rType; - - if (!Enum.TryParse(rowType, out rType)) - return ReportViewType.BaseItem; - - return rType; - } - - /// Gets header metadata type. - /// The header. - /// The header metadata type. - public static HeaderMetadata GetHeaderMetadataType(string header) - { - if (string.IsNullOrEmpty(header)) - return HeaderMetadata.None; - - HeaderMetadata rType; - - if (!Enum.TryParse(header, out rType)) - return HeaderMetadata.None; - - return rType; - } - - /// Convert field to string. - /// Generic type parameter. - /// The value. - /// Type of the field. - /// The field converted to string. - public static string ConvertToString(T value, ReportFieldType fieldType) - { - if (value == null) - return ""; - switch (fieldType) - { - case ReportFieldType.String: - return value.ToString(); - case ReportFieldType.Boolean: - return value.ToString(); - case ReportFieldType.Date: - return string.Format("{0:d}", value); - case ReportFieldType.Time: - return string.Format("{0:t}", value); - case ReportFieldType.DateTime: - return string.Format("{0:d}", value); + /// A report helper. + public class ReportHelper + { + #region [Public Methods] + + /// Convert field to string. + /// Generic type parameter. + /// The value. + /// Type of the field. + /// The field converted to string. + public static string ConvertToString(T value, ReportFieldType fieldType) + { + if (value == null) + return ""; + switch (fieldType) + { + case ReportFieldType.String: + return value.ToString(); + case ReportFieldType.Boolean: + return value.ToString(); + case ReportFieldType.Date: + return string.Format("{0:d}", value); + case ReportFieldType.Time: + return string.Format("{0:t}", value); + case ReportFieldType.DateTime: + return string.Format("{0:d}", value); case ReportFieldType.Minutes: return string.Format("{0}mn", value); - case ReportFieldType.Int: - return string.Format("", value); - default: - if (value is Guid) - return string.Format("{0:N}", value); - return value.ToString(); - } - } - } + case ReportFieldType.Int: + return string.Format("", value); + default: + if (value is Guid) + return string.Format("{0:N}", value); + return value.ToString(); + } + } + + /// Gets filtered report header metadata. + /// The report columns. + /// The default return value. + /// The filtered report header metadata. + public static List GetFilteredReportHeaderMetadata(string reportColumns, Func> defaultReturnValue = null) + { + if (!string.IsNullOrEmpty(reportColumns)) + { + var s = reportColumns.Split('|').Select(x => ReportHelper.GetHeaderMetadataType(x)).Where(x => x != HeaderMetadata.None); + return s.ToList(); + } + else + if (defaultReturnValue != null) + return defaultReturnValue(); + else + return new List(); + } + + /// Gets header metadata type. + /// The header. + /// The header metadata type. + public static HeaderMetadata GetHeaderMetadataType(string header) + { + if (string.IsNullOrEmpty(header)) + return HeaderMetadata.None; + + HeaderMetadata rType; + + if (!Enum.TryParse(header, out rType)) + return HeaderMetadata.None; + + return rType; + } + + /// Gets report view type. + /// The type. + /// The report view type. + public static ReportViewType GetReportViewType(string rowType) + { + if (string.IsNullOrEmpty(rowType)) + return ReportViewType.ReportData; + + ReportViewType rType; + + if (!Enum.TryParse(rowType, out rType)) + return ReportViewType.ReportData; + + return rType; + } + + /// Gets row type. + /// The type. + /// The row type. + public static ReportIncludeItemTypes GetRowType(string rowType) + { + if (string.IsNullOrEmpty(rowType)) + return ReportIncludeItemTypes.BaseItem; + + ReportIncludeItemTypes rType; + + if (!Enum.TryParse(rowType, out rType)) + return ReportIncludeItemTypes.BaseItem; + + return rType; + } + + /// Gets report display type. + /// Type of the display. + /// The report display type. + public static ReportDisplayType GetReportDisplayType(string displayType) + { + if (string.IsNullOrEmpty(displayType)) + return ReportDisplayType.ScreenExport; + + ReportDisplayType rType; + + if (!Enum.TryParse(displayType, out rType)) + return ReportDisplayType.ScreenExport; + + return rType; + } + + /// Gets core localized string. + /// The phrase. + /// The core localized string. + public static string GetCoreLocalizedString(string phrase) + { + return BaseItem.LocalizationManager.GetLocalizedString(phrase); + } + + #endregion + + } } diff --git a/MediaBrowser.Api/Reports/Common/ReportIncludeItemTypes.cs b/MediaBrowser.Api/Reports/Common/ReportIncludeItemTypes.cs new file mode 100644 index 0000000000..4b6d96792c --- /dev/null +++ b/MediaBrowser.Api/Reports/Common/ReportIncludeItemTypes.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MediaBrowser.Api.Reports +{ + public enum ReportIncludeItemTypes + { + MusicArtist, + MusicAlbum, + Book, + BoxSet, + Episode, + Game, + Video, + Movie, + MusicVideo, + Trailer, + Season, + Series, + Audio, + BaseItem, + Artist + } +} diff --git a/MediaBrowser.Api/Reports/Common/ReportViewType.cs b/MediaBrowser.Api/Reports/Common/ReportViewType.cs index efdfcb0e79..d9b049bb79 100644 --- a/MediaBrowser.Api/Reports/Common/ReportViewType.cs +++ b/MediaBrowser.Api/Reports/Common/ReportViewType.cs @@ -6,20 +6,9 @@ namespace MediaBrowser.Api.Reports { public enum ReportViewType { - MusicArtist, - MusicAlbum, - Book, - BoxSet, - Episode, - Game, - Video, - Movie, - MusicVideo, - Trailer, - Season, - Series, - Audio, - BaseItem, - Artist + ReportData, + ReportStatistics, + ReportActivities + } } diff --git a/MediaBrowser.Api/Reports/Data/ReportBuilder.cs b/MediaBrowser.Api/Reports/Data/ReportBuilder.cs index 00ce183178..512441bd7b 100644 --- a/MediaBrowser.Api/Reports/Data/ReportBuilder.cs +++ b/MediaBrowser.Api/Reports/Data/ReportBuilder.cs @@ -17,164 +17,98 @@ using System.Threading.Tasks; namespace MediaBrowser.Api.Reports { - /// A report builder. - /// - public class ReportBuilder : ReportBuilderBase - { - - /// - /// Initializes a new instance of the MediaBrowser.Api.Reports.ReportBuilder class. - /// Manager for library. - public ReportBuilder(ILibraryManager libraryManager) - : base(libraryManager) - { - } - - private Func GetBoolString = s => s == true ? "x" : ""; - - public ReportResult GetReportResult(BaseItem[] items, ReportViewType reportRowType, BaseReportRequest request) - { - List headersMetadata = this.GetFilteredReportHeaderMetadata(reportRowType, request); - - var headers = GetReportHeaders(reportRowType, headersMetadata); - var rows = GetReportRows(items, headersMetadata); - - ReportResult result = new ReportResult { Headers = headers }; - HeaderMetadata groupBy = ReportHelper.GetHeaderMetadataType(request.GroupBy); - int i = headers.FindIndex(x => x.FieldName == groupBy); - if (groupBy != HeaderMetadata.None && i > 0) - { - var rowsGroup = rows.SelectMany(x => x.Columns[i].Name.Split(';'), (x, g) => new { Genre = g.Trim(), Rows = x }) - .GroupBy(x => x.Genre) - .OrderBy(x => x.Key) - .Select(x => new ReportGroup { Name = x.Key, Rows = x.Select(r => r.Rows).ToList() }); - - result.Groups = rowsGroup.ToList(); - result.IsGrouped = true; - } - else - { - result.Rows = rows; - result.IsGrouped = false; - } - - return result; - } - - public List GetReportHeaders(ReportViewType reportRowType, BaseReportRequest request) - { - List headersMetadata = this.GetReportHeaders(reportRowType); - if (request != null && !string.IsNullOrEmpty(request.ReportColumns)) - { - List headersMetadataFiltered = this.GetFilteredReportHeaderMetadata(reportRowType, request); - foreach (ReportHeader reportHeader in headersMetadata) - { - if (!headersMetadataFiltered.Contains(reportHeader.FieldName)) - { - reportHeader.Visible = false; - } - } - - - } - - return headersMetadata; - } - - public List GetReportHeaders(ReportViewType reportRowType, List headersMetadata = null) - { - if (headersMetadata == null) - headersMetadata = this.GetDefaultReportHeaderMetadata(reportRowType); - - List> options = new List>(); - foreach (HeaderMetadata header in headersMetadata) - { - options.Add(GetReportOption(header)); - } - - - List headers = new List(); - foreach (ReportOptions option in options) - { - headers.Add(option.Header); - } - return headers; - } - - private List GetReportRows(IEnumerable items, List headersMetadata) - { - List> options = new List>(); - foreach (HeaderMetadata header in headersMetadata) - { - options.Add(GetReportOption(header)); - } - - var rows = new List(); - - foreach (BaseItem item in items) - { - ReportRow rRow = GetRow(item); - foreach (ReportOptions option in options) - { - object itemColumn = option.Column != null ? option.Column(item, rRow) : ""; - object itemId = option.ItemID != null ? option.ItemID(item) : ""; - ReportItem rItem = new ReportItem - { - Name = ReportHelper.ConvertToString(itemColumn, option.Header.HeaderFieldType), - Id = ReportHelper.ConvertToString(itemId, ReportFieldType.Object) - }; - rRow.Columns.Add(rItem); - } - - rows.Add(rRow); - } - - return rows; - } - - /// Gets a row. - /// The item. - /// The row. - private ReportRow GetRow(BaseItem item) - { - var hasTrailers = item as IHasTrailers; - var hasSpecialFeatures = item as IHasSpecialFeatures; - var video = item as Video; - ReportRow rRow = new ReportRow - { - Id = item.Id.ToString("N"), - HasLockData = item.IsLocked, - IsUnidentified = item.IsUnidentified, - HasLocalTrailer = hasTrailers != null ? hasTrailers.GetTrailerIds().Count() > 0 : false, - HasImageTagsPrimary = (item.ImageInfos != null && item.ImageInfos.Count(n => n.Type == ImageType.Primary) > 0), - HasImageTagsBackdrop = (item.ImageInfos != null && item.ImageInfos.Count(n => n.Type == ImageType.Backdrop) > 0), - HasImageTagsLogo = (item.ImageInfos != null && item.ImageInfos.Count(n => n.Type == ImageType.Logo) > 0), - HasSpecials = hasSpecialFeatures != null ? hasSpecialFeatures.SpecialFeatureIds.Count > 0 : false, - HasSubtitles = video != null ? video.HasSubtitles : false, - RowType = ReportHelper.GetRowType(item.GetClientTypeName()) - }; - return rRow; - } - public List GetFilteredReportHeaderMetadata(ReportViewType reportRowType, BaseReportRequest request) - { - if (request != null && !string.IsNullOrEmpty(request.ReportColumns)) - { - var s = request.ReportColumns.Split('|').Select(x => ReportHelper.GetHeaderMetadataType(x)).Where(x => x != HeaderMetadata.None); - return s.ToList(); - } - else - return this.GetDefaultReportHeaderMetadata(reportRowType); - - } - - public List GetDefaultReportHeaderMetadata(ReportViewType reportRowType) - { - switch (reportRowType) - { - case ReportViewType.Season: - return new List - { - HeaderMetadata.StatusImage, + /// A report builder. + /// + public class ReportBuilder : ReportBuilderBase + { + + #region [Constructors] + + /// + /// Initializes a new instance of the MediaBrowser.Api.Reports.ReportBuilder class. + /// Manager for library. + public ReportBuilder(ILibraryManager libraryManager) + : base(libraryManager) + { + } + + #endregion + + #region [Public Methods] + + /// Gets report result. + /// The items. + /// The request. + /// The report result. + public ReportResult GetResult(BaseItem[] items, IReportsQuery request) + { + ReportIncludeItemTypes reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes); + ReportDisplayType displayType = ReportHelper.GetReportDisplayType(request.DisplayType); + + List> options = this.GetReportOptions(request, + () => this.GetDefaultHeaderMetadata(reportRowType), + (hm) => this.GetOption(hm)).Where(x => this.DisplayTypeVisible(x.Header.DisplayType, displayType)).ToList(); + + var headers = GetHeaders(options); + var rows = GetReportRows(items, options); + + ReportResult result = new ReportResult { Headers = headers }; + HeaderMetadata groupBy = ReportHelper.GetHeaderMetadataType(request.GroupBy); + int i = headers.FindIndex(x => x.FieldName == groupBy); + if (groupBy != HeaderMetadata.None && i >= 0) + { + var rowsGroup = rows.SelectMany(x => x.Columns[i].Name.Split(';'), (x, g) => new { Group = g.Trim(), Rows = x }) + .GroupBy(x => x.Group) + .OrderBy(x => x.Key) + .Select(x => new ReportGroup { Name = x.Key, Rows = x.Select(r => r.Rows).ToList() }); + + result.Groups = rowsGroup.ToList(); + result.IsGrouped = true; + } + else + { + result.Rows = rows; + result.IsGrouped = false; + } + + return result; + } + + #endregion + + #region [Protected Internal Methods] + + /// Gets the headers. + /// Type of the header. + /// The request. + /// The headers. + /// + protected internal override List GetHeaders(H request) + { + ReportIncludeItemTypes reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes); + return this.GetHeaders(request, () => this.GetDefaultHeaderMetadata(reportRowType), (hm) => this.GetOption(hm)); + } + + #endregion + + #region [Private Methods] + + /// Gets default report header metadata. + /// Type of the report row. + /// The default report header metadata. + private List GetDefaultHeaderMetadata(ReportIncludeItemTypes reportIncludeItemTypes) + { + switch (reportIncludeItemTypes) + { + case ReportIncludeItemTypes.Season: + return new List + { + HeaderMetadata.Status, + HeaderMetadata.Locked, + HeaderMetadata.Unidentified, + HeaderMetadata.ImagePrimary, + HeaderMetadata.ImageBackdrop, + HeaderMetadata.ImageLogo, HeaderMetadata.Series, HeaderMetadata.Season, HeaderMetadata.SeasonNumber, @@ -183,10 +117,15 @@ namespace MediaBrowser.Api.Reports HeaderMetadata.Genres }; - case ReportViewType.Series: - return new List - { - HeaderMetadata.StatusImage, + case ReportIncludeItemTypes.Series: + return new List + { + HeaderMetadata.Status, + HeaderMetadata.Locked, + HeaderMetadata.Unidentified, + HeaderMetadata.ImagePrimary, + HeaderMetadata.ImageBackdrop, + HeaderMetadata.ImageLogo, HeaderMetadata.Name, HeaderMetadata.Network, HeaderMetadata.DateAdded, @@ -199,10 +138,15 @@ namespace MediaBrowser.Api.Reports HeaderMetadata.Specials }; - case ReportViewType.MusicAlbum: - return new List + case ReportIncludeItemTypes.MusicAlbum: + return new List { - HeaderMetadata.StatusImage, + HeaderMetadata.Status, + HeaderMetadata.Locked, + HeaderMetadata.Unidentified, + HeaderMetadata.ImagePrimary, + HeaderMetadata.ImageBackdrop, + HeaderMetadata.ImageLogo, HeaderMetadata.Name, HeaderMetadata.AlbumArtist, HeaderMetadata.DateAdded, @@ -212,10 +156,15 @@ namespace MediaBrowser.Api.Reports HeaderMetadata.Genres }; - case ReportViewType.MusicArtist: - return new List + case ReportIncludeItemTypes.MusicArtist: + return new List { - HeaderMetadata.StatusImage, + HeaderMetadata.Status, + HeaderMetadata.Locked, + HeaderMetadata.Unidentified, + HeaderMetadata.ImagePrimary, + HeaderMetadata.ImageBackdrop, + HeaderMetadata.ImageLogo, HeaderMetadata.MusicArtist, HeaderMetadata.Countries, HeaderMetadata.DateAdded, @@ -223,10 +172,15 @@ namespace MediaBrowser.Api.Reports HeaderMetadata.Genres }; - case ReportViewType.Game: - return new List + case ReportIncludeItemTypes.Game: + return new List { - HeaderMetadata.StatusImage, + HeaderMetadata.Status, + HeaderMetadata.Locked, + HeaderMetadata.Unidentified, + HeaderMetadata.ImagePrimary, + HeaderMetadata.ImageBackdrop, + HeaderMetadata.ImageLogo, HeaderMetadata.Name, HeaderMetadata.GameSystem, HeaderMetadata.DateAdded, @@ -239,10 +193,15 @@ namespace MediaBrowser.Api.Reports HeaderMetadata.Trailers }; - case ReportViewType.Movie: - return new List + case ReportIncludeItemTypes.Movie: + return new List { - HeaderMetadata.StatusImage, + HeaderMetadata.Status, + HeaderMetadata.Locked, + HeaderMetadata.Unidentified, + HeaderMetadata.ImagePrimary, + HeaderMetadata.ImageBackdrop, + HeaderMetadata.ImageLogo, HeaderMetadata.Name, HeaderMetadata.DateAdded, HeaderMetadata.ReleaseDate, @@ -259,10 +218,15 @@ namespace MediaBrowser.Api.Reports HeaderMetadata.Specials }; - case ReportViewType.Book: - return new List + case ReportIncludeItemTypes.Book: + return new List { - HeaderMetadata.StatusImage, + HeaderMetadata.Status, + HeaderMetadata.Locked, + HeaderMetadata.Unidentified, + HeaderMetadata.ImagePrimary, + HeaderMetadata.ImageBackdrop, + HeaderMetadata.ImageLogo, HeaderMetadata.Name, HeaderMetadata.DateAdded, HeaderMetadata.ReleaseDate, @@ -272,10 +236,15 @@ namespace MediaBrowser.Api.Reports HeaderMetadata.CommunityRating }; - case ReportViewType.BoxSet: - return new List + case ReportIncludeItemTypes.BoxSet: + return new List { - HeaderMetadata.StatusImage, + HeaderMetadata.Status, + HeaderMetadata.Locked, + HeaderMetadata.Unidentified, + HeaderMetadata.ImagePrimary, + HeaderMetadata.ImageBackdrop, + HeaderMetadata.ImageLogo, HeaderMetadata.Name, HeaderMetadata.DateAdded, HeaderMetadata.ReleaseDate, @@ -286,10 +255,15 @@ namespace MediaBrowser.Api.Reports HeaderMetadata.Trailers }; - case ReportViewType.Audio: - return new List + case ReportIncludeItemTypes.Audio: + return new List { - HeaderMetadata.StatusImage, + HeaderMetadata.Status, + HeaderMetadata.Locked, + HeaderMetadata.Unidentified, + HeaderMetadata.ImagePrimary, + HeaderMetadata.ImageBackdrop, + HeaderMetadata.ImageLogo, HeaderMetadata.Name, HeaderMetadata.AudioAlbumArtist, HeaderMetadata.AudioAlbum, @@ -305,10 +279,15 @@ namespace MediaBrowser.Api.Reports HeaderMetadata.Audio }; - case ReportViewType.Episode: - return new List + case ReportIncludeItemTypes.Episode: + return new List { - HeaderMetadata.StatusImage, + HeaderMetadata.Status, + HeaderMetadata.Locked, + HeaderMetadata.Unidentified, + HeaderMetadata.ImagePrimary, + HeaderMetadata.ImageBackdrop, + HeaderMetadata.ImageLogo, HeaderMetadata.Name, HeaderMetadata.EpisodeSeries, HeaderMetadata.Season, @@ -327,14 +306,23 @@ namespace MediaBrowser.Api.Reports HeaderMetadata.Specials }; - case ReportViewType.Video: - case ReportViewType.MusicVideo: - case ReportViewType.Trailer: - case ReportViewType.BaseItem: - default: - return new List + case ReportIncludeItemTypes.Video: + case ReportIncludeItemTypes.MusicVideo: + case ReportIncludeItemTypes.Trailer: + case ReportIncludeItemTypes.BaseItem: + default: + return new List { - HeaderMetadata.StatusImage, + HeaderMetadata.Status, + HeaderMetadata.Locked, + HeaderMetadata.Unidentified, + HeaderMetadata.ImagePrimary, + HeaderMetadata.ImageBackdrop, + HeaderMetadata.ImageLogo, + HeaderMetadata.Unidentified, + HeaderMetadata.ImagePrimary, + HeaderMetadata.ImageBackdrop, + HeaderMetadata.ImageLogo, HeaderMetadata.Name, HeaderMetadata.DateAdded, HeaderMetadata.ReleaseDate, @@ -351,239 +339,313 @@ namespace MediaBrowser.Api.Reports HeaderMetadata.Specials }; - } - - } - - /// Gets report option. - /// The header. - /// The sort field. - /// The report option. - private ReportOptions GetReportOption(HeaderMetadata header, string sortField = "") - { - ReportHeader reportHeader = new ReportHeader - { - HeaderFieldType = ReportFieldType.String, - SortField = sortField, - Type = "", - ItemViewType = ItemViewType.None - }; - - Func column = null; - Func itemId = null; - HeaderMetadata internalHeader = header; - - switch (header) - { - case HeaderMetadata.StatusImage: - reportHeader.ItemViewType = ItemViewType.StatusImage; - internalHeader = HeaderMetadata.Status; - reportHeader.CanGroup = false; - break; - - case HeaderMetadata.Name: - column = (i, r) => i.Name; - reportHeader.ItemViewType = ItemViewType.Detail; - reportHeader.SortField = "SortName"; - break; - - case HeaderMetadata.DateAdded: - column = (i, r) => i.DateCreated; - reportHeader.SortField = "DateCreated,SortName"; - reportHeader.HeaderFieldType = ReportFieldType.DateTime; - reportHeader.Type = ""; - break; - - case HeaderMetadata.PremiereDate: - case HeaderMetadata.ReleaseDate: - column = (i, r) => i.PremiereDate; - reportHeader.HeaderFieldType = ReportFieldType.DateTime; - reportHeader.SortField = "ProductionYear,PremiereDate,SortName"; - break; - - case HeaderMetadata.Runtime: - column = (i, r) => this.GetRuntimeDateTime(i.RunTimeTicks); - reportHeader.HeaderFieldType = ReportFieldType.Minutes; - reportHeader.SortField = "Runtime,SortName"; - break; - - case HeaderMetadata.PlayCount: - reportHeader.HeaderFieldType = ReportFieldType.Int; - break; - - case HeaderMetadata.Season: - column = (i, r) => this.GetEpisode(i); - reportHeader.ItemViewType = ItemViewType.Detail; - reportHeader.SortField = "SortName"; - break; - - case HeaderMetadata.SeasonNumber: - column = (i, r) => this.GetObject(i, (x) => x.IndexNumber == null ? "" : x.IndexNumber.ToString()); - reportHeader.SortField = "IndexNumber"; - reportHeader.HeaderFieldType = ReportFieldType.Int; - break; - - case HeaderMetadata.Series: - column = (i, r) => this.GetObject(i, (x) => x.SeriesName); - reportHeader.ItemViewType = ItemViewType.Detail; - reportHeader.SortField = "SeriesSortName,SortName"; - break; - - case HeaderMetadata.EpisodeSeries: - column = (i, r) => this.GetObject(i, (x) => x.SeriesName); - reportHeader.ItemViewType = ItemViewType.Detail; - itemId = (i) => - { - Series series = this.GetObject(i, (x) => x.Series); - if (series == null) - return string.Empty; - return series.Id; - }; - reportHeader.SortField = "SeriesSortName,SortName"; - internalHeader = HeaderMetadata.Series; - break; - - case HeaderMetadata.EpisodeSeason: - column = (i, r) => this.GetObject(i, (x) => x.SeriesName); - reportHeader.ItemViewType = ItemViewType.Detail; - itemId = (i) => - { - Season season = this.GetObject(i, (x) => x.Season); - if (season == null) - return string.Empty; - return season.Id; - }; - reportHeader.SortField = "SortName"; - internalHeader = HeaderMetadata.Season; - break; - - case HeaderMetadata.Network: - column = (i, r) => this.GetListAsString(i.Studios); - itemId = (i) => this.GetStudioID(i.Studios.FirstOrDefault()); - reportHeader.ItemViewType = ItemViewType.ItemByNameDetails; - reportHeader.SortField = "Studio,SortName"; - break; - - case HeaderMetadata.Year: - column = (i, r) => this.GetSeriesProductionYear(i); - reportHeader.SortField = "ProductionYear,PremiereDate,SortName"; - break; - - case HeaderMetadata.ParentalRating: - column = (i, r) => i.OfficialRating; - reportHeader.SortField = "OfficialRating,SortName"; - break; - - case HeaderMetadata.CommunityRating: - column = (i, r) => i.CommunityRating; - reportHeader.SortField = "CommunityRating,SortName"; - break; - - case HeaderMetadata.Trailers: - column = (i, r) => this.GetBoolString(r.HasLocalTrailer); - reportHeader.ItemViewType = ItemViewType.TrailersImage; - break; - - case HeaderMetadata.Specials: - column = (i, r) => this.GetBoolString(r.HasSpecials); - reportHeader.ItemViewType = ItemViewType.SpecialsImage; - break; - - case HeaderMetadata.GameSystem: - column = (i, r) => this.GetObject(i, (x) => x.GameSystem); - reportHeader.SortField = "GameSystem,SortName"; - break; - - case HeaderMetadata.Players: - column = (i, r) => this.GetObject(i, (x) => x.PlayersSupported); - reportHeader.SortField = "Players,GameSystem,SortName"; - break; - - case HeaderMetadata.AlbumArtist: - column = (i, r) => this.GetObject(i, (x) => x.AlbumArtist); - itemId = (i) => this.GetPersonID(this.GetObject(i, (x) => x.AlbumArtist)); - reportHeader.ItemViewType = ItemViewType.Detail; - reportHeader.SortField = "AlbumArtist,Album,SortName"; - - break; - case HeaderMetadata.MusicArtist: - column = (i, r) => this.GetObject(i, (x) => x.GetLookupInfo().Name); - reportHeader.ItemViewType = ItemViewType.Detail; - reportHeader.SortField = "AlbumArtist,Album,SortName"; - internalHeader = HeaderMetadata.AlbumArtist; - break; - case HeaderMetadata.AudioAlbumArtist: - column = (i, r) => this.GetListAsString(this.GetObject>(i, (x) => x.AlbumArtists)); - reportHeader.SortField = "AlbumArtist,Album,SortName"; - internalHeader = HeaderMetadata.AlbumArtist; - break; - - case HeaderMetadata.AudioAlbum: - column = (i, r) => this.GetObject(i, (x) => x.Album); - reportHeader.SortField = "Album,SortName"; - internalHeader = HeaderMetadata.Album; - break; - - case HeaderMetadata.Countries: - column = (i, r) => this.GetListAsString(this.GetObject>(i, (x) => x.ProductionLocations)); - break; - - case HeaderMetadata.Disc: - column = (i, r) => i.ParentIndexNumber; - break; - - case HeaderMetadata.Track: - column = (i, r) => i.IndexNumber; - break; - - case HeaderMetadata.Tracks: - column = (i, r) => this.GetObject>(i, (x) => x.Tracks.ToList(), new List + /// + /// true if this MediaBrowser.Api.Reports.GetActivityLogs has query limit, false if not. + bool HasQueryLimit { get; set; } + /// Gets or sets who group this MediaBrowser.Api.Reports.GetActivityLogs. + /// Describes who group this MediaBrowser.Api.Reports.GetActivityLogs. + string GroupBy { get; set; } /// - /// Gets or sets the video types. + /// Skips over a given number of items within the results. Use for paging. /// - /// The video types. - [ApiMember(Name = "VideoTypes", Description = "Optional filter by VideoType (videofile, dvd, bluray, iso). Allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string VideoTypes { get; set; } - + /// The start index. + int? StartIndex { get; set; } /// - /// Gets or sets the video formats. + /// The maximum number of items to return /// - /// The video formats. - [ApiMember(Name = "Is3D", Description = "Optional filter by items that are 3D, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? Is3D { get; set; } + /// The limit. + int? Limit { get; set; } - /// - /// Gets or sets the series status. - /// - /// The series status. - [ApiMember(Name = "SeriesStatus", Description = "Optional filter by Series Status. Allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string SeriesStatus { get; set; } + } + public interface IReportsHeader + { + /// Gets or sets the report view. + /// The report view. + string ReportView { get; set; } - [ApiMember(Name = "NameStartsWithOrGreater", Description = "Optional filter by items whose name is sorted equally or greater than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string NameStartsWithOrGreater { get; set; } + /// Gets or sets the report columns. + /// The report columns. + string ReportColumns { get; set; } - [ApiMember(Name = "NameStartsWith", Description = "Optional filter by items whose name is sorted equally than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string NameStartsWith { get; set; } + /// Gets or sets a list of types of the include items. + /// A list of types of the include items. + string IncludeItemTypes { get; set; } - [ApiMember(Name = "NameLessThan", Description = "Optional filter by items whose name is equally or lesser than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string NameLessThan { get; set; } + /// Gets or sets a list of types of the displays. + /// A list of types of the displays. + string DisplayType { get; set; } - [ApiMember(Name = "AlbumArtistStartsWithOrGreater", Description = "Optional filter by items whose album artist is sorted equally or greater than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string AlbumArtistStartsWithOrGreater { get; set; } + } - /// - /// Gets or sets the air days. - /// - /// The air days. - [ApiMember(Name = "AirDays", Description = "Optional filter by Series Air Days. Allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string AirDays { get; set; } + public class BaseReportRequest : BaseItemsRequest, IReportsQuery + { + /// Gets or sets the report view. + /// The report view. + [ApiMember(Name = "ReportView", Description = "The report view. Values (ReportData, ReportStatistics, ReportActivities)", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + public string ReportView { get; set; } + + /// Gets or sets the report view. + /// The report view. + [ApiMember(Name = "DisplayType", Description = "The report display type. Values (None, Screen, Export, ScreenExport)", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + public string DisplayType { get; set; } /// - /// Gets or sets the min offical rating. - /// - /// The min offical rating. - [ApiMember(Name = "MinOfficialRating", Description = "Optional filter by minimum official rating (PG, PG-13, TV-MA, etc).", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string MinOfficialRating { get; set; } + /// Gets or sets a value indicating whether this MediaBrowser.Api.Reports.BaseReportRequest has + /// query limit. + /// + /// true if this MediaBrowser.Api.Reports.BaseReportRequest has query limit, false if not. + [ApiMember(Name = "HasQueryLimit", Description = "Optional. If specified, results will include all records.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool HasQueryLimit { get; set; } /// - /// Gets or sets the max offical rating. - /// - /// The max offical rating. - [ApiMember(Name = "MaxOfficialRating", Description = "Optional filter by maximum official rating (PG, PG-13, TV-MA, etc).", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string MaxOfficialRating { get; set; } - - [ApiMember(Name = "HasThemeSong", Description = "Optional filter by items with theme songs.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public bool? HasThemeSong { get; set; } - - [ApiMember(Name = "HasThemeVideo", Description = "Optional filter by items with theme videos.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public bool? HasThemeVideo { get; set; } - - [ApiMember(Name = "HasSubtitles", Description = "Optional filter by items with subtitles.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public bool? HasSubtitles { get; set; } - - [ApiMember(Name = "HasSpecialFeature", Description = "Optional filter by items with special features.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public bool? HasSpecialFeature { get; set; } - - [ApiMember(Name = "HasTrailer", Description = "Optional filter by items with trailers.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public bool? HasTrailer { get; set; } - - [ApiMember(Name = "AdjacentTo", Description = "Optional. Return items that are siblings of a supplied item.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string AdjacentTo { get; set; } - - [ApiMember(Name = "MinIndexNumber", Description = "Optional filter by minimum index number.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? MinIndexNumber { get; set; } - - [ApiMember(Name = "MinPlayers", Description = "Optional filter by minimum number of game players.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? MinPlayers { get; set; } - - [ApiMember(Name = "MaxPlayers", Description = "Optional filter by maximum number of game players.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? MaxPlayers { get; set; } - - [ApiMember(Name = "ParentIndexNumber", Description = "Optional filter by parent index number.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? ParentIndexNumber { get; set; } - - [ApiMember(Name = "HasParentalRating", Description = "Optional filter by items that have or do not have a parental rating", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? HasParentalRating { get; set; } + /// Gets or sets who group this MediaBrowser.Api.Reports.BaseReportRequest. + /// Describes who group this MediaBrowser.Api.Reports.BaseReportRequest. + [ApiMember(Name = "GroupBy", Description = "Optional. If specified, results will include grouped records.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string GroupBy { get; set; } - [ApiMember(Name = "IsHD", Description = "Optional filter by items that are HD or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? IsHD { get; set; } + /// Gets or sets the report columns. + /// The report columns. + [ApiMember(Name = "ReportColumns", Description = "Optional. The columns to show.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string ReportColumns { get; set; } - [ApiMember(Name = "LocationTypes", Description = "Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string LocationTypes { get; set; } - - [ApiMember(Name = "ExcludeLocationTypes", Description = "Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string ExcludeLocationTypes { get; set; } - - [ApiMember(Name = "IsMissing", Description = "Optional filter by items that are missing episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? IsMissing { get; set; } - - [ApiMember(Name = "IsUnaired", Description = "Optional filter by items that are unaired episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? IsUnaired { get; set; } - - [ApiMember(Name = "IsVirtualUnaired", Description = "Optional filter by items that are virtual unaired episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? IsVirtualUnaired { get; set; } - - [ApiMember(Name = "MinCommunityRating", Description = "Optional filter by minimum community rating.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public double? MinCommunityRating { get; set; } - - [ApiMember(Name = "MinCriticRating", Description = "Optional filter by minimum critic rating.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public double? MinCriticRating { get; set; } - - [ApiMember(Name = "AiredDuringSeason", Description = "Gets all episodes that aired during a season, including specials.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? AiredDuringSeason { get; set; } - - [ApiMember(Name = "MinPremiereDate", Description = "Optional. The minimum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string MinPremiereDate { get; set; } - - [ApiMember(Name = "MaxPremiereDate", Description = "Optional. The maximum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string MaxPremiereDate { get; set; } - - [ApiMember(Name = "HasOverview", Description = "Optional filter by items that have an overview or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? HasOverview { get; set; } - - [ApiMember(Name = "HasImdbId", Description = "Optional filter by items that have an imdb id or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? HasImdbId { get; set; } - - [ApiMember(Name = "HasTmdbId", Description = "Optional filter by items that have a tmdb id or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? HasTmdbId { get; set; } - - [ApiMember(Name = "HasTvdbId", Description = "Optional filter by items that have a tvdb id or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? HasTvdbId { get; set; } - - [ApiMember(Name = "IsYearMismatched", Description = "Optional filter by items that are potentially misidentified.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? IsYearMismatched { get; set; } - - [ApiMember(Name = "IsInBoxSet", Description = "Optional filter by items that are in boxsets, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? IsInBoxSet { get; set; } - - [ApiMember(Name = "IsLocked", Description = "Optional filter by items that are locked.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public bool? IsLocked { get; set; } - - [ApiMember(Name = "IsUnidentified", Description = "Optional filter by items that are unidentified by internet metadata providers.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public bool? IsUnidentified { get; set; } - - [ApiMember(Name = "IsPlaceHolder", Description = "Optional filter by items that are placeholders", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public bool? IsPlaceHolder { get; set; } - - [ApiMember(Name = "HasOfficialRating", Description = "Optional filter by items that have official ratings", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public bool? HasOfficialRating { get; set; } - - [ApiMember(Name = "CollapseBoxSetItems", Description = "Whether or not to hide items behind their boxsets.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? CollapseBoxSetItems { get; set; } - - public string[] GetStudios() - { - return (Studios ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); - } - - public string[] GetStudioIds() - { - return (StudioIds ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); - } - - public string[] GetPersonTypes() - { - return (PersonTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - } - - public string[] GetPersonIds() - { - return (PersonIds ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - } - - public VideoType[] GetVideoTypes() - { - var val = VideoTypes; - - if (string.IsNullOrEmpty(val)) - { - return new VideoType[] { }; - } - - return val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(v => (VideoType)Enum.Parse(typeof(VideoType), v, true)).ToArray(); - } + } [Route("/Reports/Items", "GET", Summary = "Gets reports based on library items")] @@ -261,8 +100,27 @@ namespace MediaBrowser.Api.Reports } [Route("/Reports/Headers", "GET", Summary = "Gets reports headers based on library items")] - public class GetReportHeaders : BaseReportRequest, IReturn> - { + public class GetReportHeaders : IReturn>, IReportsHeader + { + /// Gets or sets the report view. + /// The report view. + [ApiMember(Name = "ReportView", Description = "The report view. Values (ReportData, ReportStatistics, ReportActivities)", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + public string ReportView { get; set; } + + /// Gets or sets the report view. + /// The report view. + [ApiMember(Name = "DisplayType", Description = "The report display type. Values (None, Screen, Export, ScreenExport)", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + public string DisplayType { get; set; } + + /// Gets or sets a list of types of the include items. + /// A list of types of the include items. + [ApiMember(Name = "IncludeItemTypes", Description = "Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] + public string IncludeItemTypes { get; set; } + + /// Gets or sets the report columns. + /// The report columns. + [ApiMember(Name = "ReportColumns", Description = "Optional. The columns to show.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string ReportColumns { get; set; } } [Route("/Reports/Statistics", "GET", Summary = "Gets reports statistics based on library items")] @@ -273,7 +131,7 @@ namespace MediaBrowser.Api.Reports } [Route("/Reports/Items/Download", "GET", Summary = "Downloads report")] - public class GetReportDownload : BaseReportRequest + public class GetReportDownload : BaseReportRequest, IReportsDownload { public GetReportDownload() { @@ -281,6 +139,66 @@ namespace MediaBrowser.Api.Reports } public ReportExportType ExportType { get; set; } + + /// Gets or sets the minimum date. + /// The minimum date. + [ApiMember(Name = "MinDate", Description = "Optional. The minimum date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public string MinDate { get; set; } + } + + [Route("/Reports/Activities", "GET", Summary = "Gets activities entries")] + public class GetActivityLogs : IReturn, IReportsQuery, IReportsDownload + { + /// Gets or sets the report view. + /// The report view. + [ApiMember(Name = "ReportView", Description = "The report view. Values (ReportData, ReportStatistics, ReportActivities)", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + public string ReportView { get; set; } + + /// Gets or sets the report view. + /// The report view. + [ApiMember(Name = "DisplayType", Description = "The report display type. Values (None, Screen, Export, ScreenExport)", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + public string DisplayType { get; set; } + + /// + /// Gets or sets a value indicating whether this MediaBrowser.Api.Reports.GetActivityLogs has + /// query limit. + /// + /// true if this MediaBrowser.Api.Reports.GetActivityLogs has query limit, false if not. + [ApiMember(Name = "HasQueryLimit", Description = "Optional. If specified, results will include all records.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool HasQueryLimit { get; set; } + + /// Gets or sets who group this MediaBrowser.Api.Reports.GetActivityLogs. + /// Describes who group this MediaBrowser.Api.Reports.GetActivityLogs. + [ApiMember(Name = "GroupBy", Description = "Optional. If specified, results will include grouped records.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string GroupBy { get; set; } + + /// Gets or sets the report columns. + /// The report columns. + [ApiMember(Name = "ReportColumns", Description = "Optional. The columns to show.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string ReportColumns { get; set; } + + /// + /// Skips over a given number of items within the results. Use for paging. + /// + /// The start index. + [ApiMember(Name = "StartIndex", Description = "Optional. The record index to start at. All items with a lower index will be dropped from the results.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? StartIndex { get; set; } + + /// + /// The maximum number of items to return + /// + /// The limit. + [ApiMember(Name = "Limit", Description = "Optional. The maximum number of records to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? Limit { get; set; } + + /// Gets or sets the minimum date. + /// The minimum date. + [ApiMember(Name = "MinDate", Description = "Optional. The minimum date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public string MinDate { get; set; } + + [ApiMember(Name = "IncludeItemTypes", Description = "Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] + public string IncludeItemTypes { get; set; } + } } diff --git a/MediaBrowser.Api/Reports/ReportsService.cs b/MediaBrowser.Api/Reports/ReportsService.cs index ebf5183c51..82e7264f15 100644 --- a/MediaBrowser.Api/Reports/ReportsService.cs +++ b/MediaBrowser.Api/Reports/ReportsService.cs @@ -24,1139 +24,666 @@ using System.Text; namespace MediaBrowser.Api.Reports { - /// The reports service. - /// - public class ReportsService : BaseApiService - { - - - /// Manager for user. - private readonly IUserManager _userManager; - - /// Manager for library. - private readonly ILibraryManager _libraryManager; - /// The localization. - private readonly ILocalizationManager _localization; - - /// - /// Initializes a new instance of the MediaBrowser.Api.Reports.ReportsService class. - /// Manager for user. - /// Manager for library. - /// The localization. - public ReportsService(IUserManager userManager, ILibraryManager libraryManager, ILocalizationManager localization) - { - _userManager = userManager; - _libraryManager = libraryManager; - _localization = localization; - } - - /// Gets the given request. - /// The request. - /// A Task<object> - public async Task Get(GetReportHeaders request) - { - if (string.IsNullOrEmpty(request.IncludeItemTypes)) - return null; - - ReportViewType reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes); - ReportBuilder reportBuilder = new ReportBuilder(_libraryManager); - var reportResult = reportBuilder.GetReportHeaders(reportRowType, request); - - return ToOptimizedResult(reportResult); - - } - - /// Gets the given request. - /// The request. - /// A Task<object> - public async Task Get(GetItemReport request) - { - if (string.IsNullOrEmpty(request.IncludeItemTypes)) - return null; - - var reportResult = await GetReportResult(request); - - return ToOptimizedResult(reportResult); - } - - /// Gets the given request. - /// The request. - /// A Task<object> - public async Task Get(GetReportDownload request) - { - if (string.IsNullOrEmpty(request.IncludeItemTypes)) - return null; - - var headers = new Dictionary(); - string fileExtension = "csv"; - string contentType = "text/plain;charset='utf-8'"; - - switch (request.ExportType) - { - case ReportExportType.CSV: - break; - case ReportExportType.Excel: - contentType = "application/vnd.ms-excel"; - fileExtension = "xls"; - break; - } - - var filename = "ReportExport." + fileExtension; - headers["Content-Disposition"] = string.Format("attachment; filename=\"{0}\"", filename); - headers["Content-Encoding"] = "UTF-8"; - - ReportViewType reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes); - ReportBuilder reportBuilder = new ReportBuilder(_libraryManager); - QueryResult queryResult = await GetQueryResult(request).ConfigureAwait(false); - ReportResult reportResult = reportBuilder.GetReportResult(queryResult.Items, reportRowType, request); - - reportResult.TotalRecordCount = queryResult.TotalRecordCount; - - string result = string.Empty; - switch (request.ExportType) - { - case ReportExportType.CSV: - result = new ReportExport().ExportToCsv(reportResult); - break; - case ReportExportType.Excel: - result = new ReportExport().ExportToExcel(reportResult); - break; - } - - object ro = ResultFactory.GetResult(result, contentType, headers); - return ro; - } - - /// Gets the given request. - /// The request. - /// A Task<object> - public async Task Get(GetReportStatistics request) - { - if (string.IsNullOrEmpty(request.IncludeItemTypes)) - return null; - var reportResult = await GetReportStatistic(request); - - return ToOptimizedResult(reportResult); - } - - /// Gets report statistic. - /// The request. - /// The report statistic. - private async Task GetReportStatistic(GetReportStatistics request) - { - ReportViewType reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes); - QueryResult queryResult = await GetQueryResult(request).ConfigureAwait(false); - - ReportStatBuilder reportBuilder = new ReportStatBuilder(_libraryManager); - ReportStatResult reportResult = reportBuilder.GetReportStatResult(queryResult.Items, ReportHelper.GetRowType(request.IncludeItemTypes), request.TopItems ?? 5); - reportResult.TotalRecordCount = reportResult.Groups.Count(); - return reportResult; - } - - /// Gets report result. - /// The request. - /// The report result. - private async Task GetReportResult(GetItemReport request) - { - - ReportViewType reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes); - ReportBuilder reportBuilder = new ReportBuilder(_libraryManager); - QueryResult queryResult = await GetQueryResult(request).ConfigureAwait(false); - ReportResult reportResult = reportBuilder.GetReportResult(queryResult.Items, reportRowType, request); - reportResult.TotalRecordCount = queryResult.TotalRecordCount; - - return reportResult; - } - - /// Gets query result. - /// The request. - /// The query result. - private async Task> GetQueryResult(BaseReportRequest request) - { - // Placeholder in case needed later - request.Recursive = true; - var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null; - request.Fields = "MediaSources,DateCreated,Settings,Studios,SyncInfo,ItemCounts"; - - var parentItem = string.IsNullOrEmpty(request.ParentId) ? - (user == null ? _libraryManager.RootFolder : user.RootFolder) : - _libraryManager.GetItemById(request.ParentId); - - var item = string.IsNullOrEmpty(request.ParentId) ? - user == null ? _libraryManager.RootFolder : user.RootFolder : - parentItem; - - IEnumerable items; - - if (request.Recursive) - { - var result = await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false); - return result; - } - else - { - if (user == null) - { - var result = await ((Folder)item).GetItems(GetItemsQuery(request, null)).ConfigureAwait(false); - return result; - } - - var userRoot = item as UserRootFolder; - - if (userRoot == null) - { - var result = await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false); - - return result; - } - - items = ((Folder)item).GetChildren(user, true); - } - - return new QueryResult { Items = items.ToArray() }; - - } - - /// Gets items query. - /// The request. - /// The user. - /// The items query. - private InternalItemsQuery GetItemsQuery(BaseReportRequest request, User user) - { - var query = new InternalItemsQuery - { - User = user, - IsPlayed = request.IsPlayed, - MediaTypes = request.GetMediaTypes(), - IncludeItemTypes = request.GetIncludeItemTypes(), - ExcludeItemTypes = request.GetExcludeItemTypes(), - Recursive = true, - SortBy = request.GetOrderBy(), - SortOrder = request.SortOrder ?? SortOrder.Ascending, - - Filter = i => ApplyAdditionalFilters(request, i, user, true, _libraryManager), - StartIndex = request.StartIndex, - IsMissing = request.IsMissing, - IsVirtualUnaired = request.IsVirtualUnaired, - IsUnaired = request.IsUnaired, - CollapseBoxSetItems = request.CollapseBoxSetItems, - NameLessThan = request.NameLessThan, - NameStartsWith = request.NameStartsWith, - NameStartsWithOrGreater = request.NameStartsWithOrGreater, - HasImdbId = request.HasImdbId, - IsYearMismatched = request.IsYearMismatched, - IsUnidentified = request.IsUnidentified, - IsPlaceHolder = request.IsPlaceHolder, - IsLocked = request.IsLocked, - IsInBoxSet = request.IsInBoxSet, - IsHD = request.IsHD, - Is3D = request.Is3D, - HasTvdbId = request.HasTvdbId, - HasTmdbId = request.HasTmdbId, - HasOverview = request.HasOverview, - HasOfficialRating = request.HasOfficialRating, - HasParentalRating = request.HasParentalRating, - HasSpecialFeature = request.HasSpecialFeature, - HasSubtitles = request.HasSubtitles, - HasThemeSong = request.HasThemeSong, - HasThemeVideo = request.HasThemeVideo, - HasTrailer = request.HasTrailer, - Tags = request.GetTags(), - OfficialRatings = request.GetOfficialRatings(), - Genres = request.GetGenres(), - Studios = request.GetStudios(), - StudioIds = request.GetStudioIds(), - Person = request.Person, - PersonIds = request.GetPersonIds(), - PersonTypes = request.GetPersonTypes(), - Years = request.GetYears(), - ImageTypes = request.GetImageTypes().ToArray(), - VideoTypes = request.GetVideoTypes().ToArray(), - AdjacentTo = request.AdjacentTo - }; - - if (!string.IsNullOrWhiteSpace(request.Ids)) - { - query.CollapseBoxSetItems = false; - } - - foreach (var filter in request.GetFilters()) - { - switch (filter) - { - case ItemFilter.Dislikes: - query.IsLiked = false; - break; - case ItemFilter.IsFavorite: - query.IsFavorite = true; - break; - case ItemFilter.IsFavoriteOrLikes: - query.IsFavoriteOrLiked = true; - break; - case ItemFilter.IsFolder: - query.IsFolder = true; - break; - case ItemFilter.IsNotFolder: - query.IsFolder = false; - break; - case ItemFilter.IsPlayed: - query.IsPlayed = true; - break; - case ItemFilter.IsRecentlyAdded: - break; - case ItemFilter.IsResumable: - query.IsResumable = true; - break; - case ItemFilter.IsUnplayed: - query.IsPlayed = false; - break; - case ItemFilter.Likes: - query.IsLiked = true; - break; - } - } - - if (request.HasQueryLimit) - query.Limit = request.Limit; - return query; - } - - /// Applies filtering. - /// The items. - /// The filter. - /// The user. - /// The repository. - /// IEnumerable{BaseItem}. - internal static IEnumerable ApplyFilter(IEnumerable items, ItemFilter filter, User user, IUserDataManager repository) - { - // Avoid implicitly captured closure - var currentUser = user; - - switch (filter) - { - case ItemFilter.IsFavoriteOrLikes: - return items.Where(item => - { - var userdata = repository.GetUserData(user.Id, item.GetUserDataKey()); - - if (userdata == null) - { - return false; - } - - var likes = userdata.Likes ?? false; - var favorite = userdata.IsFavorite; - - return likes || favorite; - }); - - case ItemFilter.Likes: - return items.Where(item => - { - var userdata = repository.GetUserData(user.Id, item.GetUserDataKey()); - - return userdata != null && userdata.Likes.HasValue && userdata.Likes.Value; - }); - - case ItemFilter.Dislikes: - return items.Where(item => - { - var userdata = repository.GetUserData(user.Id, item.GetUserDataKey()); - - return userdata != null && userdata.Likes.HasValue && !userdata.Likes.Value; - }); - - case ItemFilter.IsFavorite: - return items.Where(item => - { - var userdata = repository.GetUserData(user.Id, item.GetUserDataKey()); - - return userdata != null && userdata.IsFavorite; - }); - - case ItemFilter.IsResumable: - return items.Where(item => - { - var userdata = repository.GetUserData(user.Id, item.GetUserDataKey()); - - return userdata != null && userdata.PlaybackPositionTicks > 0; - }); - - case ItemFilter.IsPlayed: - return items.Where(item => item.IsPlayed(currentUser)); - - case ItemFilter.IsUnplayed: - return items.Where(item => item.IsUnplayed(currentUser)); - - case ItemFilter.IsFolder: - return items.Where(item => item.IsFolder); - - case ItemFilter.IsNotFolder: - return items.Where(item => !item.IsFolder); - - case ItemFilter.IsRecentlyAdded: - return items.Where(item => (DateTime.UtcNow - item.DateCreated).TotalDays <= 10); - } - - return items; - } - - /// Applies the additional filters. - /// The request. - /// Zero-based index of the. - /// The user. - /// true if this object is pre filtered. - /// Manager for library. - /// true if it succeeds, false if it fails. - private bool ApplyAdditionalFilters(BaseReportRequest request, BaseItem i, User user, bool isPreFiltered, ILibraryManager libraryManager) - { - var video = i as Video; - - if (!isPreFiltered) - { - var mediaTypes = request.GetMediaTypes(); - if (mediaTypes.Length > 0) - { - if (!(!string.IsNullOrEmpty(i.MediaType) && mediaTypes.Contains(i.MediaType, StringComparer.OrdinalIgnoreCase))) - { - return false; - } - } - - if (request.IsPlayed.HasValue) - { - var val = request.IsPlayed.Value; - if (i.IsPlayed(user) != val) - { - return false; - } - } - - // Exclude item types - var excluteItemTypes = request.GetExcludeItemTypes(); - if (excluteItemTypes.Length > 0 && excluteItemTypes.Contains(i.GetType().Name, StringComparer.OrdinalIgnoreCase)) - { - return false; - } - - // Include item types - var includeItemTypes = request.GetIncludeItemTypes(); - if (includeItemTypes.Length > 0 && !includeItemTypes.Contains(i.GetType().Name, StringComparer.OrdinalIgnoreCase)) - { - return false; - } - - if (request.IsInBoxSet.HasValue) - { - var val = request.IsInBoxSet.Value; - if (i.Parents.OfType().Any() != val) - { - return false; - } - } - - // Filter by Video3DFormat - if (request.Is3D.HasValue) - { - var val = request.Is3D.Value; - - if (video == null || val != video.Video3DFormat.HasValue) - { - return false; - } - } - - if (request.IsHD.HasValue) - { - var val = request.IsHD.Value; - - if (video == null || val != video.IsHD) - { - return false; - } - } - - if (request.IsUnidentified.HasValue) - { - var val = request.IsUnidentified.Value; - if (i.IsUnidentified != val) - { - return false; - } - } - - if (request.IsLocked.HasValue) - { - var val = request.IsLocked.Value; - if (i.IsLocked != val) - { - return false; - } - } - - if (request.HasOverview.HasValue) - { - var filterValue = request.HasOverview.Value; - - var hasValue = !string.IsNullOrEmpty(i.Overview); - - if (hasValue != filterValue) - { - return false; - } - } - - if (request.HasImdbId.HasValue) - { - var filterValue = request.HasImdbId.Value; - - var hasValue = !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Imdb)); - - if (hasValue != filterValue) - { - return false; - } - } - - if (request.HasTmdbId.HasValue) - { - var filterValue = request.HasTmdbId.Value; - - var hasValue = !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Tmdb)); - - if (hasValue != filterValue) - { - return false; - } - } - - if (request.HasTvdbId.HasValue) - { - var filterValue = request.HasTvdbId.Value; - - var hasValue = !string.IsNullOrEmpty(i.GetProviderId(MetadataProviders.Tvdb)); - - if (hasValue != filterValue) - { - return false; - } - } - - if (request.IsYearMismatched.HasValue) - { - var filterValue = request.IsYearMismatched.Value; - - if (UserViewBuilder.IsYearMismatched(i, libraryManager) != filterValue) - { - return false; - } - } - - if (request.HasOfficialRating.HasValue) - { - var filterValue = request.HasOfficialRating.Value; - - var hasValue = !string.IsNullOrEmpty(i.OfficialRating); - - if (hasValue != filterValue) - { - return false; - } - } - - if (request.IsPlaceHolder.HasValue) - { - var filterValue = request.IsPlaceHolder.Value; - - var isPlaceHolder = false; - - var hasPlaceHolder = i as ISupportsPlaceHolders; - - if (hasPlaceHolder != null) - { - isPlaceHolder = hasPlaceHolder.IsPlaceHolder; - } - - if (isPlaceHolder != filterValue) - { - return false; - } - } - - if (request.HasSpecialFeature.HasValue) - { - var filterValue = request.HasSpecialFeature.Value; - - var movie = i as IHasSpecialFeatures; - - if (movie != null) - { - var ok = filterValue - ? movie.SpecialFeatureIds.Count > 0 - : movie.SpecialFeatureIds.Count == 0; - - if (!ok) - { - return false; - } - } - else - { - return false; - } - } - - if (request.HasSubtitles.HasValue) - { - var val = request.HasSubtitles.Value; - - if (video == null || val != video.HasSubtitles) - { - return false; - } - } - - if (request.HasParentalRating.HasValue) - { - var val = request.HasParentalRating.Value; - - var rating = i.CustomRating; - - if (string.IsNullOrEmpty(rating)) - { - rating = i.OfficialRating; - } - - if (val) - { - if (string.IsNullOrEmpty(rating)) - { - return false; - } - } - else - { - if (!string.IsNullOrEmpty(rating)) - { - return false; - } - } - } - - if (request.HasTrailer.HasValue) - { - var val = request.HasTrailer.Value; - var trailerCount = 0; - - var hasTrailers = i as IHasTrailers; - if (hasTrailers != null) - { - trailerCount = hasTrailers.GetTrailerIds().Count; - } - - var ok = val ? trailerCount > 0 : trailerCount == 0; - - if (!ok) - { - return false; - } - } - - if (request.HasThemeSong.HasValue) - { - var filterValue = request.HasThemeSong.Value; - - var themeCount = 0; - var iHasThemeMedia = i as IHasThemeMedia; - - if (iHasThemeMedia != null) - { - themeCount = iHasThemeMedia.ThemeSongIds.Count; - } - var ok = filterValue ? themeCount > 0 : themeCount == 0; - - if (!ok) - { - return false; - } - } - - if (request.HasThemeVideo.HasValue) - { - var filterValue = request.HasThemeVideo.Value; - - var themeCount = 0; - var iHasThemeMedia = i as IHasThemeMedia; - - if (iHasThemeMedia != null) - { - themeCount = iHasThemeMedia.ThemeVideoIds.Count; - } - var ok = filterValue ? themeCount > 0 : themeCount == 0; - - if (!ok) - { - return false; - } - } - - // Apply tag filter - var tags = request.GetTags(); - if (tags.Length > 0) - { - var hasTags = i as IHasTags; - if (hasTags == null) - { - return false; - } - if (!(tags.Any(v => hasTags.Tags.Contains(v, StringComparer.OrdinalIgnoreCase)))) - { - return false; - } - } - - // Apply official rating filter - var officialRatings = request.GetOfficialRatings(); - if (officialRatings.Length > 0 && !officialRatings.Contains(i.OfficialRating ?? string.Empty)) - { - return false; - } - - // Apply genre filter - var genres = request.GetGenres(); - if (genres.Length > 0 && !(genres.Any(v => i.Genres.Contains(v, StringComparer.OrdinalIgnoreCase)))) - { - return false; - } - - // Filter by VideoType - var videoTypes = request.GetVideoTypes(); - if (videoTypes.Length > 0 && (video == null || !videoTypes.Contains(video.VideoType))) - { - return false; - } - - var imageTypes = request.GetImageTypes().ToList(); - if (imageTypes.Count > 0) - { - if (!(imageTypes.Any(i.HasImage))) - { - return false; - } - } - - // Apply studio filter - var studios = request.GetStudios(); - if (studios.Length > 0 && !studios.Any(v => i.Studios.Contains(v, StringComparer.OrdinalIgnoreCase))) - { - return false; - } - - // Apply studio filter - var studioIds = request.GetStudioIds(); - if (studioIds.Length > 0 && !studioIds.Any(id => - { - var studioItem = libraryManager.GetItemById(id); - return studioItem != null && i.Studios.Contains(studioItem.Name, StringComparer.OrdinalIgnoreCase); - })) - { - return false; - } - - // Apply year filter - var years = request.GetYears(); - if (years.Length > 0 && !(i.ProductionYear.HasValue && years.Contains(i.ProductionYear.Value))) - { - return false; - } - - // Apply person filter - var personIds = request.GetPersonIds(); - if (personIds.Length > 0) - { - var names = personIds - .Select(libraryManager.GetItemById) - .Select(p => p == null ? "-1" : p.Name) - .ToList(); - - if (!(names.Any(v => _libraryManager.GetPeople(i).Select(p => p.Name).Contains(v, StringComparer.OrdinalIgnoreCase)))) - { - return false; - } - } - - // Apply person filter - if (!string.IsNullOrEmpty(request.Person)) - { - var personTypes = request.GetPersonTypes(); - - if (personTypes.Length == 0) - { - if (!(_libraryManager.GetPeople(i).Any(p => string.Equals(p.Name, request.Person, StringComparison.OrdinalIgnoreCase)))) - { - return false; - } - } - else - { - var types = personTypes; - - var ok = new[] { i }.Any(item => - _libraryManager.GetPeople(i).Any(p => - p.Name.Equals(request.Person, StringComparison.OrdinalIgnoreCase) && (types.Contains(p.Type, StringComparer.OrdinalIgnoreCase) || types.Contains(p.Role, StringComparer.OrdinalIgnoreCase)))); - - if (!ok) - { - return false; - } - } - } - } - - if (request.MinCommunityRating.HasValue) - { - var val = request.MinCommunityRating.Value; - - if (!(i.CommunityRating.HasValue && i.CommunityRating >= val)) - { - return false; - } - } - - if (request.MinCriticRating.HasValue) - { - var val = request.MinCriticRating.Value; - - var hasCriticRating = i as IHasCriticRating; - - if (hasCriticRating != null) - { - if (!(hasCriticRating.CriticRating.HasValue && hasCriticRating.CriticRating >= val)) - { - return false; - } - } - else - { - return false; - } - } - - // Artists - if (!string.IsNullOrEmpty(request.ArtistIds)) - { - var artistIds = request.ArtistIds.Split('|'); - - var audio = i as IHasArtist; - - if (!(audio != null && artistIds.Any(id => - { - var artistItem = libraryManager.GetItemById(id); - return artistItem != null && audio.HasAnyArtist(artistItem.Name); - }))) - { - return false; - } - } - - // Artists - if (!string.IsNullOrEmpty(request.Artists)) - { - var artists = request.Artists.Split('|'); - - var audio = i as IHasArtist; - - if (!(audio != null && artists.Any(audio.HasAnyArtist))) - { - return false; - } - } - - // Albums - if (!string.IsNullOrEmpty(request.Albums)) - { - var albums = request.Albums.Split('|'); - - var audio = i as Audio; - - if (audio != null) - { - if (!albums.Any(a => string.Equals(a, audio.Album, StringComparison.OrdinalIgnoreCase))) - { - return false; - } - } - - var album = i as MusicAlbum; - - if (album != null) - { - if (!albums.Any(a => string.Equals(a, album.Name, StringComparison.OrdinalIgnoreCase))) - { - return false; - } - } - - var musicVideo = i as MusicVideo; - - if (musicVideo != null) - { - if (!albums.Any(a => string.Equals(a, musicVideo.Album, StringComparison.OrdinalIgnoreCase))) - { - return false; - } - } - - return false; - } - - // Min index number - if (request.MinIndexNumber.HasValue) - { - if (!(i.IndexNumber.HasValue && i.IndexNumber.Value >= request.MinIndexNumber.Value)) - { - return false; - } - } - - // Min official rating - if (!string.IsNullOrEmpty(request.MinOfficialRating)) - { - var level = _localization.GetRatingLevel(request.MinOfficialRating); - - if (level.HasValue) - { - var rating = i.CustomRating; - - if (string.IsNullOrEmpty(rating)) - { - rating = i.OfficialRating; - } - - if (!string.IsNullOrEmpty(rating)) - { - var itemLevel = _localization.GetRatingLevel(rating); - - if (!(!itemLevel.HasValue || itemLevel.Value >= level.Value)) - { - return false; - } - } - } - } - - // Max official rating - if (!string.IsNullOrEmpty(request.MaxOfficialRating)) - { - var level = _localization.GetRatingLevel(request.MaxOfficialRating); - - if (level.HasValue) - { - var rating = i.CustomRating; - - if (string.IsNullOrEmpty(rating)) - { - rating = i.OfficialRating; - } - - if (!string.IsNullOrEmpty(rating)) - { - var itemLevel = _localization.GetRatingLevel(rating); - - if (!(!itemLevel.HasValue || itemLevel.Value <= level.Value)) - { - return false; - } - } - } - } - - // LocationTypes - if (!string.IsNullOrEmpty(request.LocationTypes)) - { - var vals = request.LocationTypes.Split(','); - if (!vals.Contains(i.LocationType.ToString(), StringComparer.OrdinalIgnoreCase)) - { - return false; - } - } - - // ExcludeLocationTypes - if (!string.IsNullOrEmpty(request.ExcludeLocationTypes)) - { - var vals = request.ExcludeLocationTypes.Split(','); - if (vals.Contains(i.LocationType.ToString(), StringComparer.OrdinalIgnoreCase)) - { - return false; - } - } - - if (!string.IsNullOrEmpty(request.AlbumArtistStartsWithOrGreater)) - { - var ok = new[] { i }.OfType() - .Any(p => string.Compare(request.AlbumArtistStartsWithOrGreater, p.AlbumArtists.FirstOrDefault(), StringComparison.CurrentCultureIgnoreCase) < 1); - - if (!ok) - { - return false; - } - } - - // Filter by Series Status - if (!string.IsNullOrEmpty(request.SeriesStatus)) - { - var vals = request.SeriesStatus.Split(','); - - var ok = new[] { i }.OfType().Any(p => p.Status.HasValue && vals.Contains(p.Status.Value.ToString(), StringComparer.OrdinalIgnoreCase)); - - if (!ok) - { - return false; - } - } - - // Filter by Series AirDays - if (!string.IsNullOrEmpty(request.AirDays)) - { - var days = request.AirDays.Split(',').Select(d => (DayOfWeek)Enum.Parse(typeof(DayOfWeek), d, true)); - - var ok = new[] { i }.OfType().Any(p => p.AirDays != null && days.Any(d => p.AirDays.Contains(d))); - - if (!ok) - { - return false; - } - } - - if (request.MinPlayers.HasValue) - { - var filterValue = request.MinPlayers.Value; - - var game = i as Game; - - if (game != null) - { - var players = game.PlayersSupported ?? 1; - - var ok = players >= filterValue; - - if (!ok) - { - return false; - } - } - else - { - return false; - } - } - - if (request.MaxPlayers.HasValue) - { - var filterValue = request.MaxPlayers.Value; - - var game = i as Game; - - if (game != null) - { - var players = game.PlayersSupported ?? 1; - - var ok = players <= filterValue; - - if (!ok) - { - return false; - } - } - else - { - return false; - } - } - - if (request.ParentIndexNumber.HasValue) - { - var filterValue = request.ParentIndexNumber.Value; - - var episode = i as Episode; - - if (episode != null) - { - if (episode.ParentIndexNumber.HasValue && episode.ParentIndexNumber.Value != filterValue) - { - return false; - } - } - - var song = i as Audio; - - if (song != null) - { - if (song.ParentIndexNumber.HasValue && song.ParentIndexNumber.Value != filterValue) - { - return false; - } - } - } - - if (request.AiredDuringSeason.HasValue) - { - var episode = i as Episode; - - if (episode == null) - { - return false; - } - - if (!Series.FilterEpisodesBySeason(new[] { episode }, request.AiredDuringSeason.Value, true).Any()) - { - return false; - } - } - - if (!string.IsNullOrEmpty(request.MinPremiereDate)) - { - var date = DateTime.Parse(request.MinPremiereDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime(); - - if (!(i.PremiereDate.HasValue && i.PremiereDate.Value >= date)) - { - return false; - } - } - - if (!string.IsNullOrEmpty(request.MaxPremiereDate)) - { - var date = DateTime.Parse(request.MaxPremiereDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime(); - - if (!(i.PremiereDate.HasValue && i.PremiereDate.Value <= date)) - { - return false; - } - } - - return true; - } - - /// Applies the paging. - /// The request. - /// The items. - /// IEnumerable{BaseItem}. - private IEnumerable ApplyPaging(GetItems request, IEnumerable items) - { - // Start at - if (request.StartIndex.HasValue) - { - items = items.Skip(request.StartIndex.Value); - } - - // Return limit - if (request.Limit.HasValue) - { - items = items.Take(request.Limit.Value); - } - - return items; - } - - } + /// The reports service. + /// + public class ReportsService : BaseApiService + { + #region [Constructors] + + /// + /// Initializes a new instance of the MediaBrowser.Api.Reports.ReportsService class. + /// Manager for user. + /// Manager for library. + /// The localization. + /// Manager for activity. + public ReportsService(IUserManager userManager, ILibraryManager libraryManager, ILocalizationManager localization, IActivityManager activityManager, IActivityRepository repo) + { + _userManager = userManager; + _libraryManager = libraryManager; + _localization = localization; + _activityManager = activityManager; + _repo = repo; + } + + #endregion + + #region [Private Fields] + + private readonly IActivityManager _activityManager; ///< Manager for activity + + /// Manager for library. + private readonly ILibraryManager _libraryManager; ///< Manager for library + /// The localization. + + private readonly ILocalizationManager _localization; ///< The localization + + private readonly IActivityRepository _repo; + + /// Manager for user. + private readonly IUserManager _userManager; ///< Manager for user + + #endregion + + #region [Public Methods] + + /// Gets the given request. + /// The request. + /// A Task<object> + public async Task Get(GetActivityLogs request) + { + request.DisplayType = "Screen"; + ReportResult result = await GetReportActivities(request).ConfigureAwait(false); + return ToOptimizedResult(result); + } + + /// Gets the given request. + /// The request. + /// A Task<object> + public async Task Get(GetReportHeaders request) + { + if (string.IsNullOrEmpty(request.IncludeItemTypes)) + return null; + + request.DisplayType = "Screen"; + ReportViewType reportViewType = ReportHelper.GetReportViewType(request.ReportView); + + List result = new List(); + switch (reportViewType) + { + case ReportViewType.ReportData: + ReportBuilder dataBuilder = new ReportBuilder(_libraryManager); + result = dataBuilder.GetHeaders(request); + break; + case ReportViewType.ReportStatistics: + break; + case ReportViewType.ReportActivities: + ReportActivitiesBuilder activityBuilder = new ReportActivitiesBuilder(_libraryManager, _userManager); + result = activityBuilder.GetHeaders(request); + break; + } + + return ToOptimizedResult(result); + + } + + /// Gets the given request. + /// The request. + /// A Task<object> + public async Task Get(GetItemReport request) + { + if (string.IsNullOrEmpty(request.IncludeItemTypes)) + return null; + + request.DisplayType = "Screen"; + var reportResult = await GetReportResult(request); + + return ToOptimizedResult(reportResult); + } + + /// Gets the given request. + /// The request. + /// A Task<object> + public async Task Get(GetReportStatistics request) + { + if (string.IsNullOrEmpty(request.IncludeItemTypes)) + return null; + request.DisplayType = "Screen"; + var reportResult = await GetReportStatistic(request); + + return ToOptimizedResult(reportResult); + } + + /// Gets the given request. + /// The request. + /// A Task<object> + public async Task Get(GetReportDownload request) + { + if (string.IsNullOrEmpty(request.IncludeItemTypes)) + return null; + + request.DisplayType = "Export"; + ReportViewType reportViewType = ReportHelper.GetReportViewType(request.ReportView); + var headers = new Dictionary(); + string fileExtension = "csv"; + string contentType = "text/plain;charset='utf-8'"; + + switch (request.ExportType) + { + case ReportExportType.CSV: + break; + case ReportExportType.Excel: + contentType = "application/vnd.ms-excel"; + fileExtension = "xls"; + break; + } + + var filename = "ReportExport." + fileExtension; + headers["Content-Disposition"] = string.Format("attachment; filename=\"{0}\"", filename); + headers["Content-Encoding"] = "UTF-8"; + + ReportResult result = null; + switch (reportViewType) + { + case ReportViewType.ReportStatistics: + case ReportViewType.ReportData: + ReportIncludeItemTypes reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes); + ReportBuilder dataBuilder = new ReportBuilder(_libraryManager); + QueryResult queryResult = await GetQueryResult(request).ConfigureAwait(false); + result = dataBuilder.GetResult(queryResult.Items, request); + result.TotalRecordCount = queryResult.TotalRecordCount; + break; + case ReportViewType.ReportActivities: + result = await GetReportActivities(request).ConfigureAwait(false); + break; + } + + string returnResult = string.Empty; + switch (request.ExportType) + { + case ReportExportType.CSV: + returnResult = new ReportExport().ExportToCsv(result); + break; + case ReportExportType.Excel: + returnResult = new ReportExport().ExportToExcel(result); + break; + } + + object ro = ResultFactory.GetResult(returnResult, contentType, headers); + return ro; + } + + #endregion + + #region [Private Methods] + + /// Gets items query. + /// The request. + /// The user. + /// The items query. + private InternalItemsQuery GetItemsQuery(BaseReportRequest request, User user) + { + var query = new InternalItemsQuery + { + User = user, + IsPlayed = request.IsPlayed, + MediaTypes = request.GetMediaTypes(), + IncludeItemTypes = request.GetIncludeItemTypes(), + ExcludeItemTypes = request.GetExcludeItemTypes(), + Recursive = request.Recursive, + SortBy = request.GetOrderBy(), + SortOrder = request.SortOrder ?? SortOrder.Ascending, + + Filter = i => ApplyAdditionalFilters(request, i, user, _libraryManager), + + Limit = request.Limit, + StartIndex = request.StartIndex, + IsMissing = request.IsMissing, + IsVirtualUnaired = request.IsVirtualUnaired, + IsUnaired = request.IsUnaired, + CollapseBoxSetItems = request.CollapseBoxSetItems, + NameLessThan = request.NameLessThan, + NameStartsWith = request.NameStartsWith, + NameStartsWithOrGreater = request.NameStartsWithOrGreater, + HasImdbId = request.HasImdbId, + IsYearMismatched = request.IsYearMismatched, + IsUnidentified = request.IsUnidentified, + IsPlaceHolder = request.IsPlaceHolder, + IsLocked = request.IsLocked, + IsInBoxSet = request.IsInBoxSet, + IsHD = request.IsHD, + Is3D = request.Is3D, + HasTvdbId = request.HasTvdbId, + HasTmdbId = request.HasTmdbId, + HasOverview = request.HasOverview, + HasOfficialRating = request.HasOfficialRating, + HasParentalRating = request.HasParentalRating, + HasSpecialFeature = request.HasSpecialFeature, + HasSubtitles = request.HasSubtitles, + HasThemeSong = request.HasThemeSong, + HasThemeVideo = request.HasThemeVideo, + HasTrailer = request.HasTrailer, + Tags = request.GetTags(), + OfficialRatings = request.GetOfficialRatings(), + Genres = request.GetGenres(), + Studios = request.GetStudios(), + StudioIds = request.GetStudioIds(), + Person = request.Person, + PersonIds = request.GetPersonIds(), + PersonTypes = request.GetPersonTypes(), + Years = request.GetYears(), + ImageTypes = request.GetImageTypes().ToArray(), + VideoTypes = request.GetVideoTypes().ToArray(), + AdjacentTo = request.AdjacentTo, + ItemIds = request.GetItemIds(), + MinPlayers = request.MinPlayers, + MaxPlayers = request.MaxPlayers, + MinCommunityRating = request.MinCommunityRating, + MinCriticRating = request.MinCriticRating + }; + + if (!string.IsNullOrWhiteSpace(request.Ids)) + { + query.CollapseBoxSetItems = false; + } + + foreach (var filter in request.GetFilters()) + { + switch (filter) + { + case ItemFilter.Dislikes: + query.IsLiked = false; + break; + case ItemFilter.IsFavorite: + query.IsFavorite = true; + break; + case ItemFilter.IsFavoriteOrLikes: + query.IsFavoriteOrLiked = true; + break; + case ItemFilter.IsFolder: + query.IsFolder = true; + break; + case ItemFilter.IsNotFolder: + query.IsFolder = false; + break; + case ItemFilter.IsPlayed: + query.IsPlayed = true; + break; + case ItemFilter.IsRecentlyAdded: + break; + case ItemFilter.IsResumable: + query.IsResumable = true; + break; + case ItemFilter.IsUnplayed: + query.IsPlayed = false; + break; + case ItemFilter.Likes: + query.IsLiked = true; + break; + } + } + + if (request.HasQueryLimit) + query.Limit = request.Limit; + + return query; + } + + private bool ApplyAdditionalFilters(BaseReportRequest request, BaseItem i, User user, ILibraryManager libraryManager) + { + // Artists + if (!string.IsNullOrEmpty(request.ArtistIds)) + { + var artistIds = request.ArtistIds.Split(new[] { '|', ',' }); + + var audio = i as IHasArtist; + + if (!(audio != null && artistIds.Any(id => + { + var artistItem = libraryManager.GetItemById(id); + return artistItem != null && audio.HasAnyArtist(artistItem.Name); + }))) + { + return false; + } + } + + // Artists + if (!string.IsNullOrEmpty(request.Artists)) + { + var artists = request.Artists.Split('|'); + + var audio = i as IHasArtist; + + if (!(audio != null && artists.Any(audio.HasAnyArtist))) + { + return false; + } + } + + // Albums + if (!string.IsNullOrEmpty(request.Albums)) + { + var albums = request.Albums.Split('|'); + + var audio = i as Audio; + + if (audio != null) + { + if (!albums.Any(a => string.Equals(a, audio.Album, StringComparison.OrdinalIgnoreCase))) + { + return false; + } + } + + var album = i as MusicAlbum; + + if (album != null) + { + if (!albums.Any(a => string.Equals(a, album.Name, StringComparison.OrdinalIgnoreCase))) + { + return false; + } + } + + var musicVideo = i as MusicVideo; + + if (musicVideo != null) + { + if (!albums.Any(a => string.Equals(a, musicVideo.Album, StringComparison.OrdinalIgnoreCase))) + { + return false; + } + } + + return false; + } + + // Min index number + if (request.MinIndexNumber.HasValue) + { + if (!(i.IndexNumber.HasValue && i.IndexNumber.Value >= request.MinIndexNumber.Value)) + { + return false; + } + } + + // Min official rating + if (!string.IsNullOrEmpty(request.MinOfficialRating)) + { + var level = _localization.GetRatingLevel(request.MinOfficialRating); + + if (level.HasValue) + { + var rating = i.CustomRating; + + if (string.IsNullOrEmpty(rating)) + { + rating = i.OfficialRating; + } + + if (!string.IsNullOrEmpty(rating)) + { + var itemLevel = _localization.GetRatingLevel(rating); + + if (!(!itemLevel.HasValue || itemLevel.Value >= level.Value)) + { + return false; + } + } + } + } + + // Max official rating + if (!string.IsNullOrEmpty(request.MaxOfficialRating)) + { + var level = _localization.GetRatingLevel(request.MaxOfficialRating); + + if (level.HasValue) + { + var rating = i.CustomRating; + + if (string.IsNullOrEmpty(rating)) + { + rating = i.OfficialRating; + } + + if (!string.IsNullOrEmpty(rating)) + { + var itemLevel = _localization.GetRatingLevel(rating); + + if (!(!itemLevel.HasValue || itemLevel.Value <= level.Value)) + { + return false; + } + } + } + } + + // LocationTypes + if (!string.IsNullOrEmpty(request.LocationTypes)) + { + var vals = request.LocationTypes.Split(','); + if (!vals.Contains(i.LocationType.ToString(), StringComparer.OrdinalIgnoreCase)) + { + return false; + } + } + + // ExcludeLocationTypes + if (!string.IsNullOrEmpty(request.ExcludeLocationTypes)) + { + var vals = request.ExcludeLocationTypes.Split(','); + if (vals.Contains(i.LocationType.ToString(), StringComparer.OrdinalIgnoreCase)) + { + return false; + } + } + + if (!string.IsNullOrEmpty(request.AlbumArtistStartsWithOrGreater)) + { + var ok = new[] { i }.OfType() + .Any(p => string.Compare(request.AlbumArtistStartsWithOrGreater, p.AlbumArtists.FirstOrDefault(), StringComparison.CurrentCultureIgnoreCase) < 1); + + if (!ok) + { + return false; + } + } + + // Filter by Series Status + if (!string.IsNullOrEmpty(request.SeriesStatus)) + { + var vals = request.SeriesStatus.Split(','); + + var ok = new[] { i }.OfType().Any(p => p.Status.HasValue && vals.Contains(p.Status.Value.ToString(), StringComparer.OrdinalIgnoreCase)); + + if (!ok) + { + return false; + } + } + + // Filter by Series AirDays + if (!string.IsNullOrEmpty(request.AirDays)) + { + var days = request.AirDays.Split(',').Select(d => (DayOfWeek)Enum.Parse(typeof(DayOfWeek), d, true)); + + var ok = new[] { i }.OfType().Any(p => p.AirDays != null && days.Any(d => p.AirDays.Contains(d))); + + if (!ok) + { + return false; + } + } + + if (request.ParentIndexNumber.HasValue) + { + var filterValue = request.ParentIndexNumber.Value; + + var episode = i as Episode; + + if (episode != null) + { + if (episode.ParentIndexNumber.HasValue && episode.ParentIndexNumber.Value != filterValue) + { + return false; + } + } + + var song = i as Audio; + + if (song != null) + { + if (song.ParentIndexNumber.HasValue && song.ParentIndexNumber.Value != filterValue) + { + return false; + } + } + } + + if (request.AiredDuringSeason.HasValue) + { + var episode = i as Episode; + + if (episode == null) + { + return false; + } + + if (!Series.FilterEpisodesBySeason(new[] { episode }, request.AiredDuringSeason.Value, true).Any()) + { + return false; + } + } + + if (!string.IsNullOrEmpty(request.MinPremiereDate)) + { + var date = DateTime.Parse(request.MinPremiereDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime(); + + if (!(i.PremiereDate.HasValue && i.PremiereDate.Value >= date)) + { + return false; + } + } + + if (!string.IsNullOrEmpty(request.MaxPremiereDate)) + { + var date = DateTime.Parse(request.MaxPremiereDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime(); + + if (!(i.PremiereDate.HasValue && i.PremiereDate.Value <= date)) + { + return false; + } + } + + return true; + } + + /// Applies the paging. + /// The request. + /// The items. + /// IEnumerable{BaseItem}. + private IEnumerable ApplyPaging(BaseReportRequest request, IEnumerable items) + { + // Start at + if (request.StartIndex.HasValue) + { + items = items.Skip(request.StartIndex.Value); + } + + // Return limit + if (request.Limit.HasValue) + { + items = items.Take(request.Limit.Value); + } + + return items; + } + + /// Gets query result. + /// The request. + /// The query result. + private async Task> GetQueryResult(BaseReportRequest request) + { + // Placeholder in case needed later + request.Recursive = true; + var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null; + request.Fields = "MediaSources,DateCreated,Settings,Studios,SyncInfo,ItemCounts"; + + var parentItem = string.IsNullOrEmpty(request.ParentId) ? + (user == null ? _libraryManager.RootFolder : user.RootFolder) : + _libraryManager.GetItemById(request.ParentId); + + var item = string.IsNullOrEmpty(request.ParentId) ? + user == null ? _libraryManager.RootFolder : user.RootFolder : + parentItem; + + IEnumerable items; + + if (request.Recursive) + { + var result = await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false); + return result; + } + else + { + if (user == null) + { + var result = await ((Folder)item).GetItems(GetItemsQuery(request, null)).ConfigureAwait(false); + return result; + } + + var userRoot = item as UserRootFolder; + + if (userRoot == null) + { + var result = await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false); + + return result; + } + + items = ((Folder)item).GetChildren(user, true); + } + + return new QueryResult { Items = items.ToArray() }; + + } + + /// Gets report activities. + /// The request. + /// The report activities. + private Task GetReportActivities(IReportsDownload request) + { + return Task.Run(() => + { + DateTime? minDate = string.IsNullOrWhiteSpace(request.MinDate) ? + (DateTime?)null : + DateTime.Parse(request.MinDate, null, DateTimeStyles.RoundtripKind).ToUniversalTime(); + + QueryResult queryResult; + if (request.HasQueryLimit) + queryResult = _repo.GetActivityLogEntries(minDate, request.StartIndex, request.Limit); + else + queryResult = _repo.GetActivityLogEntries(minDate, request.StartIndex, null); + //var queryResult = _activityManager.GetActivityLogEntries(minDate, request.StartIndex, request.Limit); + + ReportActivitiesBuilder builder = new ReportActivitiesBuilder(_libraryManager, _userManager); + var result = builder.GetResult(queryResult, request); + result.TotalRecordCount = queryResult.TotalRecordCount; + return result; + + }); + + } + + /// Gets report result. + /// The request. + /// The report result. + private async Task GetReportResult(GetItemReport request) + { + ReportBuilder reportBuilder = new ReportBuilder(_libraryManager); + QueryResult queryResult = await GetQueryResult(request).ConfigureAwait(false); + ReportResult reportResult = reportBuilder.GetResult(queryResult.Items, request); + reportResult.TotalRecordCount = queryResult.TotalRecordCount; + + return reportResult; + } + + /// Gets report statistic. + /// The request. + /// The report statistic. + private async Task GetReportStatistic(GetReportStatistics request) + { + ReportIncludeItemTypes reportRowType = ReportHelper.GetRowType(request.IncludeItemTypes); + QueryResult queryResult = await GetQueryResult(request).ConfigureAwait(false); + + ReportStatBuilder reportBuilder = new ReportStatBuilder(_libraryManager); + ReportStatResult reportResult = reportBuilder.GetResult(queryResult.Items, ReportHelper.GetRowType(request.IncludeItemTypes), request.TopItems ?? 5); + reportResult.TotalRecordCount = reportResult.Groups.Count(); + return reportResult; + } + + #endregion + + } } diff --git a/MediaBrowser.Api/Reports/Stat/ReportStatBuilder.cs b/MediaBrowser.Api/Reports/Stat/ReportStatBuilder.cs index 541bb92d9f..c9ee6337ff 100644 --- a/MediaBrowser.Api/Reports/Stat/ReportStatBuilder.cs +++ b/MediaBrowser.Api/Reports/Stat/ReportStatBuilder.cs @@ -9,206 +9,276 @@ using System.Threading.Tasks; namespace MediaBrowser.Api.Reports { - /// A report stat builder. - /// - public class ReportStatBuilder : ReportBuilderBase - { - /// - /// Initializes a new instance of the MediaBrowser.Api.Reports.ReportStatBuilder class. - /// Manager for library. - public ReportStatBuilder(ILibraryManager libraryManager) - : base(libraryManager) - { - } - - /// Gets report stat result. - /// The items. - /// Type of the report row. - /// The top item. - /// The report stat result. - public ReportStatResult GetReportStatResult(BaseItem[] items, ReportViewType reportRowType, int topItem = 5) - { - ReportStatResult result = new ReportStatResult(); - result = this.GetResultGenres(result, items, topItem); - result = this.GetResultStudios(result, items, topItem); - result = this.GetResultPersons(result, items, topItem); - result = this.GetResultProductionYears(result, items, topItem); - result = this.GetResulProductionLocations(result, items, topItem); - result = this.GetResultCommunityRatings(result, items, topItem); - result = this.GetResultParentalRatings(result, items, topItem); - - switch (reportRowType) - { - case ReportViewType.Season: - case ReportViewType.Series: - case ReportViewType.MusicAlbum: - case ReportViewType.MusicArtist: - case ReportViewType.Game: - break; - case ReportViewType.Movie: - case ReportViewType.BoxSet: - - break; - case ReportViewType.Book: - case ReportViewType.Episode: - case ReportViewType.Video: - case ReportViewType.MusicVideo: - case ReportViewType.Trailer: - case ReportViewType.Audio: - case ReportViewType.BaseItem: - default: - break; - } - - result.Groups = result.Groups.OrderByDescending(n => n.Items.Count()).ToList(); - - return result; - } - - private ReportStatResult GetResultGenres(ReportStatResult result, BaseItem[] items, int topItem = 5) - { - this.GetGroups(result, ReportHelper.GetServerLocalizedString("HeaderGenres"), topItem, - items.SelectMany(x => x.Genres) - .GroupBy(x => x) - .OrderByDescending(x => x.Count()) - .Take(topItem) - .Select(x => new ReportStatItem - { - Name = x.Key, - Value = x.Count().ToString(), - Id = GetGenreID(x.Key) - })); - return result; - - } - - private ReportStatResult GetResultStudios(ReportStatResult result, BaseItem[] items, int topItem = 5) - { - this.GetGroups(result, ReportHelper.GetServerLocalizedString("HeaderStudios"), topItem, - items.SelectMany(x => x.Studios) - .GroupBy(x => x) - .OrderByDescending(x => x.Count()) - .Take(topItem) - .Select(x => new ReportStatItem - { - Name = x.Key, - Value = x.Count().ToString(), - Id = GetStudioID(x.Key) - }) - ); - - return result; - - } - - private ReportStatResult GetResultPersons(ReportStatResult result, BaseItem[] items, int topItem = 5) - { - List t = new List { PersonType.Actor, PersonType.Composer, PersonType.Director, PersonType.GuestStar, PersonType.Producer, PersonType.Writer, "Artist", "AlbumArtist" }; - foreach (var item in t) - { - this.GetGroups(result, ReportHelper.GetServerLocalizedString("Option" + item), topItem, - items.SelectMany(x => _libraryManager.GetPeople(x)) - .Where(n => n.Type == item) - .GroupBy(x => x.Name) - .OrderByDescending(x => x.Count()) - .Take(topItem) - .Select(x => new ReportStatItem - { - Name = x.Key, - Value = x.Count().ToString(), - Id = GetPersonID(x.Key) - }) - ); - } - - return result; - } - - private ReportStatResult GetResultCommunityRatings(ReportStatResult result, BaseItem[] items, int topItem = 5) - { - this.GetGroups(result, ReportHelper.GetServerLocalizedString("LabelCommunityRating"), topItem, - items.Where(x => x.CommunityRating != null && x.CommunityRating > 0) - .GroupBy(x => x.CommunityRating) - .OrderByDescending(x => x.Count()) - .Take(topItem) - .Select(x => new ReportStatItem - { - Name = x.Key.ToString(), - Value = x.Count().ToString() - }) - ); - - return result; - } - - private ReportStatResult GetResultParentalRatings(ReportStatResult result, BaseItem[] items, int topItem = 5) - { - this.GetGroups(result, ReportHelper.GetServerLocalizedString("HeaderParentalRatings"), topItem, - items.Where(x => x.OfficialRating != null) - .GroupBy(x => x.OfficialRating) - .OrderByDescending(x => x.Count()) - .Take(topItem) - .Select(x => new ReportStatItem - { - Name = x.Key.ToString(), - Value = x.Count().ToString() - }) - ); - - return result; - } - - - private ReportStatResult GetResultProductionYears(ReportStatResult result, BaseItem[] items, int topItem = 5) - { - this.GetGroups(result, ReportHelper.GetServerLocalizedString("HeaderYears"), topItem, - items.Where(x => x.ProductionYear != null && x.ProductionYear > 0) - .GroupBy(x => x.ProductionYear) - .OrderByDescending(x => x.Count()) - .Take(topItem) - .Select(x => new ReportStatItem - { - Name = x.Key.ToString(), - Value = x.Count().ToString() - }) - ); - - return result; - } - - private ReportStatResult GetResulProductionLocations(ReportStatResult result, BaseItem[] items, int topItem = 5) - { - this.GetGroups(result, ReportHelper.GetServerLocalizedString("HeaderCountries"), topItem, - items.OfType() - .Where(x => x.ProductionLocations != null) - .SelectMany(x => x.ProductionLocations) - .GroupBy(x => x) - .OrderByDescending(x => x.Count()) - .Take(topItem) - .Select(x => new ReportStatItem - { - Name = x.Key.ToString(), - Value = x.Count().ToString() - }) - ); - - return result; - } - - - /// Gets the groups. - /// The result. - /// The header. - /// The top item. - /// The top. - private void GetGroups(ReportStatResult result, string header, int topItem, IEnumerable top) - { - if (top.Count() > 0) - { - var group = new ReportStatGroup { Header = ReportStatGroup.FormatedHeader(header, topItem) }; - group.Items.AddRange(top); - result.Groups.Add(group); - } - } - } + /// A report stat builder. + /// + public class ReportStatBuilder : ReportBuilderBase + { + #region [Constructors] + + /// + /// Initializes a new instance of the MediaBrowser.Api.Reports.ReportStatBuilder class. + /// Manager for library. + public ReportStatBuilder(ILibraryManager libraryManager) + : base(libraryManager) + { + } + + #endregion + + #region [Public Methods] + + /// Gets report stat result. + /// The items. + /// List of types of the report include items. + /// The top item. + /// The report stat result. + public ReportStatResult GetResult(BaseItem[] items, ReportIncludeItemTypes reportIncludeItemTypes, int topItem = 5) + { + ReportStatResult result = new ReportStatResult(); + result = this.GetResultGenres(result, items, topItem); + result = this.GetResultStudios(result, items, topItem); + result = this.GetResultPersons(result, items, topItem); + result = this.GetResultProductionYears(result, items, topItem); + result = this.GetResulProductionLocations(result, items, topItem); + result = this.GetResultCommunityRatings(result, items, topItem); + result = this.GetResultParentalRatings(result, items, topItem); + + switch (reportIncludeItemTypes) + { + case ReportIncludeItemTypes.Season: + case ReportIncludeItemTypes.Series: + case ReportIncludeItemTypes.MusicAlbum: + case ReportIncludeItemTypes.MusicArtist: + case ReportIncludeItemTypes.Game: + break; + case ReportIncludeItemTypes.Movie: + case ReportIncludeItemTypes.BoxSet: + + break; + case ReportIncludeItemTypes.Book: + case ReportIncludeItemTypes.Episode: + case ReportIncludeItemTypes.Video: + case ReportIncludeItemTypes.MusicVideo: + case ReportIncludeItemTypes.Trailer: + case ReportIncludeItemTypes.Audio: + case ReportIncludeItemTypes.BaseItem: + default: + break; + } + + result.Groups = result.Groups.OrderByDescending(n => n.Items.Count()).ToList(); + + return result; + } + + #endregion + + #region [Protected Internal Methods] + /// Gets the headers. + /// Type of the header. + /// The request. + /// The headers. + /// + protected internal override List GetHeaders(H request) + { + throw new NotImplementedException(); + } + + #endregion + + #region [Private Methods] + + /// Gets the groups. + /// The result. + /// The header. + /// The top item. + /// The top. + private void GetGroups(ReportStatResult result, string header, int topItem, IEnumerable top) + { + if (top != null && top.Count() > 0) + { + var group = new ReportStatGroup { Header = ReportStatGroup.FormatedHeader(header, topItem) }; + group.Items.AddRange(top); + result.Groups.Add(group); + } + } + + /// Gets resul production locations. + /// The result. + /// The items. + /// The top item. + /// The resul production locations. + private ReportStatResult GetResulProductionLocations(ReportStatResult result, BaseItem[] items, int topItem = 5) + { + this.GetGroups(result, GetLocalizedHeader(HeaderMetadata.Countries), topItem, + items.OfType() + .Where(x => x.ProductionLocations != null) + .SelectMany(x => x.ProductionLocations) + .GroupBy(x => x) + .OrderByDescending(x => x.Count()) + .Take(topItem) + .Select(x => new ReportStatItem + { + Name = x.Key.ToString(), + Value = x.Count().ToString() + }) + ); + + return result; + } + + /// Gets result community ratings. + /// The result. + /// The items. + /// The top item. + /// The result community ratings. + private ReportStatResult GetResultCommunityRatings(ReportStatResult result, BaseItem[] items, int topItem = 5) + { + this.GetGroups(result, GetLocalizedHeader(HeaderMetadata.CommunityRating), topItem, + items.Where(x => x.CommunityRating != null && x.CommunityRating > 0) + .GroupBy(x => x.CommunityRating) + .OrderByDescending(x => x.Count()) + .Take(topItem) + .Select(x => new ReportStatItem + { + Name = x.Key.ToString(), + Value = x.Count().ToString() + }) + ); + + return result; + } + + /// Gets result genres. + /// The result. + /// The items. + /// The top item. + /// The result genres. + private ReportStatResult GetResultGenres(ReportStatResult result, BaseItem[] items, int topItem = 5) + { + this.GetGroups(result, GetLocalizedHeader(HeaderMetadata.Genres), topItem, + items.SelectMany(x => x.Genres) + .GroupBy(x => x) + .OrderByDescending(x => x.Count()) + .Take(topItem) + .Select(x => new ReportStatItem + { + Name = x.Key, + Value = x.Count().ToString(), + Id = GetGenreID(x.Key) + })); + return result; + + } + + /// Gets result parental ratings. + /// The result. + /// The items. + /// The top item. + /// The result parental ratings. + private ReportStatResult GetResultParentalRatings(ReportStatResult result, BaseItem[] items, int topItem = 5) + { + this.GetGroups(result, GetLocalizedHeader(HeaderMetadata.ParentalRatings), topItem, + items.Where(x => x.OfficialRating != null) + .GroupBy(x => x.OfficialRating) + .OrderByDescending(x => x.Count()) + .Take(topItem) + .Select(x => new ReportStatItem + { + Name = x.Key.ToString(), + Value = x.Count().ToString() + }) + ); + + return result; + } + + /// Gets result persons. + /// The result. + /// The items. + /// The top item. + /// The result persons. + private ReportStatResult GetResultPersons(ReportStatResult result, BaseItem[] items, int topItem = 5) + { + List t = new List + { + HeaderMetadata.Actor, + HeaderMetadata.Composer, + HeaderMetadata.Director, + HeaderMetadata.GuestStar, + HeaderMetadata.Producer, + HeaderMetadata.Writer, + HeaderMetadata.Artist, + HeaderMetadata.AlbumArtist + }; + foreach (var item in t) + { + var ps = items.Where(x => x.People != null && x.SupportsPeople).SelectMany(x => x.People) + .Where(n => n.Type == item.ToString()) + .GroupBy(x => x.Name) + .OrderByDescending(x => x.Count()) + .Take(topItem); + if (ps != null && ps.Count() > 0) + this.GetGroups(result, GetLocalizedHeader(item), topItem, + ps.Select(x => new ReportStatItem + { + Name = x.Key, + Value = x.Count().ToString(), + Id = GetPersonID(x.Key) + }) + ); + } + + return result; + } + + /// Gets result production years. + /// The result. + /// The items. + /// The top item. + /// The result production years. + private ReportStatResult GetResultProductionYears(ReportStatResult result, BaseItem[] items, int topItem = 5) + { + this.GetGroups(result, GetLocalizedHeader(HeaderMetadata.Year), topItem, + items.Where(x => x.ProductionYear != null && x.ProductionYear > 0) + .GroupBy(x => x.ProductionYear) + .OrderByDescending(x => x.Count()) + .Take(topItem) + .Select(x => new ReportStatItem + { + Name = x.Key.ToString(), + Value = x.Count().ToString() + }) + ); + + return result; + } + + /// Gets result studios. + /// The result. + /// The items. + /// The top item. + /// The result studios. + private ReportStatResult GetResultStudios(ReportStatResult result, BaseItem[] items, int topItem = 5) + { + this.GetGroups(result, GetLocalizedHeader(HeaderMetadata.Studios), topItem, + items.SelectMany(x => x.Studios) + .GroupBy(x => x) + .OrderByDescending(x => x.Count()) + .Take(topItem) + .Select(x => new ReportStatItem + { + Name = x.Key, + Value = x.Count().ToString(), + Id = GetStudioID(x.Key) + }) + ); + + return result; + + } + + #endregion + + } } diff --git a/MediaBrowser.Api/SearchService.cs b/MediaBrowser.Api/SearchService.cs index 2cca72593b..302b8d834c 100644 --- a/MediaBrowser.Api/SearchService.cs +++ b/MediaBrowser.Api/SearchService.cs @@ -4,6 +4,7 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Search; @@ -171,6 +172,8 @@ namespace MediaBrowser.Api ProductionYear = item.ProductionYear }; + result.ChannelId = item.ChannelId; + var primaryImageTag = _imageProcessor.GetImageCacheTag(item, ImageType.Primary); if (primaryImageTag != null) @@ -181,24 +184,19 @@ namespace MediaBrowser.Api SetThumbImageInfo(result, item); SetBackdropImageInfo(result, item); - var episode = item as Episode; - - if (episode != null) + var hasSeries = item as IHasSeries; + if (hasSeries != null) { - result.Series = episode.Series.Name; + result.Series = hasSeries.SeriesName; } var season = item as Season; - if (season != null) { - result.Series = season.Series.Name; - result.EpisodeCount = season.GetRecursiveChildren(i => i is Episode).Count; } var series = item as Series; - if (series != null) { result.EpisodeCount = series.GetRecursiveChildren(i => i is Episode).Count; @@ -223,6 +221,12 @@ namespace MediaBrowser.Api result.Artists = song.Artists.ToArray(); } + if (!string.IsNullOrWhiteSpace(item.ChannelId)) + { + var channel = _libraryManager.GetItemById(item.ChannelId); + result.ChannelName = channel == null ? null : channel.Name; + } + return result; } diff --git a/MediaBrowser.Api/StartupWizardService.cs b/MediaBrowser.Api/StartupWizardService.cs index 6ee8d36035..e9ac45fa25 100644 --- a/MediaBrowser.Api/StartupWizardService.cs +++ b/MediaBrowser.Api/StartupWizardService.cs @@ -3,8 +3,10 @@ using MediaBrowser.Controller; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Connect; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.LiveTv; using MediaBrowser.Controller.Net; using MediaBrowser.Model.Configuration; +using MediaBrowser.Model.LiveTv; using ServiceStack; using System; using System.Linq; @@ -49,13 +51,15 @@ namespace MediaBrowser.Api private readonly IServerApplicationHost _appHost; private readonly IUserManager _userManager; private readonly IConnectManager _connectManager; + private readonly ILiveTvManager _liveTvManager; - public StartupWizardService(IServerConfigurationManager config, IServerApplicationHost appHost, IUserManager userManager, IConnectManager connectManager) + public StartupWizardService(IServerConfigurationManager config, IServerApplicationHost appHost, IUserManager userManager, IConnectManager connectManager, ILiveTvManager liveTvManager) { _config = config; _appHost = appHost; _userManager = userManager; _connectManager = connectManager; + _liveTvManager = liveTvManager; } public void Post(ReportStartupWizardComplete request) @@ -67,6 +71,8 @@ namespace MediaBrowser.Api _config.Configuration.EnableLibraryMetadataSubFolder = true; _config.Configuration.EnableUserSpecificUserViews = true; _config.Configuration.EnableCustomPathSubFolders = true; + _config.Configuration.DisableXmlSavers = true; + _config.Configuration.DisableStartupScan = true; _config.SaveConfiguration(); } @@ -82,7 +88,7 @@ namespace MediaBrowser.Api public object Get(GetStartupConfiguration request) { - return new StartupConfiguration + var result = new StartupConfiguration { UICulture = _config.Configuration.UICulture, EnableInternetProviders = _config.Configuration.EnableInternetProviders, @@ -90,6 +96,22 @@ namespace MediaBrowser.Api MetadataCountryCode = _config.Configuration.MetadataCountryCode, PreferredMetadataLanguage = _config.Configuration.PreferredMetadataLanguage }; + + var tvConfig = GetLiveTVConfiguration(); + + if (tvConfig.TunerHosts.Count > 0) + { + result.LiveTvTunerPath = tvConfig.TunerHosts[0].Url; + result.LiveTvTunerType = tvConfig.TunerHosts[0].Type; + } + + if (tvConfig.ListingProviders.Count > 0) + { + result.LiveTvGuideProviderId = tvConfig.ListingProviders[0].Id; + result.LiveTvGuideProviderType = tvConfig.ListingProviders[0].Type; + } + + return result; } public void Post(UpdateStartupConfiguration request) @@ -100,6 +122,9 @@ namespace MediaBrowser.Api _config.Configuration.MetadataCountryCode = request.MetadataCountryCode; _config.Configuration.PreferredMetadataLanguage = request.PreferredMetadataLanguage; _config.SaveConfiguration(); + + var task = UpdateTuners(request); + Task.WaitAll(task); } public object Get(GetStartupUser request) @@ -140,6 +165,51 @@ namespace MediaBrowser.Api return result; } + + private async Task UpdateTuners(UpdateStartupConfiguration request) + { + var config = GetLiveTVConfiguration(); + var save = false; + + if (string.IsNullOrWhiteSpace(request.LiveTvTunerPath) || + string.IsNullOrWhiteSpace(request.LiveTvTunerType)) + { + if (config.TunerHosts.Count > 0) + { + config.TunerHosts.Clear(); + save = true; + } + } + else + { + if (!config.TunerHosts.Any(i => string.Equals(i.Type, request.LiveTvTunerType, StringComparison.OrdinalIgnoreCase) && string.Equals(i.Url, request.LiveTvTunerPath, StringComparison.OrdinalIgnoreCase))) + { + // Add tuner + await _liveTvManager.SaveTunerHost(new TunerHostInfo + { + IsEnabled = true, + Type = request.LiveTvTunerType, + Url = request.LiveTvTunerPath + + }).ConfigureAwait(false); + } + } + + if (save) + { + SaveLiveTVConfiguration(config); + } + } + + private void SaveLiveTVConfiguration(LiveTvOptions config) + { + _config.SaveConfiguration("livetv", config); + } + + private LiveTvOptions GetLiveTVConfiguration() + { + return _config.GetConfiguration("livetv"); + } } public class StartupConfiguration @@ -149,6 +219,10 @@ namespace MediaBrowser.Api public bool SaveLocalMeta { get; set; } public string MetadataCountryCode { get; set; } public string PreferredMetadataLanguage { get; set; } + public string LiveTvTunerType { get; set; } + public string LiveTvTunerPath { get; set; } + public string LiveTvGuideProviderId { get; set; } + public string LiveTvGuideProviderType { get; set; } } public class StartupInfo diff --git a/MediaBrowser.Api/Sync/SyncHelper.cs b/MediaBrowser.Api/Sync/SyncHelper.cs index 6a5b6a927a..0d3e8707db 100644 --- a/MediaBrowser.Api/Sync/SyncHelper.cs +++ b/MediaBrowser.Api/Sync/SyncHelper.cs @@ -10,11 +10,6 @@ namespace MediaBrowser.Api.Sync { List options = new List(); - if (items.Count > 1) - { - options.Add(SyncJobOption.Name); - } - foreach (BaseItemDto item in items) { if (item.SupportsSync ?? false) @@ -65,7 +60,6 @@ namespace MediaBrowser.Api.Sync { List options = new List(); - options.Add(SyncJobOption.Name); options.Add(SyncJobOption.Quality); options.Add(SyncJobOption.Profile); options.Add(SyncJobOption.UnwatchedOnly); diff --git a/MediaBrowser.Api/UserLibrary/ArtistsService.cs b/MediaBrowser.Api/UserLibrary/ArtistsService.cs index 2393d05335..cde5eade5b 100644 --- a/MediaBrowser.Api/UserLibrary/ArtistsService.cs +++ b/MediaBrowser.Api/UserLibrary/ArtistsService.cs @@ -6,7 +6,6 @@ using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Dto; using ServiceStack; -using System; using System.Collections.Generic; using System.Linq; @@ -124,48 +123,18 @@ namespace MediaBrowser.Api.UserLibrary /// The request. /// The items. /// IEnumerable{Tuple{System.StringFunc{System.Int32}}}. - protected override IEnumerable GetAllItems(GetItemsByName request, IEnumerable items) + protected override IEnumerable GetAllItems(GetItemsByName request, IEnumerable items) { if (request is GetAlbumArtists) { - return items - .Where(i => !i.IsFolder) - .OfType() - .SelectMany(i => i.AlbumArtists) - .DistinctNames() - .Select(name => - { - try - { - return LibraryManager.GetArtist(name); - } - catch (Exception ex) - { - Logger.ErrorException("Error getting artist {0}", ex, name); - return null; - } - - }).Where(i => i != null); + return LibraryManager.GetAlbumArtists(items + .Where(i => !i.IsFolder) + .OfType()); } - return items + return LibraryManager.GetArtists(items .Where(i => !i.IsFolder) - .OfType() - .SelectMany(i => i.AllArtists) - .DistinctNames() - .Select(name => - { - try - { - return LibraryManager.GetArtist(name); - } - catch (Exception ex) - { - Logger.ErrorException("Error getting artist {0}", ex, name); - return null; - } - - }).Where(i => i != null); + .OfType()); } } } diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs index 8084fd083b..a282029433 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs @@ -48,6 +48,42 @@ namespace MediaBrowser.Api.UserLibrary DtoService = dtoService; } + protected BaseItem GetParentItem(GetItemsByName request) + { + BaseItem parentItem; + + if (!string.IsNullOrWhiteSpace(request.UserId)) + { + var user = UserManager.GetUserById(request.UserId); + parentItem = string.IsNullOrEmpty(request.ParentId) ? user.RootFolder : LibraryManager.GetItemById(request.ParentId); + } + else + { + parentItem = string.IsNullOrEmpty(request.ParentId) ? LibraryManager.RootFolder : LibraryManager.GetItemById(request.ParentId); + } + + return parentItem; + } + + protected string GetParentItemViewType(GetItemsByName request) + { + var parent = GetParentItem(request); + + var collectionFolder = parent as ICollectionFolder; + if (collectionFolder != null) + { + return collectionFolder.CollectionType; + } + + var view = parent as UserView; + if (view != null) + { + return view.ViewType; + } + + return null; + } + /// /// Gets the specified request. /// @@ -114,13 +150,13 @@ namespace MediaBrowser.Api.UserLibrary var filteredItems = FilterItems(request, extractedItems, user); - filteredItems = FilterByLibraryItems(request, filteredItems, user, libraryItems); + filteredItems = FilterByLibraryItems(request, filteredItems.Cast(), user, libraryItems).Cast(); - filteredItems = LibraryManager.Sort(filteredItems, user, request.GetOrderBy(), request.SortOrder ?? SortOrder.Ascending).Cast(); + filteredItems = LibraryManager.Sort(filteredItems, user, request.GetOrderBy(), request.SortOrder ?? SortOrder.Ascending); var ibnItemsArray = filteredItems.ToList(); - IEnumerable ibnItems = ibnItemsArray; + IEnumerable ibnItems = ibnItemsArray; var result = new ItemsResult { @@ -141,14 +177,14 @@ namespace MediaBrowser.Api.UserLibrary } - IEnumerable>> tuples; + IEnumerable>> tuples; if (dtoOptions.Fields.Contains(ItemFields.ItemCounts)) { - tuples = ibnItems.Select(i => new Tuple>(i, i.GetTaggedItems(libraryItems).ToList())); + tuples = ibnItems.Select(i => new Tuple>(i, ((IItemByName)i).GetTaggedItems(libraryItems).ToList())); } else { - tuples = ibnItems.Select(i => new Tuple>(i, new List())); + tuples = ibnItems.Select(i => new Tuple>(i, new List())); } var dtos = tuples.Select(i => DtoService.GetItemByNameDto(i.Item1, dtoOptions, i.Item2, user)); @@ -180,7 +216,7 @@ namespace MediaBrowser.Api.UserLibrary return options.Fields.Contains(ItemFields.ItemCounts); } - private IEnumerable FilterByLibraryItems(GetItemsByName request, IEnumerable items, User user, IEnumerable libraryItems) + private IEnumerable FilterByLibraryItems(GetItemsByName request, IEnumerable items, User user, IEnumerable libraryItems) { var filters = request.GetFilters().ToList(); @@ -211,7 +247,7 @@ namespace MediaBrowser.Api.UserLibrary /// The items. /// The user. /// IEnumerable{`0}. - private IEnumerable FilterItems(GetItemsByName request, IEnumerable items, User user) + private IEnumerable FilterItems(GetItemsByName request, IEnumerable items, User user) { if (!string.IsNullOrEmpty(request.NameStartsWithOrGreater)) { @@ -375,7 +411,7 @@ namespace MediaBrowser.Api.UserLibrary /// The request. /// The items. /// IEnumerable{Task{`0}}. - protected abstract IEnumerable GetAllItems(GetItemsByName request, IEnumerable items); + protected abstract IEnumerable GetAllItems(GetItemsByName request, IEnumerable items); } /// @@ -383,22 +419,6 @@ namespace MediaBrowser.Api.UserLibrary /// public class GetItemsByName : BaseItemsRequest, IReturn { - /// - /// Gets or sets the user id. - /// - /// The user id. - [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string UserId { get; set; } - - [ApiMember(Name = "NameStartsWithOrGreater", Description = "Optional filter by items whose name is sorted equally or greater than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string NameStartsWithOrGreater { get; set; } - - [ApiMember(Name = "NameStartsWith", Description = "Optional filter by items whose name is sorted equally than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string NameStartsWith { get; set; } - - [ApiMember(Name = "NameLessThan", Description = "Optional filter by items whose name is sorted less than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string NameLessThan { get; set; } - public GetItemsByName() { Recursive = true; diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs index fffc11d68f..7db8e4dcad 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs @@ -14,6 +14,97 @@ namespace MediaBrowser.Api.UserLibrary EnableImages = true; } + /// + /// Gets or sets the max offical rating. + /// + /// The max offical rating. + [ApiMember(Name = "MaxOfficialRating", Description = "Optional filter by maximum official rating (PG, PG-13, TV-MA, etc).", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string MaxOfficialRating { get; set; } + + [ApiMember(Name = "HasThemeSong", Description = "Optional filter by items with theme songs.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public bool? HasThemeSong { get; set; } + + [ApiMember(Name = "HasThemeVideo", Description = "Optional filter by items with theme videos.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public bool? HasThemeVideo { get; set; } + + [ApiMember(Name = "HasSubtitles", Description = "Optional filter by items with subtitles.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public bool? HasSubtitles { get; set; } + + [ApiMember(Name = "HasSpecialFeature", Description = "Optional filter by items with special features.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public bool? HasSpecialFeature { get; set; } + + [ApiMember(Name = "HasTrailer", Description = "Optional filter by items with trailers.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public bool? HasTrailer { get; set; } + + [ApiMember(Name = "AdjacentTo", Description = "Optional. Return items that are siblings of a supplied item.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string AdjacentTo { get; set; } + + [ApiMember(Name = "MinIndexNumber", Description = "Optional filter by minimum index number.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? MinIndexNumber { get; set; } + + [ApiMember(Name = "MinPlayers", Description = "Optional filter by minimum number of game players.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? MinPlayers { get; set; } + + [ApiMember(Name = "MaxPlayers", Description = "Optional filter by maximum number of game players.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? MaxPlayers { get; set; } + + [ApiMember(Name = "ParentIndexNumber", Description = "Optional filter by parent index number.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? ParentIndexNumber { get; set; } + + [ApiMember(Name = "HasParentalRating", Description = "Optional filter by items that have or do not have a parental rating", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool? HasParentalRating { get; set; } + + [ApiMember(Name = "IsHD", Description = "Optional filter by items that are HD or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool? IsHD { get; set; } + + [ApiMember(Name = "LocationTypes", Description = "Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] + public string LocationTypes { get; set; } + + [ApiMember(Name = "ExcludeLocationTypes", Description = "Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] + public string ExcludeLocationTypes { get; set; } + + [ApiMember(Name = "IsMissing", Description = "Optional filter by items that are missing episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool? IsMissing { get; set; } + + [ApiMember(Name = "IsUnaired", Description = "Optional filter by items that are unaired episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool? IsUnaired { get; set; } + + [ApiMember(Name = "IsVirtualUnaired", Description = "Optional filter by items that are virtual unaired episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool? IsVirtualUnaired { get; set; } + + [ApiMember(Name = "MinCommunityRating", Description = "Optional filter by minimum community rating.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public double? MinCommunityRating { get; set; } + + [ApiMember(Name = "MinCriticRating", Description = "Optional filter by minimum critic rating.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public double? MinCriticRating { get; set; } + + [ApiMember(Name = "AiredDuringSeason", Description = "Gets all episodes that aired during a season, including specials.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int? AiredDuringSeason { get; set; } + + [ApiMember(Name = "MinPremiereDate", Description = "Optional. The minimum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public string MinPremiereDate { get; set; } + + [ApiMember(Name = "MaxPremiereDate", Description = "Optional. The maximum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public string MaxPremiereDate { get; set; } + + [ApiMember(Name = "HasOverview", Description = "Optional filter by items that have an overview or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool? HasOverview { get; set; } + + [ApiMember(Name = "HasImdbId", Description = "Optional filter by items that have an imdb id or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool? HasImdbId { get; set; } + + [ApiMember(Name = "HasTmdbId", Description = "Optional filter by items that have a tmdb id or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool? HasTmdbId { get; set; } + + [ApiMember(Name = "HasTvdbId", Description = "Optional filter by items that have a tvdb id or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool? HasTvdbId { get; set; } + + [ApiMember(Name = "IsYearMismatched", Description = "Optional filter by items that are potentially misidentified.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool? IsYearMismatched { get; set; } + + [ApiMember(Name = "IsInBoxSet", Description = "Optional filter by items that are in boxsets, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool? IsInBoxSet { get; set; } + /// /// Skips over a given number of items within the results. Use for paging. /// @@ -130,6 +221,121 @@ namespace MediaBrowser.Api.UserLibrary [ApiMember(Name = "EnableImageTypes", Description = "Optional. The image types to include in the output.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string EnableImageTypes { get; set; } + /// + /// Limit results to items containing a specific person + /// + /// The person. + [ApiMember(Name = "Person", Description = "Optional. If specified, results will be filtered to include only those containing the specified person.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string Person { get; set; } + + [ApiMember(Name = "PersonIds", Description = "Optional. If specified, results will be filtered to include only those containing the specified person.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string PersonIds { get; set; } + + /// + /// If the Person filter is used, this can also be used to restrict to a specific person type + /// + /// The type of the person. + [ApiMember(Name = "PersonTypes", Description = "Optional. If specified, along with Person, results will be filtered to include only those containing the specified person and PersonType. Allows multiple, comma-delimited", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string PersonTypes { get; set; } + + /// + /// Limit results to items containing specific studios + /// + /// The studios. + [ApiMember(Name = "Studios", Description = "Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] + public string Studios { get; set; } + + [ApiMember(Name = "StudioIds", Description = "Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] + public string StudioIds { get; set; } + + /// + /// Gets or sets the studios. + /// + /// The studios. + [ApiMember(Name = "Artists", Description = "Optional. If specified, results will be filtered based on artist. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] + public string Artists { get; set; } + + [ApiMember(Name = "ArtistIds", Description = "Optional. If specified, results will be filtered based on artist. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] + public string ArtistIds { get; set; } + + [ApiMember(Name = "Albums", Description = "Optional. If specified, results will be filtered based on album. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] + public string Albums { get; set; } + + /// + /// Gets or sets the item ids. + /// + /// The item ids. + [ApiMember(Name = "Ids", Description = "Optional. If specific items are needed, specify a list of item id's to retrieve. This allows multiple, comma delimited.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] + public string Ids { get; set; } + + /// + /// Gets or sets the video types. + /// + /// The video types. + [ApiMember(Name = "VideoTypes", Description = "Optional filter by VideoType (videofile, dvd, bluray, iso). Allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] + public string VideoTypes { get; set; } + + /// + /// Gets or sets the air days. + /// + /// The air days. + [ApiMember(Name = "AirDays", Description = "Optional filter by Series Air Days. Allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] + public string AirDays { get; set; } + + /// + /// Gets or sets the user id. + /// + /// The user id. + [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] + public string UserId { get; set; } + + /// + /// Gets or sets the min offical rating. + /// + /// The min offical rating. + [ApiMember(Name = "MinOfficialRating", Description = "Optional filter by minimum official rating (PG, PG-13, TV-MA, etc).", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string MinOfficialRating { get; set; } + + [ApiMember(Name = "IsLocked", Description = "Optional filter by items that are locked.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public bool? IsLocked { get; set; } + + [ApiMember(Name = "IsUnidentified", Description = "Optional filter by items that are unidentified by internet metadata providers.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public bool? IsUnidentified { get; set; } + + [ApiMember(Name = "IsPlaceHolder", Description = "Optional filter by items that are placeholders", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public bool? IsPlaceHolder { get; set; } + + [ApiMember(Name = "HasOfficialRating", Description = "Optional filter by items that have official ratings", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public bool? HasOfficialRating { get; set; } + + [ApiMember(Name = "CollapseBoxSetItems", Description = "Whether or not to hide items behind their boxsets.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool? CollapseBoxSetItems { get; set; } + /// + /// Gets or sets the video formats. + /// + /// The video formats. + [ApiMember(Name = "Is3D", Description = "Optional filter by items that are 3D, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool? Is3D { get; set; } + + /// + /// Gets or sets the series status. + /// + /// The series status. + [ApiMember(Name = "SeriesStatus", Description = "Optional filter by Series Status. Allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] + public string SeriesStatus { get; set; } + + [ApiMember(Name = "NameStartsWithOrGreater", Description = "Optional filter by items whose name is sorted equally or greater than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string NameStartsWithOrGreater { get; set; } + + [ApiMember(Name = "NameStartsWith", Description = "Optional filter by items whose name is sorted equally than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string NameStartsWith { get; set; } + + [ApiMember(Name = "NameLessThan", Description = "Optional filter by items whose name is equally or lesser than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string NameLessThan { get; set; } + + [ApiMember(Name = "AlbumArtistStartsWithOrGreater", Description = "Optional filter by items whose album artist is sorted equally or greater than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public string AlbumArtistStartsWithOrGreater { get; set; } + public string[] GetGenres() { return (Genres ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); @@ -164,6 +370,43 @@ namespace MediaBrowser.Api.UserLibrary { return (Years ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray(); } + + public string[] GetStudios() + { + return (Studios ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); + } + + public string[] GetStudioIds() + { + return (StudioIds ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); + } + + public string[] GetPersonTypes() + { + return (PersonTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + } + + public string[] GetPersonIds() + { + return (PersonIds ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + } + + public string[] GetItemIds() + { + return (Ids ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + } + + public VideoType[] GetVideoTypes() + { + var val = VideoTypes; + + if (string.IsNullOrEmpty(val)) + { + return new VideoType[] { }; + } + + return val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(v => (VideoType)Enum.Parse(typeof(VideoType), v, true)).ToArray(); + } /// /// Gets the filters. diff --git a/MediaBrowser.Api/UserLibrary/GameGenresService.cs b/MediaBrowser.Api/UserLibrary/GameGenresService.cs index f9d0f0d0fa..a1ad14a4de 100644 --- a/MediaBrowser.Api/UserLibrary/GameGenresService.cs +++ b/MediaBrowser.Api/UserLibrary/GameGenresService.cs @@ -99,14 +99,24 @@ namespace MediaBrowser.Api.UserLibrary /// The request. /// The items. /// IEnumerable{Tuple{System.StringFunc{System.Int32}}}. - protected override IEnumerable GetAllItems(GetItemsByName request, IEnumerable items) + protected override IEnumerable GetAllItems(GetItemsByName request, IEnumerable items) { - var itemsList = items.Where(i => i.Genres != null).ToList(); - - return itemsList + return items .SelectMany(i => i.Genres) .DistinctNames() - .Select(name => LibraryManager.GetGameGenre(name)); + .Select(name => + { + try + { + return LibraryManager.GetGameGenre(name); + } + catch (Exception ex) + { + Logger.ErrorException("Error getting genre {0}", ex, name); + return null; + } + }) + .Where(i => i != null); } } } diff --git a/MediaBrowser.Api/UserLibrary/GenresService.cs b/MediaBrowser.Api/UserLibrary/GenresService.cs index 0702468861..d383bd0adb 100644 --- a/MediaBrowser.Api/UserLibrary/GenresService.cs +++ b/MediaBrowser.Api/UserLibrary/GenresService.cs @@ -1,11 +1,10 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Querying; +using MediaBrowser.Model.Entities; using ServiceStack; using System; using System.Collections.Generic; @@ -104,8 +103,38 @@ namespace MediaBrowser.Api.UserLibrary /// The request. /// The items. /// IEnumerable{Tuple{System.StringFunc{System.Int32}}}. - protected override IEnumerable GetAllItems(GetItemsByName request, IEnumerable items) + protected override IEnumerable GetAllItems(GetItemsByName request, IEnumerable items) { + var viewType = GetParentItemViewType(request); + + if (string.Equals(viewType, CollectionType.Music) || string.Equals(viewType, CollectionType.MusicVideos)) + { + return items + .SelectMany(i => i.Genres) + .DistinctNames() + .Select(name => LibraryManager.GetMusicGenre(name)); + } + + if (string.Equals(viewType, CollectionType.Games)) + { + return items + .SelectMany(i => i.Genres) + .DistinctNames() + .Select(name => + { + try + { + return LibraryManager.GetGameGenre(name); + } + catch (Exception ex) + { + Logger.ErrorException("Error getting genre {0}", ex, name); + return null; + } + }) + .Where(i => i != null); + } + return items .SelectMany(i => i.Genres) .DistinctNames() diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index fda933b593..7d3290c2f9 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -2,7 +2,6 @@ using MediaBrowser.Controller.Dto; 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.Localization; @@ -25,249 +24,6 @@ namespace MediaBrowser.Api.UserLibrary [Route("/Users/{UserId}/Items", "GET", Summary = "Gets items based on a query.")] public class GetItems : BaseItemsRequest, IReturn { - /// - /// Gets or sets the user id. - /// - /// The user id. - [ApiMember(Name = "UserId", Description = "User Id", IsRequired = false, DataType = "string", ParameterType = "path", Verb = "GET")] - public string UserId { get; set; } - - /// - /// Limit results to items containing a specific person - /// - /// The person. - [ApiMember(Name = "Person", Description = "Optional. If specified, results will be filtered to include only those containing the specified person.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string Person { get; set; } - - [ApiMember(Name = "PersonIds", Description = "Optional. If specified, results will be filtered to include only those containing the specified person.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string PersonIds { get; set; } - - /// - /// If the Person filter is used, this can also be used to restrict to a specific person type - /// - /// The type of the person. - [ApiMember(Name = "PersonTypes", Description = "Optional. If specified, along with Person, results will be filtered to include only those containing the specified person and PersonType. Allows multiple, comma-delimited", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string PersonTypes { get; set; } - - /// - /// Limit results to items containing specific studios - /// - /// The studios. - [ApiMember(Name = "Studios", Description = "Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string Studios { get; set; } - - [ApiMember(Name = "StudioIds", Description = "Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string StudioIds { get; set; } - - /// - /// Gets or sets the studios. - /// - /// The studios. - [ApiMember(Name = "Artists", Description = "Optional. If specified, results will be filtered based on artist. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string Artists { get; set; } - - [ApiMember(Name = "ArtistIds", Description = "Optional. If specified, results will be filtered based on artist. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string ArtistIds { get; set; } - - [ApiMember(Name = "Albums", Description = "Optional. If specified, results will be filtered based on album. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string Albums { get; set; } - - /// - /// Gets or sets the item ids. - /// - /// The item ids. - [ApiMember(Name = "Ids", Description = "Optional. If specific items are needed, specify a list of item id's to retrieve. This allows multiple, comma delimited.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string Ids { get; set; } - - /// - /// Gets or sets the video types. - /// - /// The video types. - [ApiMember(Name = "VideoTypes", Description = "Optional filter by VideoType (videofile, dvd, bluray, iso). Allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string VideoTypes { get; set; } - - /// - /// Gets or sets the video formats. - /// - /// The video formats. - [ApiMember(Name = "Is3D", Description = "Optional filter by items that are 3D, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? Is3D { get; set; } - - /// - /// Gets or sets the series status. - /// - /// The series status. - [ApiMember(Name = "SeriesStatus", Description = "Optional filter by Series Status. Allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string SeriesStatus { get; set; } - - [ApiMember(Name = "NameStartsWithOrGreater", Description = "Optional filter by items whose name is sorted equally or greater than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string NameStartsWithOrGreater { get; set; } - - [ApiMember(Name = "NameStartsWith", Description = "Optional filter by items whose name is sorted equally than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string NameStartsWith { get; set; } - - [ApiMember(Name = "NameLessThan", Description = "Optional filter by items whose name is equally or lesser than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string NameLessThan { get; set; } - - [ApiMember(Name = "AlbumArtistStartsWithOrGreater", Description = "Optional filter by items whose album artist is sorted equally or greater than a given input string.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string AlbumArtistStartsWithOrGreater { get; set; } - - /// - /// Gets or sets the air days. - /// - /// The air days. - [ApiMember(Name = "AirDays", Description = "Optional filter by Series Air Days. Allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string AirDays { get; set; } - - /// - /// Gets or sets the min offical rating. - /// - /// The min offical rating. - [ApiMember(Name = "MinOfficialRating", Description = "Optional filter by minimum official rating (PG, PG-13, TV-MA, etc).", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string MinOfficialRating { get; set; } - - /// - /// Gets or sets the max offical rating. - /// - /// The max offical rating. - [ApiMember(Name = "MaxOfficialRating", Description = "Optional filter by maximum official rating (PG, PG-13, TV-MA, etc).", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string MaxOfficialRating { get; set; } - - [ApiMember(Name = "HasThemeSong", Description = "Optional filter by items with theme songs.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public bool? HasThemeSong { get; set; } - - [ApiMember(Name = "HasThemeVideo", Description = "Optional filter by items with theme videos.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public bool? HasThemeVideo { get; set; } - - [ApiMember(Name = "HasSubtitles", Description = "Optional filter by items with subtitles.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public bool? HasSubtitles { get; set; } - - [ApiMember(Name = "HasSpecialFeature", Description = "Optional filter by items with special features.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public bool? HasSpecialFeature { get; set; } - - [ApiMember(Name = "HasTrailer", Description = "Optional filter by items with trailers.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public bool? HasTrailer { get; set; } - - [ApiMember(Name = "AdjacentTo", Description = "Optional. Return items that are siblings of a supplied item.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public string AdjacentTo { get; set; } - - [ApiMember(Name = "MinIndexNumber", Description = "Optional filter by minimum index number.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? MinIndexNumber { get; set; } - - [ApiMember(Name = "MinPlayers", Description = "Optional filter by minimum number of game players.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? MinPlayers { get; set; } - - [ApiMember(Name = "MaxPlayers", Description = "Optional filter by maximum number of game players.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? MaxPlayers { get; set; } - - [ApiMember(Name = "ParentIndexNumber", Description = "Optional filter by parent index number.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? ParentIndexNumber { get; set; } - - [ApiMember(Name = "HasParentalRating", Description = "Optional filter by items that have or do not have a parental rating", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? HasParentalRating { get; set; } - - [ApiMember(Name = "IsHD", Description = "Optional filter by items that are HD or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? IsHD { get; set; } - - [ApiMember(Name = "LocationTypes", Description = "Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string LocationTypes { get; set; } - - [ApiMember(Name = "ExcludeLocationTypes", Description = "Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string ExcludeLocationTypes { get; set; } - - [ApiMember(Name = "IsMissing", Description = "Optional filter by items that are missing episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? IsMissing { get; set; } - - [ApiMember(Name = "IsUnaired", Description = "Optional filter by items that are unaired episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? IsUnaired { get; set; } - - [ApiMember(Name = "IsVirtualUnaired", Description = "Optional filter by items that are virtual unaired episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? IsVirtualUnaired { get; set; } - - [ApiMember(Name = "MinCommunityRating", Description = "Optional filter by minimum community rating.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public double? MinCommunityRating { get; set; } - - [ApiMember(Name = "MinCriticRating", Description = "Optional filter by minimum critic rating.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public double? MinCriticRating { get; set; } - - [ApiMember(Name = "AiredDuringSeason", Description = "Gets all episodes that aired during a season, including specials.", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] - public int? AiredDuringSeason { get; set; } - - [ApiMember(Name = "MinPremiereDate", Description = "Optional. The minimum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string MinPremiereDate { get; set; } - - [ApiMember(Name = "MaxPremiereDate", Description = "Optional. The maximum premiere date. Format = ISO", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] - public string MaxPremiereDate { get; set; } - - [ApiMember(Name = "HasOverview", Description = "Optional filter by items that have an overview or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? HasOverview { get; set; } - - [ApiMember(Name = "HasImdbId", Description = "Optional filter by items that have an imdb id or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? HasImdbId { get; set; } - - [ApiMember(Name = "HasTmdbId", Description = "Optional filter by items that have a tmdb id or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? HasTmdbId { get; set; } - - [ApiMember(Name = "HasTvdbId", Description = "Optional filter by items that have a tvdb id or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? HasTvdbId { get; set; } - - [ApiMember(Name = "IsYearMismatched", Description = "Optional filter by items that are potentially misidentified.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? IsYearMismatched { get; set; } - - [ApiMember(Name = "IsInBoxSet", Description = "Optional filter by items that are in boxsets, or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? IsInBoxSet { get; set; } - - [ApiMember(Name = "IsLocked", Description = "Optional filter by items that are locked.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public bool? IsLocked { get; set; } - - [ApiMember(Name = "IsUnidentified", Description = "Optional filter by items that are unidentified by internet metadata providers.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public bool? IsUnidentified { get; set; } - - [ApiMember(Name = "IsPlaceHolder", Description = "Optional filter by items that are placeholders", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public bool? IsPlaceHolder { get; set; } - - [ApiMember(Name = "HasOfficialRating", Description = "Optional filter by items that have official ratings", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public bool? HasOfficialRating { get; set; } - - [ApiMember(Name = "CollapseBoxSetItems", Description = "Whether or not to hide items behind their boxsets.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? CollapseBoxSetItems { get; set; } - - public string[] GetStudios() - { - return (Studios ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); - } - - public string[] GetStudioIds() - { - return (StudioIds ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries); - } - - public string[] GetPersonTypes() - { - return (PersonTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - } - - public string[] GetPersonIds() - { - return (PersonIds ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - } - - public string[] GetItemIds() - { - return (Ids ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - } - - public VideoType[] GetVideoTypes() - { - var val = VideoTypes; - - if (string.IsNullOrEmpty(val)) - { - return new VideoType[] { }; - } - - return val.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(v => (VideoType)Enum.Parse(typeof(VideoType), v, true)).ToArray(); - } } /// @@ -361,7 +117,16 @@ namespace MediaBrowser.Api.UserLibrary if (!string.IsNullOrEmpty(request.Ids)) { request.Recursive = true; - var result = await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false); + var query = GetItemsQuery(request, user); + var result = await ((Folder)item).GetItems(query).ConfigureAwait(false); + + if (string.IsNullOrWhiteSpace(request.SortBy)) + { + var ids = query.ItemIds.ToList(); + + // Try to preserve order + result.Items = result.Items.OrderBy(i => ids.IndexOf(i.Id.ToString("N"))).ToArray(); + } return new Tuple, bool>(result, true); } diff --git a/MediaBrowser.Api/UserLibrary/MusicGenresService.cs b/MediaBrowser.Api/UserLibrary/MusicGenresService.cs index e63d6c0f47..1826915840 100644 --- a/MediaBrowser.Api/UserLibrary/MusicGenresService.cs +++ b/MediaBrowser.Api/UserLibrary/MusicGenresService.cs @@ -99,11 +99,9 @@ namespace MediaBrowser.Api.UserLibrary /// The request. /// The items. /// IEnumerable{Tuple{System.StringFunc{System.Int32}}}. - protected override IEnumerable GetAllItems(GetItemsByName request, IEnumerable items) + protected override IEnumerable GetAllItems(GetItemsByName request, IEnumerable items) { - var itemsList = items.ToList(); - - return itemsList + return items .SelectMany(i => i.Genres) .DistinctNames() .Select(name => LibraryManager.GetMusicGenre(name)); diff --git a/MediaBrowser.Api/UserLibrary/PersonsService.cs b/MediaBrowser.Api/UserLibrary/PersonsService.cs index bd9898dcdf..e6a60fcc6d 100644 --- a/MediaBrowser.Api/UserLibrary/PersonsService.cs +++ b/MediaBrowser.Api/UserLibrary/PersonsService.cs @@ -16,12 +16,6 @@ namespace MediaBrowser.Api.UserLibrary [Route("/Persons", "GET", Summary = "Gets all persons from a given item, folder, or the entire library")] public class GetPersons : GetItemsByName { - /// - /// Gets or sets the person types. - /// - /// The person types. - [ApiMember(Name = "PersonTypes", Description = "Optional filter by person type. Accepts multiple, comma-delimited.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] - public string PersonTypes { get; set; } } /// @@ -114,7 +108,7 @@ namespace MediaBrowser.Api.UserLibrary /// The request. /// The items. /// IEnumerable{Tuple{System.StringFunc{System.Int32}}}. - protected override IEnumerable GetAllItems(GetItemsByName request, IEnumerable items) + protected override IEnumerable GetAllItems(GetItemsByName request, IEnumerable items) { var inputPersonTypes = ((GetPersons)request).PersonTypes; var personTypes = string.IsNullOrEmpty(inputPersonTypes) ? new string[] { } : inputPersonTypes.Split(','); diff --git a/MediaBrowser.Api/UserLibrary/PlaystateService.cs b/MediaBrowser.Api/UserLibrary/PlaystateService.cs index 5f32725d8a..7002a37035 100644 --- a/MediaBrowser.Api/UserLibrary/PlaystateService.cs +++ b/MediaBrowser.Api/UserLibrary/PlaystateService.cs @@ -185,6 +185,9 @@ namespace MediaBrowser.Api.UserLibrary [ApiMember(Name = "PlaySessionId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] public string PlaySessionId { get; set; } + + [ApiMember(Name = "RepeatMode", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public RepeatMode RepeatMode { get; set; } } /// @@ -325,7 +328,8 @@ namespace MediaBrowser.Api.UserLibrary VolumeLevel = request.VolumeLevel, PlayMethod = request.PlayMethod, PlaySessionId = request.PlaySessionId, - LiveStreamId = request.LiveStreamId + LiveStreamId = request.LiveStreamId, + RepeatMode = request.RepeatMode }); } diff --git a/MediaBrowser.Api/UserLibrary/StudiosService.cs b/MediaBrowser.Api/UserLibrary/StudiosService.cs index d6a7db14fb..7cf8d752a4 100644 --- a/MediaBrowser.Api/UserLibrary/StudiosService.cs +++ b/MediaBrowser.Api/UserLibrary/StudiosService.cs @@ -103,7 +103,7 @@ namespace MediaBrowser.Api.UserLibrary /// The request. /// The items. /// IEnumerable{Tuple{System.StringFunc{System.Int32}}}. - protected override IEnumerable GetAllItems(GetItemsByName request, IEnumerable items) + protected override IEnumerable GetAllItems(GetItemsByName request, IEnumerable items) { var itemsList = items.Where(i => i.Studios != null).ToList(); diff --git a/MediaBrowser.Api/UserLibrary/UserViewsService.cs b/MediaBrowser.Api/UserLibrary/UserViewsService.cs index 43d8dbc169..a49ab85562 100644 --- a/MediaBrowser.Api/UserLibrary/UserViewsService.cs +++ b/MediaBrowser.Api/UserLibrary/UserViewsService.cs @@ -89,7 +89,7 @@ namespace MediaBrowser.Api.UserLibrary var views = user.RootFolder .GetChildren(user, true) .OfType() - .Where(i => IsEligibleForSpecialView(i)) + .Where(IsEligibleForSpecialView) .ToList(); var list = views diff --git a/MediaBrowser.Api/UserLibrary/YearsService.cs b/MediaBrowser.Api/UserLibrary/YearsService.cs index 859b9f9592..4b3fe6c8e7 100644 --- a/MediaBrowser.Api/UserLibrary/YearsService.cs +++ b/MediaBrowser.Api/UserLibrary/YearsService.cs @@ -103,7 +103,7 @@ namespace MediaBrowser.Api.UserLibrary /// The request. /// The items. /// IEnumerable{Tuple{System.StringFunc{System.Int32}}}. - protected override IEnumerable GetAllItems(GetItemsByName request, IEnumerable items) + protected override IEnumerable GetAllItems(GetItemsByName request, IEnumerable items) { var itemsList = items.Where(i => i.ProductionYear != null).ToList(); diff --git a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs index ae2148f086..89f405e8a0 100644 --- a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs +++ b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs @@ -68,6 +68,9 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager // http://stackoverflow.com/questions/566437/http-post-returns-the-error-417-expectation-failed-c ServicePointManager.Expect100Continue = false; + + // Trakt requests sometimes fail without this + ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls; } /// @@ -124,7 +127,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager private WebRequest GetRequest(HttpRequestOptions options, string method, bool enableHttpCompression) { - var request = WebRequest.Create(options.Url); + var request = CreateWebRequest(options.Url); var httpWebRequest = request as HttpWebRequest; if (httpWebRequest != null) @@ -432,7 +435,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager var httpResponse = (HttpWebResponse)response; - EnsureSuccessStatusCode(httpResponse, options); + EnsureSuccessStatusCode(client, httpResponse, options); options.CancellationToken.ThrowIfCancellationRequested(); @@ -443,7 +446,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager { var httpResponse = (HttpWebResponse)response; - EnsureSuccessStatusCode(httpResponse, options); + EnsureSuccessStatusCode(client, httpResponse, options); options.CancellationToken.ThrowIfCancellationRequested(); @@ -629,7 +632,8 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager { var httpResponse = (HttpWebResponse)response; - EnsureSuccessStatusCode(httpResponse, options); + var client = GetHttpClient(GetHostFromUrl(options.Url), options.EnableHttpCompression); + EnsureSuccessStatusCode(client, httpResponse, options); options.CancellationToken.ThrowIfCancellationRequested(); @@ -803,13 +807,20 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager return exception; } - private void EnsureSuccessStatusCode(HttpWebResponse response, HttpRequestOptions options) + private void EnsureSuccessStatusCode(HttpClientInfo client, HttpWebResponse response, HttpRequestOptions options) { var statusCode = response.StatusCode; + var isSuccessful = statusCode >= HttpStatusCode.OK && statusCode <= (HttpStatusCode)299; if (!isSuccessful) { + if ((int) statusCode == 429) + { + client.LastTimeout = DateTime.UtcNow; + } + + if (statusCode == HttpStatusCode.RequestEntityTooLarge) if (options.LogErrorResponseBody) { try diff --git a/MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs b/MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs index ea4a61e25a..e9ef846634 100644 --- a/MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs +++ b/MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs @@ -234,10 +234,10 @@ namespace MediaBrowser.Common.Implementations.IO { if (_supportsAsyncFileStreams && isAsync) { - return new FileStream(path, mode, access, share, 4096, true); + return new FileStream(path, mode, access, share, StreamDefaults.DefaultFileStreamBufferSize, true); } - return new FileStream(path, mode, access, share); + return new FileStream(path, mode, access, share, StreamDefaults.DefaultFileStreamBufferSize); } /// diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs index c2551731fb..cbd1c1ac55 100644 --- a/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs +++ b/MediaBrowser.Common.Implementations/ScheduledTasks/ScheduledTaskWorker.cs @@ -312,7 +312,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks trigger.Triggered -= trigger_Triggered; trigger.Triggered += trigger_Triggered; - trigger.Start(isApplicationStartup); + trigger.Start(LastExecutionResult, isApplicationStartup); } } @@ -340,7 +340,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks await Task.Delay(1000).ConfigureAwait(false); - trigger.Start(false); + trigger.Start(LastExecutionResult, false); } private Task _currentTask; diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs index eb2b46c223..d9c178d8b5 100644 --- a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs +++ b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs @@ -45,9 +45,6 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks // Until we can vary these default triggers per server and MBT, we need something that makes sense for both return new ITaskTrigger[] { - // At startup - new StartupTrigger {DelayMs = 60000}, - // Every so often new IntervalTrigger { Interval = TimeSpan.FromHours(24)} }; diff --git a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs index 6b9bcbfc1a..b2759c52a6 100644 --- a/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs +++ b/MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs @@ -42,9 +42,6 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks // Until we can vary these default triggers per server and MBT, we need something that makes sense for both return new ITaskTrigger[] { - // At startup - new StartupTrigger {DelayMs = 30000}, - // Every so often new IntervalTrigger { Interval = TimeSpan.FromHours(24)} }; diff --git a/MediaBrowser.Common.Implementations/Serialization/JsonSerializer.cs b/MediaBrowser.Common.Implementations/Serialization/JsonSerializer.cs index b49551ea99..f194b334a8 100644 --- a/MediaBrowser.Common.Implementations/Serialization/JsonSerializer.cs +++ b/MediaBrowser.Common.Implementations/Serialization/JsonSerializer.cs @@ -61,7 +61,7 @@ namespace MediaBrowser.Common.Implementations.Serialization private Stream OpenFile(string path) { - return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); + return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 131072); } /// diff --git a/MediaBrowser.Common.Implementations/Updates/InstallationManager.cs b/MediaBrowser.Common.Implementations/Updates/InstallationManager.cs index 23785083b6..5f205d69e7 100644 --- a/MediaBrowser.Common.Implementations/Updates/InstallationManager.cs +++ b/MediaBrowser.Common.Implementations/Updates/InstallationManager.cs @@ -149,10 +149,12 @@ namespace MediaBrowser.Common.Implementations.Updates /// Gets all available packages. /// /// The cancellation token. + /// if set to true [with registration]. /// Type of the package. /// The application version. /// Task{List{PackageInfo}}. public async Task> GetAvailablePackages(CancellationToken cancellationToken, + bool withRegistration = true, PackageType? packageType = null, Version applicationVersion = null) { @@ -163,13 +165,22 @@ namespace MediaBrowser.Common.Implementations.Updates { "systemid", _applicationHost.SystemId } }; - using (var json = await _httpClient.Post(MbAdmin.HttpsUrl + "service/package/retrieveall", data, cancellationToken).ConfigureAwait(false)) + if (withRegistration) { - cancellationToken.ThrowIfCancellationRequested(); + using (var json = await _httpClient.Post(MbAdmin.HttpsUrl + "service/package/retrieveall", data, cancellationToken).ConfigureAwait(false)) + { + cancellationToken.ThrowIfCancellationRequested(); - var packages = _jsonSerializer.DeserializeFromStream>(json).ToList(); + var packages = _jsonSerializer.DeserializeFromStream>(json).ToList(); + + return FilterPackages(packages, packageType, applicationVersion); + } + } + else + { + var packages = await GetAvailablePackagesWithoutRegistrationInfo(cancellationToken).ConfigureAwait(false); - return FilterPackages(packages, packageType, applicationVersion); + return FilterPackages(packages.ToList(), packageType, applicationVersion); } } diff --git a/MediaBrowser.Common/IO/StreamDefaults.cs b/MediaBrowser.Common/IO/StreamDefaults.cs index 0cbf1643fb..450d293d4b 100644 --- a/MediaBrowser.Common/IO/StreamDefaults.cs +++ b/MediaBrowser.Common/IO/StreamDefaults.cs @@ -9,11 +9,11 @@ namespace MediaBrowser.Common.IO /// /// The default copy to buffer size /// - public const int DefaultCopyToBufferSize = 81920; + public const int DefaultCopyToBufferSize = 262144; /// /// The default file stream buffer size /// - public const int DefaultFileStreamBufferSize = 4096; + public const int DefaultFileStreamBufferSize = 262144; } } diff --git a/MediaBrowser.Common/ScheduledTasks/DailyTrigger.cs b/MediaBrowser.Common/ScheduledTasks/DailyTrigger.cs index 2f935607bf..382a412552 100644 --- a/MediaBrowser.Common/ScheduledTasks/DailyTrigger.cs +++ b/MediaBrowser.Common/ScheduledTasks/DailyTrigger.cs @@ -1,6 +1,7 @@ -using System; +using MediaBrowser.Model.Events; +using MediaBrowser.Model.Tasks; +using System; using System.Threading; -using MediaBrowser.Model.Events; namespace MediaBrowser.Common.ScheduledTasks { @@ -32,8 +33,9 @@ namespace MediaBrowser.Common.ScheduledTasks /// /// Stars waiting for the trigger action /// + /// The last result. /// if set to true [is application startup]. - public void Start(bool isApplicationStartup) + public void Start(TaskResult lastResult, bool isApplicationStartup) { DisposeTimer(); diff --git a/MediaBrowser.Common/ScheduledTasks/ITaskTrigger.cs b/MediaBrowser.Common/ScheduledTasks/ITaskTrigger.cs index d30111316a..8c87f8f380 100644 --- a/MediaBrowser.Common/ScheduledTasks/ITaskTrigger.cs +++ b/MediaBrowser.Common/ScheduledTasks/ITaskTrigger.cs @@ -1,5 +1,6 @@ -using System; -using MediaBrowser.Model.Events; +using MediaBrowser.Model.Events; +using MediaBrowser.Model.Tasks; +using System; namespace MediaBrowser.Common.ScheduledTasks { @@ -16,8 +17,9 @@ namespace MediaBrowser.Common.ScheduledTasks /// /// Stars waiting for the trigger action /// + /// The last result. /// if set to true [is application startup]. - void Start(bool isApplicationStartup); + void Start(TaskResult lastResult, bool isApplicationStartup); /// /// Stops waiting for the trigger action diff --git a/MediaBrowser.Common/ScheduledTasks/IntervalTrigger.cs b/MediaBrowser.Common/ScheduledTasks/IntervalTrigger.cs index 455a70d7e5..b615adf816 100644 --- a/MediaBrowser.Common/ScheduledTasks/IntervalTrigger.cs +++ b/MediaBrowser.Common/ScheduledTasks/IntervalTrigger.cs @@ -1,6 +1,7 @@ -using System; +using MediaBrowser.Model.Events; +using MediaBrowser.Model.Tasks; +using System; using System.Threading; -using MediaBrowser.Model.Events; namespace MediaBrowser.Common.ScheduledTasks { @@ -29,15 +30,43 @@ namespace MediaBrowser.Common.ScheduledTasks /// public TaskExecutionOptions TaskOptions { get; set; } + /// + /// Gets or sets the first run delay. + /// + /// The first run delay. + public TimeSpan FirstRunDelay { get; set; } + + public IntervalTrigger() + { + FirstRunDelay = TimeSpan.FromHours(1); + } + /// /// Stars waiting for the trigger action /// + /// The last result. /// if set to true [is application startup]. - public void Start(bool isApplicationStartup) + public void Start(TaskResult lastResult, bool isApplicationStartup) { DisposeTimer(); - Timer = new Timer(state => OnTriggered(), null, Interval, TimeSpan.FromMilliseconds(-1)); + var triggerDate = lastResult != null ? + lastResult.EndTimeUtc.Add(Interval) : + DateTime.UtcNow.Add(FirstRunDelay); + + if (DateTime.UtcNow > triggerDate) + { + if (isApplicationStartup) + { + triggerDate = DateTime.UtcNow.AddMinutes(5); + } + else + { + triggerDate = DateTime.UtcNow.Add(Interval); + } + } + + Timer = new Timer(state => OnTriggered(), null, triggerDate - DateTime.UtcNow, TimeSpan.FromMilliseconds(-1)); } /// diff --git a/MediaBrowser.Common/ScheduledTasks/StartupTrigger.cs b/MediaBrowser.Common/ScheduledTasks/StartupTrigger.cs index a58fa22b9b..1d82dc76a5 100644 --- a/MediaBrowser.Common/ScheduledTasks/StartupTrigger.cs +++ b/MediaBrowser.Common/ScheduledTasks/StartupTrigger.cs @@ -1,6 +1,7 @@ -using System; +using MediaBrowser.Model.Events; +using MediaBrowser.Model.Tasks; +using System; using System.Threading.Tasks; -using MediaBrowser.Model.Events; namespace MediaBrowser.Common.ScheduledTasks { @@ -27,8 +28,9 @@ namespace MediaBrowser.Common.ScheduledTasks /// /// Stars waiting for the trigger action /// + /// The last result. /// if set to true [is application startup]. - public async void Start(bool isApplicationStartup) + public async void Start(TaskResult lastResult, bool isApplicationStartup) { if (isApplicationStartup) { diff --git a/MediaBrowser.Common/ScheduledTasks/SystemEventTrigger.cs b/MediaBrowser.Common/ScheduledTasks/SystemEventTrigger.cs index a40dc6b5cb..eaf4afc758 100644 --- a/MediaBrowser.Common/ScheduledTasks/SystemEventTrigger.cs +++ b/MediaBrowser.Common/ScheduledTasks/SystemEventTrigger.cs @@ -1,8 +1,8 @@ -using MediaBrowser.Model.Tasks; +using MediaBrowser.Model.Events; +using MediaBrowser.Model.Tasks; using Microsoft.Win32; using System; using System.Threading.Tasks; -using MediaBrowser.Model.Events; namespace MediaBrowser.Common.ScheduledTasks { @@ -28,8 +28,9 @@ namespace MediaBrowser.Common.ScheduledTasks /// /// Stars waiting for the trigger action /// + /// The last result. /// if set to true [is application startup]. - public void Start(bool isApplicationStartup) + public void Start(TaskResult lastResult, bool isApplicationStartup) { switch (SystemEvent) { diff --git a/MediaBrowser.Common/ScheduledTasks/WeeklyTrigger.cs b/MediaBrowser.Common/ScheduledTasks/WeeklyTrigger.cs index a3818f83fd..2e38264b24 100644 --- a/MediaBrowser.Common/ScheduledTasks/WeeklyTrigger.cs +++ b/MediaBrowser.Common/ScheduledTasks/WeeklyTrigger.cs @@ -1,6 +1,7 @@ using System; using System.Threading; using MediaBrowser.Model.Events; +using MediaBrowser.Model.Tasks; namespace MediaBrowser.Common.ScheduledTasks { @@ -38,8 +39,9 @@ namespace MediaBrowser.Common.ScheduledTasks /// /// Stars waiting for the trigger action /// + /// The last result. /// if set to true [is application startup]. - public void Start(bool isApplicationStartup) + public void Start(TaskResult lastResult, bool isApplicationStartup) { DisposeTimer(); diff --git a/MediaBrowser.Common/Updates/IInstallationManager.cs b/MediaBrowser.Common/Updates/IInstallationManager.cs index 592613c54c..7d721da6f0 100644 --- a/MediaBrowser.Common/Updates/IInstallationManager.cs +++ b/MediaBrowser.Common/Updates/IInstallationManager.cs @@ -45,10 +45,12 @@ namespace MediaBrowser.Common.Updates /// Gets all available packages. /// /// The cancellation token. + /// if set to true [with registration]. /// Type of the package. /// The application version. /// Task{List{PackageInfo}}. Task> GetAvailablePackages(CancellationToken cancellationToken, + bool withRegistration = true, PackageType? packageType = null, Version applicationVersion = null); diff --git a/MediaBrowser.Controller/Channels/IChannelManager.cs b/MediaBrowser.Controller/Channels/IChannelManager.cs index f5c4ab3733..8d3e0f5962 100644 --- a/MediaBrowser.Controller/Channels/IChannelManager.cs +++ b/MediaBrowser.Controller/Channels/IChannelManager.cs @@ -121,10 +121,9 @@ namespace MediaBrowser.Controller.Channels /// /// Gets the channel folder. /// - /// The user identifier. /// The cancellation token. /// BaseItemDto. - Task GetInternalChannelFolder(string userId, CancellationToken cancellationToken); + Task GetInternalChannelFolder(CancellationToken cancellationToken); /// /// Gets the channel folder. diff --git a/MediaBrowser.Controller/Dlna/IDeviceDiscovery.cs b/MediaBrowser.Controller/Dlna/IDeviceDiscovery.cs new file mode 100644 index 0000000000..e8083b3632 --- /dev/null +++ b/MediaBrowser.Controller/Dlna/IDeviceDiscovery.cs @@ -0,0 +1,10 @@ +using System; + +namespace MediaBrowser.Controller.Dlna +{ + public interface IDeviceDiscovery + { + event EventHandler DeviceDiscovered; + event EventHandler DeviceLeft; + } +} diff --git a/MediaBrowser.Controller/Dto/IDtoService.cs b/MediaBrowser.Controller/Dto/IDtoService.cs index 5ec8f274be..e4ab291025 100644 --- a/MediaBrowser.Controller/Dto/IDtoService.cs +++ b/MediaBrowser.Controller/Dto/IDtoService.cs @@ -81,13 +81,11 @@ namespace MediaBrowser.Controller.Dto /// /// Gets the item by name dto. /// - /// /// The item. /// The options. /// The tagged items. /// The user. /// BaseItemDto. - BaseItemDto GetItemByNameDto(T item, DtoOptions options, List taggedItems, User user = null) - where T : BaseItem, IItemByName; + BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List taggedItems, User user = null); } } diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs index 8a77d76165..807beee523 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs @@ -54,6 +54,12 @@ namespace MediaBrowser.Controller.Entities.Audio get { return AlbumArtists.FirstOrDefault(); } } + [IgnoreDataMember] + public override bool SupportsPeople + { + get { return false; } + } + public List AlbumArtists { get; set; } /// diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index d9dbf265fc..594b5ca93c 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -142,7 +142,7 @@ namespace MediaBrowser.Controller.Entities public virtual string Path { get; set; } [IgnoreDataMember] - protected internal bool IsOffline { get; set; } + public bool IsOffline { get; set; } /// /// Returns the folder containing the item. @@ -419,6 +419,10 @@ namespace MediaBrowser.Controller.Entities return _sortName ?? (_sortName = CreateSortName()); } + set + { + _sortName = value; + } } public string GetInternalMetadataPath() @@ -485,6 +489,7 @@ namespace MediaBrowser.Controller.Entities /// Gets or sets the parent. /// /// The parent. + [IgnoreDataMember] public Folder Parent { get @@ -1115,6 +1120,23 @@ namespace MediaBrowser.Controller.Entities return value.Value <= maxAllowedRating.Value; } + public int? GetParentalRatingValue() + { + var rating = CustomRatingForComparison; + + if (string.IsNullOrWhiteSpace(rating)) + { + rating = OfficialRatingForComparison; + } + + if (string.IsNullOrWhiteSpace(rating)) + { + return null; + } + + return LocalizationManager.GetRatingLevel(rating); + } + private bool IsVisibleViaTags(User user) { var hasTags = this as IHasTags; diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs index 3a610be641..8821f35c8b 100644 --- a/MediaBrowser.Controller/Entities/CollectionFolder.cs +++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs @@ -35,6 +35,15 @@ namespace MediaBrowser.Controller.Entities } } + [IgnoreDataMember] + protected override bool SupportsShortcutChildren + { + get + { + return true; + } + } + public override bool CanDelete() { return false; diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 22efb09e15..c3ac77328d 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -1,7 +1,6 @@ using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Localization; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; @@ -14,7 +13,6 @@ using System.Linq; using System.Runtime.Serialization; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Model.Users; namespace MediaBrowser.Controller.Entities { @@ -50,7 +48,7 @@ namespace MediaBrowser.Controller.Entities [IgnoreDataMember] public virtual bool IsPreSorted { - get { return false; } + get { return ConfigurationManager.Configuration.EnableWindowsShortcuts; } } /// @@ -122,7 +120,7 @@ namespace MediaBrowser.Controller.Entities [IgnoreDataMember] protected virtual bool SupportsShortcutChildren { - get { return true; } + get { return false; } } /// @@ -176,7 +174,7 @@ namespace MediaBrowser.Controller.Entities protected void AddChildInternal(BaseItem child) { var actualChildren = ActualChildren; - + lock (_childrenSyncLock) { var newChildren = actualChildren.ToList(); @@ -1070,7 +1068,7 @@ namespace MediaBrowser.Controller.Entities { var changesFound = false; - if (SupportsShortcutChildren && LocationType == LocationType.FileSystem) + if (LocationType == LocationType.FileSystem) { if (RefreshLinkedChildren(fileSystemChildren)) { @@ -1092,37 +1090,43 @@ namespace MediaBrowser.Controller.Entities var currentManualLinks = LinkedChildren.Where(i => i.Type == LinkedChildType.Manual).ToList(); var currentShortcutLinks = LinkedChildren.Where(i => i.Type == LinkedChildType.Shortcut).ToList(); - var newShortcutLinks = fileSystemChildren - .Where(i => (i.Attributes & FileAttributes.Directory) != FileAttributes.Directory && FileSystem.IsShortcut(i.FullName)) - .Select(i => - { - try + List newShortcutLinks; + + if (SupportsShortcutChildren) + { + newShortcutLinks = fileSystemChildren + .Where(i => (i.Attributes & FileAttributes.Directory) != FileAttributes.Directory && FileSystem.IsShortcut(i.FullName)) + .Select(i => { - Logger.Debug("Found shortcut at {0}", i.FullName); + try + { + Logger.Debug("Found shortcut at {0}", i.FullName); - var resolvedPath = FileSystem.ResolveShortcut(i.FullName); + var resolvedPath = FileSystem.ResolveShortcut(i.FullName); - if (!string.IsNullOrEmpty(resolvedPath)) - { - return new LinkedChild + if (!string.IsNullOrEmpty(resolvedPath)) { - Path = resolvedPath, - Type = LinkedChildType.Shortcut - }; - } + return new LinkedChild + { + Path = resolvedPath, + Type = LinkedChildType.Shortcut + }; + } - Logger.Error("Error resolving shortcut {0}", i.FullName); + Logger.Error("Error resolving shortcut {0}", i.FullName); - return null; - } - catch (IOException ex) - { - Logger.ErrorException("Error resolving shortcut {0}", ex, i.FullName); - return null; - } - }) - .Where(i => i != null) - .ToList(); + return null; + } + catch (IOException ex) + { + Logger.ErrorException("Error resolving shortcut {0}", ex, i.FullName); + return null; + } + }) + .Where(i => i != null) + .ToList(); + } + else { newShortcutLinks = new List(); } if (!newShortcutLinks.SequenceEqual(currentShortcutLinks, new LinkedChildComparer())) { diff --git a/MediaBrowser.Controller/Entities/ICollectionFolder.cs b/MediaBrowser.Controller/Entities/ICollectionFolder.cs index f46d7ed6f3..b55ca0a179 100644 --- a/MediaBrowser.Controller/Entities/ICollectionFolder.cs +++ b/MediaBrowser.Controller/Entities/ICollectionFolder.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; namespace MediaBrowser.Controller.Entities { @@ -14,4 +15,17 @@ namespace MediaBrowser.Controller.Entities Guid Id { get; } IEnumerable PhysicalLocations { get; } } + + public static class CollectionFolderExtensions + { + public static string GetViewType(this ICollectionFolder folder, User user) + { + if (user.Configuration.PlainFolderViews.Contains(folder.Id.ToString("N"), StringComparer.OrdinalIgnoreCase)) + { + return null; + } + + return folder.CollectionType; + } + } } diff --git a/MediaBrowser.Controller/Entities/IHasProgramAttributes.cs b/MediaBrowser.Controller/Entities/IHasProgramAttributes.cs index 391c8f7a7e..9938a44894 100644 --- a/MediaBrowser.Controller/Entities/IHasProgramAttributes.cs +++ b/MediaBrowser.Controller/Entities/IHasProgramAttributes.cs @@ -11,6 +11,7 @@ namespace MediaBrowser.Controller.Entities bool IsKids { get; set; } bool IsRepeat { get; set; } bool? IsHD { get; set; } + bool IsSeries { get; set; } bool IsLive { get; set; } bool IsPremiere { get; set; } ProgramAudio? Audio { get; set; } diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs index c5e60a73d9..0af4972f74 100644 --- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs @@ -1,6 +1,6 @@ -using System.Collections.Generic; -using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Entities; using System; +using System.Collections.Generic; namespace MediaBrowser.Controller.Entities { @@ -40,6 +40,7 @@ namespace MediaBrowser.Controller.Entities public string NameStartsWithOrGreater { get; set; } public string NameStartsWith { get; set; } public string NameLessThan { get; set; } + public string NameContains { get; set; } public string Person { get; set; } public string[] PersonIds { get; set; } @@ -93,7 +94,11 @@ namespace MediaBrowser.Controller.Entities public string[] ChannelIds { get; set; } internal List ItemIdsFromPersonFilters { get; set; } + public int? MaxParentalRating { get; set; } + public bool? IsCurrentSchema { get; set; } + public bool? HasDeadParentId { get; set; } + public InternalItemsQuery() { Tags = new string[] { }; diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs index 02e9d4cf9e..9317f688fe 100644 --- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs +++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs @@ -74,6 +74,15 @@ namespace MediaBrowser.Controller.Entities.Movies } } + [IgnoreDataMember] + protected override bool SupportsShortcutChildren + { + get + { + return true; + } + } + public override bool IsAuthorizedToDelete(User user) { return true; diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs index fc07f67788..083ec0cb4c 100644 --- a/MediaBrowser.Controller/Entities/Movies/Movie.cs +++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs @@ -100,16 +100,23 @@ namespace MediaBrowser.Controller.Entities.Movies /// System.String. protected override string CreateUserDataKey() { - var key = this.GetProviderId(MetadataProviders.Tmdb); + var key = GetMovieUserDataKey(this); if (string.IsNullOrWhiteSpace(key)) { - key = this.GetProviderId(MetadataProviders.Imdb); + key = base.CreateUserDataKey(); } + return key; + } + + public static string GetMovieUserDataKey(BaseItem movie) + { + var key = movie.GetProviderId(MetadataProviders.Tmdb); + if (string.IsNullOrWhiteSpace(key)) { - key = base.CreateUserDataKey(); + key = movie.GetProviderId(MetadataProviders.Imdb); } return key; diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs index 8f5b8f6cff..5163c3de4a 100644 --- a/MediaBrowser.Controller/Entities/TV/Episode.cs +++ b/MediaBrowser.Controller/Entities/TV/Episode.cs @@ -150,7 +150,7 @@ namespace MediaBrowser.Controller.Entities.TV { var series = Series; - if (ParentIndexNumber.HasValue) + if (series != null && ParentIndexNumber.HasValue) { var findNumber = ParentIndexNumber.Value; diff --git a/MediaBrowser.Controller/Entities/UserRootFolder.cs b/MediaBrowser.Controller/Entities/UserRootFolder.cs index b065ae1715..a78beb645d 100644 --- a/MediaBrowser.Controller/Entities/UserRootFolder.cs +++ b/MediaBrowser.Controller/Entities/UserRootFolder.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Providers; +using System.Runtime.Serialization; +using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Library; using MediaBrowser.Model.Querying; @@ -36,6 +37,16 @@ namespace MediaBrowser.Controller.Entities return PostFilterAndSort(result.Where(filter), query); } + [IgnoreDataMember] + protected override bool SupportsShortcutChildren + { + get + { + return true; + } + } + + [IgnoreDataMember] public override bool IsPreSorted { get diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs index dad6de01a8..488e54cc3c 100644 --- a/MediaBrowser.Controller/Entities/UserView.cs +++ b/MediaBrowser.Controller/Entities/UserView.cs @@ -1,10 +1,10 @@ -using System.Runtime.Serialization; -using MediaBrowser.Controller.Playlists; +using MediaBrowser.Controller.Playlists; using MediaBrowser.Controller.TV; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Querying; using System; using System.Collections.Generic; +using System.Runtime.Serialization; using System.Threading.Tasks; namespace MediaBrowser.Controller.Entities @@ -15,7 +15,7 @@ namespace MediaBrowser.Controller.Entities public Guid ParentId { get; set; } public Guid? UserId { get; set; } - + public static ITVSeriesManager TVSeriesManager; public static IPlaylistManager PlaylistManager; diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index b2d3592776..cee5dadd25 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -66,7 +66,7 @@ namespace MediaBrowser.Controller.Entities { var result = await _channelManager.GetChannelsInternal(new ChannelQuery { - UserId = user.Id.ToString("N"), + UserId = user == null ? null : user.Id.ToString("N"), Limit = query.Limit, StartIndex = query.StartIndex @@ -264,10 +264,7 @@ namespace MediaBrowser.Controller.Entities private async Task> FindPlaylists(Folder parent, User user, InternalItemsQuery query) { - var collectionFolders = user.RootFolder.GetChildren(user, true).Select(i => i.Id).ToList(); - - var list = _playlistManager.GetPlaylists(user.Id.ToString("N")) - .Where(i => i.GetChildren(user, true).Any(media => _libraryManager.GetCollectionFolders(media).Select(c => c.Id).Any(collectionFolders.Contains))); + var list = _playlistManager.GetPlaylists(user.Id.ToString("N")); return GetResult(list, parent, query); } @@ -288,14 +285,14 @@ namespace MediaBrowser.Controller.Entities var list = new List(); - list.Add(await GetUserView(SpecialFolder.MusicLatest, user, "0", parent).ConfigureAwait(false)); - list.Add(await GetUserView(SpecialFolder.MusicPlaylists, user, "1", parent).ConfigureAwait(false)); - list.Add(await GetUserView(SpecialFolder.MusicAlbums, user, "2", parent).ConfigureAwait(false)); - list.Add(await GetUserView(SpecialFolder.MusicAlbumArtists, user, "3", parent).ConfigureAwait(false)); + list.Add(await GetUserView(SpecialFolder.MusicLatest, "0", parent).ConfigureAwait(false)); + list.Add(await GetUserView(SpecialFolder.MusicPlaylists, "1", parent).ConfigureAwait(false)); + list.Add(await GetUserView(SpecialFolder.MusicAlbums, "2", parent).ConfigureAwait(false)); + list.Add(await GetUserView(SpecialFolder.MusicAlbumArtists, "3", parent).ConfigureAwait(false)); //list.Add(await GetUserView(SpecialFolder.MusicArtists, user, "4", parent).ConfigureAwait(false)); - list.Add(await GetUserView(SpecialFolder.MusicSongs, user, "5", parent).ConfigureAwait(false)); - list.Add(await GetUserView(SpecialFolder.MusicGenres, user, "6", parent).ConfigureAwait(false)); - list.Add(await GetUserView(SpecialFolder.MusicFavorites, user, "7", parent).ConfigureAwait(false)); + list.Add(await GetUserView(SpecialFolder.MusicSongs, "5", parent).ConfigureAwait(false)); + list.Add(await GetUserView(SpecialFolder.MusicGenres, "6", parent).ConfigureAwait(false)); + list.Add(await GetUserView(SpecialFolder.MusicFavorites, "7", parent).ConfigureAwait(false)); return GetResult(list, parent, query); } @@ -304,9 +301,9 @@ namespace MediaBrowser.Controller.Entities { var list = new List(); - list.Add(await GetUserView(SpecialFolder.MusicFavoriteAlbums, user, "0", parent).ConfigureAwait(false)); - list.Add(await GetUserView(SpecialFolder.MusicFavoriteArtists, user, "1", parent).ConfigureAwait(false)); - list.Add(await GetUserView(SpecialFolder.MusicFavoriteSongs, user, "2", parent).ConfigureAwait(false)); + list.Add(await GetUserView(SpecialFolder.MusicFavoriteAlbums, "0", parent).ConfigureAwait(false)); + list.Add(await GetUserView(SpecialFolder.MusicFavoriteArtists, "1", parent).ConfigureAwait(false)); + list.Add(await GetUserView(SpecialFolder.MusicFavoriteSongs, "2", parent).ConfigureAwait(false)); return GetResult(list, parent, query); } @@ -332,7 +329,7 @@ namespace MediaBrowser.Controller.Entities }) .Where(i => i != null) - .Select(i => GetUserView(i.Name, SpecialFolder.MusicGenre, user, i.SortName, parent)); + .Select(i => GetUserView(i.Name, SpecialFolder.MusicGenre, i.SortName, parent)); var genres = await Task.WhenAll(tasks).ConfigureAwait(false); @@ -344,94 +341,42 @@ namespace MediaBrowser.Controller.Entities var items = GetRecursiveChildren(queryParent, user, new[] { CollectionType.Music, CollectionType.MusicVideos }) .Where(i => !i.IsFolder) .Where(i => i.Genres.Contains(displayParent.Name, StringComparer.OrdinalIgnoreCase)) - .OfType() - .SelectMany(i => i.AlbumArtists) - .DistinctNames() - .Select(i => - { - try - { - return _libraryManager.GetArtist(i); - } - catch - { - // Already logged at lower levels - return null; - } - }) - .Where(i => i != null); + .OfType(); - return GetResult(items, queryParent, query); + var artists = _libraryManager.GetAlbumArtists(items); + + return GetResult(artists, queryParent, query); } private QueryResult GetMusicAlbumArtists(Folder parent, User user, InternalItemsQuery query) { - var artists = GetRecursiveChildren(parent, user, new[] { CollectionType.Music, CollectionType.MusicVideos }) + var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Music, CollectionType.MusicVideos }) .Where(i => !i.IsFolder) - .OfType() - .SelectMany(i => i.AlbumArtists) - .DistinctNames() - .Select(i => - { - try - { - return _libraryManager.GetArtist(i); - } - catch - { - // Already logged at lower levels - return null; - } - }) - .Where(i => i != null); + .OfType(); + + var artists = _libraryManager.GetAlbumArtists(items); return GetResult(artists, parent, query); } private QueryResult GetMusicArtists(Folder parent, User user, InternalItemsQuery query) { - var artists = GetRecursiveChildren(parent, user, new[] { CollectionType.Music, CollectionType.MusicVideos }) + var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Music, CollectionType.MusicVideos }) .Where(i => !i.IsFolder) - .OfType() - .SelectMany(i => i.Artists) - .DistinctNames() - .Select(i => - { - try - { - return _libraryManager.GetArtist(i); - } - catch - { - // Already logged at lower levels - return null; - } - }) - .Where(i => i != null); + .OfType(); + + var artists = _libraryManager.GetArtists(items); return GetResult(artists, parent, query); } private QueryResult GetFavoriteArtists(Folder parent, User user, InternalItemsQuery query) { - var artists = GetRecursiveChildren(parent, user, new[] { CollectionType.Music, CollectionType.MusicVideos }) + var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Music, CollectionType.MusicVideos }) .Where(i => !i.IsFolder) - .OfType() - .SelectMany(i => i.AlbumArtists) - .DistinctNames() - .Select(i => - { - try - { - return _libraryManager.GetArtist(i); - } - catch - { - // Already logged at lower levels - return null; - } - }) - .Where(i => i != null && _userDataManager.GetUserData(user.Id, i.GetUserDataKey()).IsFavorite); + .OfType(); + + var artists = _libraryManager.GetAlbumArtists(items).Where(i => _userDataManager.GetUserData(user.Id, i.GetUserDataKey()).IsFavorite); return GetResult(artists, parent, query); } @@ -498,12 +443,12 @@ namespace MediaBrowser.Controller.Entities var list = new List(); - list.Add(await GetUserView(SpecialFolder.MovieResume, user, "0", parent).ConfigureAwait(false)); - list.Add(await GetUserView(SpecialFolder.MovieLatest, user, "1", parent).ConfigureAwait(false)); - list.Add(await GetUserView(SpecialFolder.MovieMovies, user, "2", parent).ConfigureAwait(false)); - list.Add(await GetUserView(SpecialFolder.MovieCollections, user, "3", parent).ConfigureAwait(false)); - list.Add(await GetUserView(SpecialFolder.MovieFavorites, user, "4", parent).ConfigureAwait(false)); - list.Add(await GetUserView(SpecialFolder.MovieGenres, user, "5", parent).ConfigureAwait(false)); + list.Add(await GetUserView(SpecialFolder.MovieResume, "0", parent).ConfigureAwait(false)); + list.Add(await GetUserView(SpecialFolder.MovieLatest, "1", parent).ConfigureAwait(false)); + list.Add(await GetUserView(SpecialFolder.MovieMovies, "2", parent).ConfigureAwait(false)); + list.Add(await GetUserView(SpecialFolder.MovieCollections, "3", parent).ConfigureAwait(false)); + list.Add(await GetUserView(SpecialFolder.MovieFavorites, "4", parent).ConfigureAwait(false)); + list.Add(await GetUserView(SpecialFolder.MovieGenres, "5", parent).ConfigureAwait(false)); return GetResult(list, parent, query); } @@ -609,7 +554,7 @@ namespace MediaBrowser.Controller.Entities }) .Where(i => i != null) - .Select(i => GetUserView(i.Name, SpecialFolder.MovieGenre, user, i.SortName, parent)); + .Select(i => GetUserView(i.Name, SpecialFolder.MovieGenre, i.SortName, parent)); var genres = await Task.WhenAll(tasks).ConfigureAwait(false); @@ -671,13 +616,13 @@ namespace MediaBrowser.Controller.Entities var list = new List(); - list.Add(await GetUserView(SpecialFolder.TvResume, user, "0", parent).ConfigureAwait(false)); - list.Add(await GetUserView(SpecialFolder.TvNextUp, user, "1", parent).ConfigureAwait(false)); - list.Add(await GetUserView(SpecialFolder.TvLatest, user, "2", parent).ConfigureAwait(false)); - list.Add(await GetUserView(SpecialFolder.TvShowSeries, user, "3", parent).ConfigureAwait(false)); - list.Add(await GetUserView(SpecialFolder.TvFavoriteSeries, user, "4", parent).ConfigureAwait(false)); - list.Add(await GetUserView(SpecialFolder.TvFavoriteEpisodes, user, "5", parent).ConfigureAwait(false)); - list.Add(await GetUserView(SpecialFolder.TvGenres, user, "6", parent).ConfigureAwait(false)); + list.Add(await GetUserView(SpecialFolder.TvResume, "0", parent).ConfigureAwait(false)); + list.Add(await GetUserView(SpecialFolder.TvNextUp, "1", parent).ConfigureAwait(false)); + list.Add(await GetUserView(SpecialFolder.TvLatest, "2", parent).ConfigureAwait(false)); + list.Add(await GetUserView(SpecialFolder.TvShowSeries, "3", parent).ConfigureAwait(false)); + list.Add(await GetUserView(SpecialFolder.TvFavoriteSeries, "4", parent).ConfigureAwait(false)); + list.Add(await GetUserView(SpecialFolder.TvFavoriteEpisodes, "5", parent).ConfigureAwait(false)); + list.Add(await GetUserView(SpecialFolder.TvGenres, "6", parent).ConfigureAwait(false)); return GetResult(list, parent, query); } @@ -692,11 +637,11 @@ namespace MediaBrowser.Controller.Entities var list = new List(); - list.Add(await GetUserView(SpecialFolder.LatestGames, user, "0", parent).ConfigureAwait(false)); - list.Add(await GetUserView(SpecialFolder.RecentlyPlayedGames, user, "1", parent).ConfigureAwait(false)); - list.Add(await GetUserView(SpecialFolder.GameFavorites, user, "2", parent).ConfigureAwait(false)); - list.Add(await GetUserView(SpecialFolder.GameSystems, user, "3", parent).ConfigureAwait(false)); - list.Add(await GetUserView(SpecialFolder.GameGenres, user, "4", parent).ConfigureAwait(false)); + list.Add(await GetUserView(SpecialFolder.LatestGames, "0", parent).ConfigureAwait(false)); + list.Add(await GetUserView(SpecialFolder.RecentlyPlayedGames, "1", parent).ConfigureAwait(false)); + list.Add(await GetUserView(SpecialFolder.GameFavorites, "2", parent).ConfigureAwait(false)); + list.Add(await GetUserView(SpecialFolder.GameSystems, "3", parent).ConfigureAwait(false)); + list.Add(await GetUserView(SpecialFolder.GameGenres, "4", parent).ConfigureAwait(false)); return GetResult(list, parent, query); } @@ -794,7 +739,7 @@ namespace MediaBrowser.Controller.Entities }) .Where(i => i != null) - .Select(i => GetUserView(i.Name, SpecialFolder.TvGenre, user, i.SortName, parent)); + .Select(i => GetUserView(i.Name, SpecialFolder.TvGenre, i.SortName, parent)); var genres = await Task.WhenAll(tasks).ConfigureAwait(false); @@ -846,7 +791,7 @@ namespace MediaBrowser.Controller.Entities }) .Where(i => i != null) - .Select(i => GetUserView(i.Name, SpecialFolder.GameGenre, user, i.SortName, parent)); + .Select(i => GetUserView(i.Name, SpecialFolder.GameGenre, i.SortName, parent)); var genres = await Task.WhenAll(tasks).ConfigureAwait(false); @@ -1926,26 +1871,20 @@ namespace MediaBrowser.Controller.Entities var list = new List(); //list.Add(await GetUserSubView(SpecialFolder.LiveTvNowPlaying, user, "0", parent).ConfigureAwait(false)); - list.Add(await GetUserView(SpecialFolder.LiveTvChannels, user, string.Empty, user.RootFolder).ConfigureAwait(false)); - list.Add(await GetUserView(SpecialFolder.LiveTvRecordingGroups, user, string.Empty, user.RootFolder).ConfigureAwait(false)); + list.Add(await GetUserView(SpecialFolder.LiveTvChannels, string.Empty, user.RootFolder).ConfigureAwait(false)); + list.Add(await GetUserView(SpecialFolder.LiveTvRecordingGroups, string.Empty, user.RootFolder).ConfigureAwait(false)); return GetResult(list, queryParent, query); } - private async Task GetUserView(string name, string type, User user, string sortName, BaseItem parent) + private Task GetUserView(string name, string type, string sortName, BaseItem parent) { - var view = await _userViewManager.GetUserSubView(name, parent.Id.ToString("N"), type, user, sortName, CancellationToken.None) - .ConfigureAwait(false); - - return view; + return _userViewManager.GetUserSubView(name, parent.Id.ToString("N"), type, sortName, CancellationToken.None); } - private async Task GetUserView(string type, User user, string sortName, BaseItem parent) + private Task GetUserView(string type, string sortName, BaseItem parent) { - var view = await _userViewManager.GetUserSubView(parent.Id.ToString("N"), type, user, sortName, CancellationToken.None) - .ConfigureAwait(false); - - return view; + return _userViewManager.GetUserSubView(parent.Id.ToString("N"), type, sortName, CancellationToken.None); } public static bool IsYearMismatched(BaseItem item, ILibraryManager libraryManager) diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index f0bfaaf667..9331ca7598 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -5,12 +5,12 @@ using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Resolvers; using MediaBrowser.Controller.Sorting; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Querying; using System; using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; -using MediaBrowser.Model.Querying; namespace MediaBrowser.Controller.Library { @@ -61,7 +61,18 @@ namespace MediaBrowser.Controller.Library /// The name. /// Task{Artist}. MusicArtist GetArtist(string name); - + /// + /// Gets the album artists. + /// + /// The items. + /// IEnumerable<MusicArtist>. + IEnumerable GetAlbumArtists(IEnumerable items); + /// + /// Gets the artists. + /// + /// The items. + /// IEnumerable<MusicArtist>. + IEnumerable GetArtists(IEnumerable items); /// /// Gets a Studio /// @@ -340,7 +351,37 @@ namespace MediaBrowser.Controller.Library Task GetNamedView(User user, string name, string viewType, - string sortName, + string sortName, + CancellationToken cancellationToken); + + /// + /// Gets the named view. + /// + /// The name. + /// Type of the view. + /// Name of the sort. + /// The cancellation token. + /// Task<UserView>. + Task GetNamedView(string name, + string viewType, + string sortName, + CancellationToken cancellationToken); + + /// + /// Gets the named view. + /// + /// The name. + /// The parent identifier. + /// Type of the view. + /// Name of the sort. + /// The unique identifier. + /// The cancellation token. + /// Task<UserView>. + Task GetNamedView(string name, + string parentId, + string viewType, + string sortName, + string uniqueId, CancellationToken cancellationToken); /// @@ -461,5 +502,12 @@ namespace MediaBrowser.Controller.Library /// The query. /// List<System.String>. List GetPeopleNames(InternalPeopleQuery query); + + /// + /// Queries the items. + /// + /// The query. + /// QueryResult<BaseItem>. + QueryResult QueryItems(InternalItemsQuery query); } } \ No newline at end of file diff --git a/MediaBrowser.Controller/Library/IMetadataFileSaver.cs b/MediaBrowser.Controller/Library/IMetadataFileSaver.cs index 0883da48f8..e09e583024 100644 --- a/MediaBrowser.Controller/Library/IMetadataFileSaver.cs +++ b/MediaBrowser.Controller/Library/IMetadataFileSaver.cs @@ -11,4 +11,9 @@ namespace MediaBrowser.Controller.Library /// System.String. string GetSavePath(IHasMetadata item); } + + public interface IConfigurableProvider + { + bool IsEnabled { get; } + } } \ No newline at end of file diff --git a/MediaBrowser.Controller/Library/IMusicManager.cs b/MediaBrowser.Controller/Library/IMusicManager.cs index 0ce0687cc1..9baf8b6f12 100644 --- a/MediaBrowser.Controller/Library/IMusicManager.cs +++ b/MediaBrowser.Controller/Library/IMusicManager.cs @@ -16,10 +16,10 @@ namespace MediaBrowser.Controller.Library /// /// Gets the instant mix from artist. /// - /// The name. + /// The artist. /// The user. /// IEnumerable{Audio}. - IEnumerable /// null if [has image] contains no value, true if [has image]; otherwise, false. public bool? HasImage { get; set; } - + /// + /// Gets or sets a value indicating whether this instance is favorite. + /// + /// null if [is favorite] contains no value, true if [is favorite]; otherwise, false. + public bool? IsFavorite { get; set; } } } diff --git a/MediaBrowser.Controller/LiveTv/IHasRegistrationInfo.cs b/MediaBrowser.Controller/LiveTv/IHasRegistrationInfo.cs new file mode 100644 index 0000000000..3626c18e54 --- /dev/null +++ b/MediaBrowser.Controller/LiveTv/IHasRegistrationInfo.cs @@ -0,0 +1,15 @@ +using MediaBrowser.Model.Entities; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.LiveTv +{ + public interface IHasRegistrationInfo + { + /// + /// Gets the registration information. + /// + /// The feature. + /// Task<MBRegistrationRecord>. + Task GetRegistrationInfo(string feature); + } +} diff --git a/MediaBrowser.Controller/LiveTv/IListingsProvider.cs b/MediaBrowser.Controller/LiveTv/IListingsProvider.cs new file mode 100644 index 0000000000..e60183bd93 --- /dev/null +++ b/MediaBrowser.Controller/LiveTv/IListingsProvider.cs @@ -0,0 +1,19 @@ +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.LiveTv; +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.LiveTv +{ + public interface IListingsProvider + { + string Name { get; } + string Type { get; } + Task> GetProgramsAsync(ListingsProviderInfo info, string channelNumber, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken); + Task AddMetadata(ListingsProviderInfo info, List channels, CancellationToken cancellationToken); + Task Validate(ListingsProviderInfo info, bool validateLogin, bool validateListings); + Task> GetLineups(ListingsProviderInfo info, string country, string location); + } +} diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index 3aa1f66efb..1458c1bc2e 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -2,6 +2,7 @@ using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.LiveTv; using MediaBrowser.Model.Querying; using System.Collections.Generic; @@ -56,20 +57,23 @@ namespace MediaBrowser.Controller.LiveTv /// The identifier. /// Task. Task CancelSeriesTimer(string id); - + /// /// Adds the parts. /// /// The services. - void AddParts(IEnumerable services); + /// The tuner hosts. + /// The listing providers. + void AddParts(IEnumerable services, IEnumerable tunerHosts, IEnumerable listingProviders); /// /// Gets the channels. /// /// The query. + /// The options. /// The cancellation token. /// IEnumerable{Channel}. - Task> GetChannels(LiveTvChannelQuery query, CancellationToken cancellationToken); + Task> GetChannels(LiveTvChannelQuery query, DtoOptions options, CancellationToken cancellationToken); /// /// Gets the recording. @@ -171,14 +175,15 @@ namespace MediaBrowser.Controller.LiveTv /// The user. /// Task{ProgramInfoDto}. Task GetProgram(string id, CancellationToken cancellationToken, User user = null); - + /// /// Gets the programs. /// /// The query. + /// The options. /// The cancellation token. /// IEnumerable{ProgramInfo}. - Task> GetPrograms(ProgramQuery query, CancellationToken cancellationToken); + Task> GetPrograms(ProgramQuery query, DtoOptions options, CancellationToken cancellationToken); /// /// Updates the timer. @@ -238,10 +243,10 @@ namespace MediaBrowser.Controller.LiveTv /// Gets the recommended programs. /// /// The query. + /// The options. /// The cancellation token. /// Task{QueryResult{ProgramInfoDto}}. - Task> GetRecommendedPrograms(RecommendedProgramQuery query, - CancellationToken cancellationToken); + Task> GetRecommendedPrograms(RecommendedProgramQuery query, DtoOptions options, CancellationToken cancellationToken); /// /// Gets the recommended programs internal. @@ -249,8 +254,7 @@ namespace MediaBrowser.Controller.LiveTv /// The query. /// The cancellation token. /// Task<QueryResult<LiveTvProgram>>. - Task> GetRecommendedProgramsInternal(RecommendedProgramQuery query, - CancellationToken cancellationToken); + Task> GetRecommendedProgramsInternal(RecommendedProgramQuery query, CancellationToken cancellationToken); /// /// Gets the live tv information. @@ -270,10 +274,9 @@ namespace MediaBrowser.Controller.LiveTv /// /// Gets the live tv folder. /// - /// The user identifier. /// The cancellation token. /// BaseItemDto. - Task GetInternalLiveTvFolder(string userId, CancellationToken cancellationToken); + Task GetInternalLiveTvFolder(CancellationToken cancellationToken); /// /// Gets the live tv folder. @@ -337,5 +340,46 @@ namespace MediaBrowser.Controller.LiveTv /// The dto. /// The user. void AddInfoToProgramDto(BaseItem item, BaseItemDto dto, User user = null); + /// + /// Saves the tuner host. + /// + /// The information. + /// Task. + Task SaveTunerHost(TunerHostInfo info); + /// + /// Saves the listing provider. + /// + /// The information. + /// if set to true [validate login]. + /// if set to true [validate listings]. + /// Task. + Task SaveListingProvider(ListingsProviderInfo info, bool validateLogin, bool validateListings); + /// + /// Gets the lineups. + /// + /// Type of the provider. + /// The provider identifier. + /// The country. + /// The location. + /// Task<List<NameIdPair>>. + Task> GetLineups(string providerType, string providerId, string country, string location); + + /// + /// Gets the registration information. + /// + /// The channel identifier. + /// The program identifier. + /// The feature. + /// Task<MBRegistrationRecord>. + Task GetRegistrationInfo(string channelId, string programId, string feature); + + /// + /// Adds the channel information. + /// + /// The dto. + /// The channel. + /// The options. + /// The user. + void AddChannelInfo(BaseItemDto dto, LiveTvChannel channel, DtoOptions options, User user); } } diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs b/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs index 1dd267c939..ba0b82a0b6 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs @@ -37,10 +37,12 @@ namespace MediaBrowser.Controller.LiveTv string ExternalId { get; set; } string EpisodeTitle { get; set; } - bool IsSeries { get; set; } string SeriesTimerId { get; set; } RecordingStatus Status { get; set; } DateTime? EndDate { get; set; } ChannelType ChannelType { get; set; } + DateTime DateLastSaved { get; set; } + DateTime DateCreated { get; set; } + DateTime DateModified { get; set; } } } diff --git a/MediaBrowser.Controller/LiveTv/ITunerHost.cs b/MediaBrowser.Controller/LiveTv/ITunerHost.cs new file mode 100644 index 0000000000..bedbcffe32 --- /dev/null +++ b/MediaBrowser.Controller/LiveTv/ITunerHost.cs @@ -0,0 +1,55 @@ +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.LiveTv; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.LiveTv +{ + public interface ITunerHost + { + /// + /// Gets the name. + /// + /// The name. + string Name { get; } + /// + /// Gets the type. + /// + /// The type. + string Type { get; } + /// + /// Gets the channels. + /// + /// The cancellation token. + /// Task<IEnumerable<ChannelInfo>>. + Task> GetChannels(CancellationToken cancellationToken); + /// + /// Gets the tuner infos. + /// + /// The cancellation token. + /// Task<List<LiveTvTunerInfo>>. + Task> GetTunerInfos(CancellationToken cancellationToken); + /// + /// Gets the channel stream. + /// + /// The channel identifier. + /// The stream identifier. + /// The cancellation token. + /// Task<MediaSourceInfo>. + Task GetChannelStream(string channelId, string streamId, CancellationToken cancellationToken); + /// + /// Gets the channel stream media sources. + /// + /// The channel identifier. + /// The cancellation token. + /// Task<List<MediaSourceInfo>>. + Task> GetChannelStreamMediaSources(string channelId, CancellationToken cancellationToken); + /// + /// Validates the specified information. + /// + /// The information. + /// Task. + Task Validate(TunerHostInfo info); + } +} diff --git a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs index 8232c5c7ad..12052905f2 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs @@ -1,4 +1,5 @@ using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.LiveTv; @@ -17,9 +18,24 @@ namespace MediaBrowser.Controller.LiveTv /// System.String. protected override string CreateUserDataKey() { + if (IsMovie) + { + var key = Movie.GetMovieUserDataKey(this); + + if (!string.IsNullOrWhiteSpace(key)) + { + return key; + } + } return GetClientTypeName() + "-" + Name; } + /// + /// Gets or sets the etag. + /// + /// The etag. + public string Etag { get; set; } + /// /// Id of the program. /// @@ -227,5 +243,19 @@ namespace MediaBrowser.Controller.LiveTv info.IsMovie = IsMovie; return info; } + + public override bool SupportsPeople + { + get + { + // Optimization + if (IsNews || IsSports) + { + return false; + } + + return base.SupportsPeople; + } + } } } diff --git a/MediaBrowser.Controller/LiveTv/LiveTvServiceStatusInfo.cs b/MediaBrowser.Controller/LiveTv/LiveTvServiceStatusInfo.cs index 0cb064aba3..4da238acf5 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvServiceStatusInfo.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvServiceStatusInfo.cs @@ -34,10 +34,16 @@ namespace MediaBrowser.Controller.LiveTv /// /// The tuners. public List Tuners { get; set; } - + /// + /// Gets or sets a value indicating whether this instance is visible. + /// + /// true if this instance is visible; otherwise, false. + public bool IsVisible { get; set; } + public LiveTvServiceStatusInfo() { Tuners = new List(); + IsVisible = true; } } } diff --git a/MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs b/MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs index aaaff6bdb9..960f8054a1 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs @@ -1,4 +1,5 @@ using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; @@ -40,6 +41,16 @@ namespace MediaBrowser.Controller.LiveTv /// System.String. protected override string CreateUserDataKey() { + if (IsMovie) + { + var key = Movie.GetMovieUserDataKey(this); + + if (!string.IsNullOrWhiteSpace(key)) + { + return key; + } + } + var name = GetClientTypeName(); if (!string.IsNullOrEmpty(ProgramId)) diff --git a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs index 36f082b02c..a6a3e61081 100644 --- a/MediaBrowser.Controller/LiveTv/ProgramInfo.cs +++ b/MediaBrowser.Controller/LiveTv/ProgramInfo.cs @@ -33,6 +33,11 @@ namespace MediaBrowser.Controller.LiveTv /// /// The overview. public string Overview { get; set; } + /// + /// Gets or sets the short overview. + /// + /// The short overview. + public string ShortOverview { get; set; } /// /// The start date of the program, in UTC. @@ -150,6 +155,36 @@ namespace MediaBrowser.Controller.LiveTv /// /// The production year. public int? ProductionYear { get; set; } + /// + /// Gets or sets the home page URL. + /// + /// The home page URL. + public string HomePageUrl { get; set; } + /// + /// Gets or sets the series identifier. + /// + /// The series identifier. + public string SeriesId { get; set; } + /// + /// Gets or sets the show identifier. + /// + /// The show identifier. + public string ShowId { get; set; } + /// + /// Gets or sets the season number. + /// + /// The season number. + public int? SeasonNumber { get; set; } + /// + /// Gets or sets the episode number. + /// + /// The episode number. + public int? EpisodeNumber { get; set; } + /// + /// Gets or sets the etag. + /// + /// The etag. + public string Etag { get; set; } public ProgramInfo() { diff --git a/MediaBrowser.Controller/LiveTv/RecordingInfo.cs b/MediaBrowser.Controller/LiveTv/RecordingInfo.cs index bf453ccf4d..3006b9bbef 100644 --- a/MediaBrowser.Controller/LiveTv/RecordingInfo.cs +++ b/MediaBrowser.Controller/LiveTv/RecordingInfo.cs @@ -16,6 +16,12 @@ namespace MediaBrowser.Controller.LiveTv /// /// The series timer identifier. public string SeriesTimerId { get; set; } + + /// + /// Gets or sets the timer identifier. + /// + /// The timer identifier. + public string TimerId { get; set; } /// /// ChannelId of the recording. @@ -179,7 +185,17 @@ namespace MediaBrowser.Controller.LiveTv /// /// null if [has image] contains no value, true if [has image]; otherwise, false. public bool? HasImage { get; set; } + /// + /// Gets or sets the show identifier. + /// + /// The show identifier. + public string ShowId { get; set; } + /// + /// Gets or sets the date last updated. + /// + /// The date last updated. + public DateTime DateLastUpdated { get; set; } public RecordingInfo() { diff --git a/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs b/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs index 1be6549ff8..2d79473f00 100644 --- a/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs +++ b/MediaBrowser.Controller/LiveTv/SeriesTimerInfo.cs @@ -94,6 +94,12 @@ namespace MediaBrowser.Controller.LiveTv /// /// true if this instance is post padding required; otherwise, false. public bool IsPostPaddingRequired { get; set; } + + /// + /// Gets or sets the series identifier. + /// + /// The series identifier. + public string SeriesId { get; set; } public SeriesTimerInfo() { diff --git a/MediaBrowser.Controller/Localization/ILocalizationManager.cs b/MediaBrowser.Controller/Localization/ILocalizationManager.cs index f41940ed45..5742d235cd 100644 --- a/MediaBrowser.Controller/Localization/ILocalizationManager.cs +++ b/MediaBrowser.Controller/Localization/ILocalizationManager.cs @@ -1,6 +1,5 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; -using System; using System.Collections.Generic; namespace MediaBrowser.Controller.Localization @@ -47,26 +46,10 @@ namespace MediaBrowser.Controller.Localization /// System.String. string GetLocalizedString(string phrase); - /// - /// Localizes the document. - /// - /// The document. - /// The culture. - /// The token builder. - /// System.String. - string LocalizeDocument(string document, string culture, Func tokenBuilder); - /// /// Gets the localization options. /// /// IEnumerable{LocalizatonOption}. IEnumerable GetLocalizationOptions(); - - /// - /// Gets the java script localization dictionary. - /// - /// The culture. - /// Dictionary{System.StringSystem.String}. - Dictionary GetJavaScriptLocalizationDictionary(string culture); } } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index fcde6d8c0e..24309734f8 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -113,6 +113,7 @@ + @@ -198,7 +199,10 @@ + + + @@ -309,7 +313,6 @@ - diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 5bec7980aa..23285b612a 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -116,5 +116,12 @@ namespace MediaBrowser.Controller.MediaEncoding Task EncodeVideo(EncodingJobOptions options, IProgress progress, CancellationToken cancellationToken); + + /// + /// Escapes the subtitle filter path. + /// + /// The path. + /// System.String. + string EscapeSubtitleFilterPath(string path); } } diff --git a/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs b/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs index e4a2cd007b..826711e51d 100644 --- a/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/ISubtitleEncoder.cs @@ -7,24 +7,6 @@ namespace MediaBrowser.Controller.MediaEncoding { public interface ISubtitleEncoder { - /// - /// Converts the subtitles. - /// - /// The stream. - /// The input format. - /// The output format. - /// The start time ticks. - /// The end time ticks. - /// The cancellation token. - /// Task{Stream}. - Task ConvertSubtitles( - Stream stream, - string inputFormat, - string outputFormat, - long startTimeTicks, - long? endTimeTicks, - CancellationToken cancellationToken); - /// /// Gets the subtitles. /// diff --git a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs index c1a4fa765a..383d0881e9 100644 --- a/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs +++ b/MediaBrowser.Controller/Providers/BaseItemXmlParser.cs @@ -74,6 +74,8 @@ namespace MediaBrowser.Controller.Providers /// The cancellation token. private void Fetch(MetadataResult item, string metadataFile, XmlReaderSettings settings, Encoding encoding, CancellationToken cancellationToken) { + item.ResetPeople(); + using (var streamReader = new StreamReader(metadataFile, encoding)) { // Use XmlReader for best performance @@ -492,7 +494,7 @@ namespace MediaBrowser.Controller.Providers { continue; } - PeopleHelper.AddPerson(itemResult.People, p); + itemResult.AddPerson(p); } break; } @@ -504,7 +506,7 @@ namespace MediaBrowser.Controller.Providers { continue; } - PeopleHelper.AddPerson(itemResult.People, p); + itemResult.AddPerson(p); } break; } @@ -529,7 +531,7 @@ namespace MediaBrowser.Controller.Providers { continue; } - PeopleHelper.AddPerson(itemResult.People, p); + itemResult.AddPerson(p); } } break; @@ -543,7 +545,7 @@ namespace MediaBrowser.Controller.Providers { continue; } - PeopleHelper.AddPerson(itemResult.People, p); + itemResult.AddPerson(p); } break; } @@ -1156,7 +1158,7 @@ namespace MediaBrowser.Controller.Providers { continue; } - PeopleHelper.AddPerson(item.People, person); + item.AddPerson(person); } } break; diff --git a/MediaBrowser.Controller/Providers/DirectoryService.cs b/MediaBrowser.Controller/Providers/DirectoryService.cs index 9e549dcf3f..79ffb0d01f 100644 --- a/MediaBrowser.Controller/Providers/DirectoryService.cs +++ b/MediaBrowser.Controller/Providers/DirectoryService.cs @@ -58,8 +58,9 @@ namespace MediaBrowser.Controller.Providers try { - var list = new DirectoryInfo(path) - .EnumerateFileSystemInfos("*", SearchOption.TopDirectoryOnly); + // using EnumerateFileSystemInfos doesn't handle reparse points (symlinks) + var list = new DirectoryInfo(path).EnumerateDirectories("*", SearchOption.TopDirectoryOnly) + .Concat(new DirectoryInfo(path).EnumerateFiles("*", SearchOption.TopDirectoryOnly)); // Seeing dupes on some users file system for some reason foreach (var item in list) diff --git a/MediaBrowser.Controller/Providers/ILocalMetadataProvider.cs b/MediaBrowser.Controller/Providers/ILocalMetadataProvider.cs index 3a8ef73254..28a5353104 100644 --- a/MediaBrowser.Controller/Providers/ILocalMetadataProvider.cs +++ b/MediaBrowser.Controller/Providers/ILocalMetadataProvider.cs @@ -18,7 +18,7 @@ namespace MediaBrowser.Controller.Providers /// The directory service. /// The cancellation token. /// Task{MetadataResult{`0}}. - Task> GetMetadata(ItemInfo info, + Task> GetMetadata(ItemInfo info, IDirectoryService directoryService, CancellationToken cancellationToken); } diff --git a/MediaBrowser.Controller/Providers/LocalMetadataResult.cs b/MediaBrowser.Controller/Providers/LocalMetadataResult.cs deleted file mode 100644 index 76b7a31360..0000000000 --- a/MediaBrowser.Controller/Providers/LocalMetadataResult.cs +++ /dev/null @@ -1,18 +0,0 @@ -using MediaBrowser.Controller.Entities; -using System.Collections.Generic; - -namespace MediaBrowser.Controller.Providers -{ - public class LocalMetadataResult : MetadataResult - where T : IHasMetadata - { - public List Images { get; set; } - public List UserDataLIst { get; set; } - - public LocalMetadataResult() - { - Images = new List(); - UserDataLIst = new List(); - } - } -} \ No newline at end of file diff --git a/MediaBrowser.Controller/Providers/MetadataResult.cs b/MediaBrowser.Controller/Providers/MetadataResult.cs index a18dd83e83..17175f91cf 100644 --- a/MediaBrowser.Controller/Providers/MetadataResult.cs +++ b/MediaBrowser.Controller/Providers/MetadataResult.cs @@ -1,18 +1,67 @@ using MediaBrowser.Controller.Entities; +using System; using System.Collections.Generic; +using System.Linq; namespace MediaBrowser.Controller.Providers { public class MetadataResult { + public List Images { get; set; } + public List UserDataList { get; set; } + + public MetadataResult() + { + Images = new List(); + } + public List People { get; set; } public bool HasMetadata { get; set; } public T Item { get; set; } - public MetadataResult() + public void AddPerson(PersonInfo p) { - People = new List(); + if (People == null) + { + People = new List(); + } + + PeopleHelper.AddPerson(People, p); + } + + /// + /// Not only does this clear, but initializes the list so that services can differentiate between a null list and zero people + /// + public void ResetPeople() + { + if (People == null) + { + People = new List(); + } + People.Clear(); + } + + public UserItemData GetOrAddUserData(string userId) + { + if (UserDataList == null) + { + UserDataList = new List(); + } + + var userData = UserDataList.FirstOrDefault(i => string.Equals(userId, i.UserId.ToString("N"), StringComparison.OrdinalIgnoreCase)); + + if (userData == null) + { + userData = new UserItemData() + { + UserId = new Guid(userId) + }; + + UserDataList.Add(userData); + } + + return userData; } } } \ No newline at end of file diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index 64b20c13ef..b3e82f9258 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -125,6 +125,12 @@ namespace MediaBrowser.Controller.Session /// The session controller. public ISessionController SessionController { get; set; } + /// + /// Gets or sets the application icon URL. + /// + /// The application icon URL. + public string AppIconUrl { get; set; } + /// /// Gets or sets the supported commands. /// diff --git a/MediaBrowser.Controller/Sync/IServerSyncProvider.cs b/MediaBrowser.Controller/Sync/IServerSyncProvider.cs index 860c736ead..36ad27290d 100644 --- a/MediaBrowser.Controller/Sync/IServerSyncProvider.cs +++ b/MediaBrowser.Controller/Sync/IServerSyncProvider.cs @@ -63,4 +63,15 @@ namespace MediaBrowser.Controller.Sync /// Task<SyncedFileInfo>. Task SendFile(string path, string[] pathParts, SyncTarget target, IProgress progress, CancellationToken cancellationToken); } + + public interface IHasDuplicateCheck + { + /// + /// Allows the duplicate job item. + /// + /// The original. + /// The duplicate. + /// true if XXXX, false otherwise. + bool AllowDuplicateJobItem(SyncJobItem original, SyncJobItem duplicate); + } } diff --git a/MediaBrowser.Dlna/Channels/DlnaChannelFactory.cs b/MediaBrowser.Dlna/Channels/DlnaChannelFactory.cs index 7e896492da..f26ceff905 100644 --- a/MediaBrowser.Dlna/Channels/DlnaChannelFactory.cs +++ b/MediaBrowser.Dlna/Channels/DlnaChannelFactory.cs @@ -24,7 +24,7 @@ namespace MediaBrowser.Dlna.Channels private readonly ILogger _logger; private readonly IHttpClient _httpClient; - private DeviceDiscovery _deviceDiscovery; + private readonly IDeviceDiscovery _deviceDiscovery; private readonly SemaphoreSlim _syncLock = new SemaphoreSlim(1, 1); private List _servers = new List(); @@ -33,21 +33,21 @@ namespace MediaBrowser.Dlna.Channels private Func> _localServersLookup; - public DlnaChannelFactory(IServerConfigurationManager config, IHttpClient httpClient, ILogger logger) + public DlnaChannelFactory(IServerConfigurationManager config, IHttpClient httpClient, ILogger logger, IDeviceDiscovery deviceDiscovery) { _config = config; _httpClient = httpClient; _logger = logger; + _deviceDiscovery = deviceDiscovery; Instance = this; } - internal void Start(DeviceDiscovery deviceDiscovery, Func> localServersLookup) + internal void Start(Func> localServersLookup) { _localServersLookup = localServersLookup; - _deviceDiscovery = deviceDiscovery; //deviceDiscovery.DeviceDiscovered += deviceDiscovery_DeviceDiscovered; - deviceDiscovery.DeviceLeft += deviceDiscovery_DeviceLeft; + _deviceDiscovery.DeviceLeft += deviceDiscovery_DeviceLeft; } async void deviceDiscovery_DeviceDiscovered(object sender, SsdpMessageEventArgs e) diff --git a/MediaBrowser.Dlna/Didl/DidlBuilder.cs b/MediaBrowser.Dlna/Didl/DidlBuilder.cs index 50a6f3ba61..390f442670 100644 --- a/MediaBrowser.Dlna/Didl/DidlBuilder.cs +++ b/MediaBrowser.Dlna/Didl/DidlBuilder.cs @@ -120,7 +120,7 @@ namespace MediaBrowser.Dlna.Didl } } - AddCover(item, null, element); + AddCover(item, context, null, element); return element; } @@ -193,6 +193,9 @@ namespace MediaBrowser.Dlna.Didl if (string.Equals(subtitleMode, "CaptionInfoEx", StringComparison.OrdinalIgnoreCase)) { + // http://192.168.1.3:9999/video.srt + // http://192.168.1.3:9999/video.srt + //var res = container.OwnerDocument.CreateElement("SEC", "CaptionInfoEx"); //res.InnerText = info.Url; @@ -201,6 +204,16 @@ namespace MediaBrowser.Dlna.Didl //res.SetAttribute("type", info.Format.ToLower()); //container.AppendChild(res); } + else if (string.Equals(subtitleMode, "smi", StringComparison.OrdinalIgnoreCase)) + { + var res = container.OwnerDocument.CreateElement(string.Empty, "res", NS_DIDL); + + res.InnerText = info.Url; + + res.SetAttribute("protocolInfo", "http-get:*:smi/caption:*"); + + container.AppendChild(res); + } else { var res = container.OwnerDocument.CreateElement(string.Empty, "res", NS_DIDL); @@ -481,7 +494,7 @@ namespace MediaBrowser.Dlna.Didl AddCommonFields(folder, stubType, null, container, filter); - AddCover(folder, stubType, container); + AddCover(folder, context, stubType, container); return container; } @@ -764,7 +777,7 @@ namespace MediaBrowser.Dlna.Didl } } - private void AddCover(BaseItem item, StubType? stubType, XmlElement element) + private void AddCover(BaseItem item, BaseItem context, StubType? stubType, XmlElement element) { if (stubType.HasValue && stubType.Value == StubType.People) { @@ -772,7 +785,26 @@ namespace MediaBrowser.Dlna.Didl return; } - var imageInfo = GetImageInfo(item); + ImageDownloadInfo imageInfo = null; + + if (context is UserView) + { + var episode = item as Episode; + if (episode != null) + { + var parent = (BaseItem)episode.Series ?? episode.Season; + if (parent != null) + { + imageInfo = GetImageInfo(parent); + } + } + } + + // Finally, just use the image from the item + if (imageInfo == null) + { + imageInfo = GetImageInfo(item); + } if (imageInfo == null) { @@ -850,7 +882,7 @@ namespace MediaBrowser.Dlna.Didl private void AddEmbeddedImageAsCover(string name, XmlElement element) { var result = element.OwnerDocument; - + var icon = result.CreateElement("upnp", "albumArtURI", NS_UPNP); var profile = result.CreateAttribute("dlna", "profileID", NS_DLNA); profile.InnerText = _profile.AlbumArtPn; @@ -925,14 +957,11 @@ namespace MediaBrowser.Dlna.Didl } } - if (item is Audio || item is Episode) - { - item = item.Parents.FirstOrDefault(i => i.HasImage(ImageType.Primary)); + item = item.Parents.FirstOrDefault(i => i.HasImage(ImageType.Primary)); - if (item != null) - { - return GetImageInfo(item, ImageType.Primary); - } + if (item != null) + { + return GetImageInfo(item, ImageType.Primary); } return null; @@ -955,17 +984,17 @@ namespace MediaBrowser.Dlna.Didl int? width = null; int? height = null; - try - { - var size = _imageProcessor.GetImageSize(imageInfo); + //try + //{ + // var size = _imageProcessor.GetImageSize(imageInfo); - width = Convert.ToInt32(size.Width); - height = Convert.ToInt32(size.Height); - } - catch - { + // width = Convert.ToInt32(size.Width); + // height = Convert.ToInt32(size.Height); + //} + //catch + //{ - } + //} return new ImageDownloadInfo { diff --git a/MediaBrowser.Dlna/DlnaManager.cs b/MediaBrowser.Dlna/DlnaManager.cs index 9ce62034b4..95e2aaa77c 100644 --- a/MediaBrowser.Dlna/DlnaManager.cs +++ b/MediaBrowser.Dlna/DlnaManager.cs @@ -530,6 +530,7 @@ namespace MediaBrowser.Dlna new SonyBravia2011Profile(), new SonyBravia2012Profile(), new SonyBravia2013Profile(), + new SonyBravia2014Profile(), new SonyBlurayPlayer2013Profile(), new SonyBlurayPlayerProfile(), new PanasonicVieraProfile(), @@ -547,7 +548,8 @@ namespace MediaBrowser.Dlna new DefaultProfile(), new PopcornHourProfile(), new VlcProfile(), - new BubbleUpnpProfile() + new BubbleUpnpProfile(), + new KodiProfile(), }; foreach (var item in list) diff --git a/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs b/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs index f9f9c3f18b..bdb778cab7 100644 --- a/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs +++ b/MediaBrowser.Dlna/Main/DlnaEntryPoint.cs @@ -38,7 +38,7 @@ namespace MediaBrowser.Dlna.Main private readonly IMediaSourceManager _mediaSourceManager; private readonly SsdpHandler _ssdpHandler; - private DeviceDiscovery _deviceDiscovery; + private readonly IDeviceDiscovery _deviceDiscovery; private readonly List _registeredServerIds = new List(); private bool _dlnaServerStarted; @@ -56,7 +56,7 @@ namespace MediaBrowser.Dlna.Main IUserDataManager userDataManager, ILocalizationManager localization, IMediaSourceManager mediaSourceManager, - ISsdpHandler ssdpHandler) + ISsdpHandler ssdpHandler, IDeviceDiscovery deviceDiscovery) { _config = config; _appHost = appHost; @@ -70,6 +70,7 @@ namespace MediaBrowser.Dlna.Main _userDataManager = userDataManager; _localization = localization; _mediaSourceManager = mediaSourceManager; + _deviceDiscovery = deviceDiscovery; _ssdpHandler = (SsdpHandler)ssdpHandler; _logger = logManager.GetLogger("Dlna"); } @@ -81,7 +82,7 @@ namespace MediaBrowser.Dlna.Main _config.NamedConfigurationUpdated += _config_NamedConfigurationUpdated; - DlnaChannelFactory.Instance.Start(_deviceDiscovery, () => _registeredServerIds); + DlnaChannelFactory.Instance.Start(() => _registeredServerIds); } void _config_NamedConfigurationUpdated(object sender, ConfigurationUpdateEventArgs e) @@ -125,9 +126,7 @@ namespace MediaBrowser.Dlna.Main { _ssdpHandler.Start(); - _deviceDiscovery = new DeviceDiscovery(_logger, _config, _ssdpHandler, _appHost); - - _deviceDiscovery.Start(); + ((DeviceDiscovery)_deviceDiscovery).Start(_ssdpHandler); } catch (Exception ex) { @@ -135,18 +134,6 @@ namespace MediaBrowser.Dlna.Main } } - private void DisposeDeviceDiscovery() - { - try - { - _deviceDiscovery.Dispose(); - } - catch (Exception ex) - { - _logger.ErrorException("Error disposing device discovery", ex); - } - } - public void StartDlnaServer() { try @@ -240,7 +227,6 @@ namespace MediaBrowser.Dlna.Main { DisposeDlnaServer(); DisposePlayToManager(); - DisposeDeviceDiscovery(); } public void DisposeDlnaServer() diff --git a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj index 06aaff734e..c49cbc6548 100644 --- a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj +++ b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj @@ -81,7 +81,9 @@ + + @@ -166,7 +168,9 @@ - + + Designer + @@ -175,7 +179,9 @@ - + + Designer + @@ -214,6 +220,16 @@ + + + Designer + + + + + Designer + + "); } - var version = GetType().Assembly.GetName().Version; + var versionString = !string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase) ? "?v=" + appVersion : string.Empty; - var imports = new string[] + var imports = new[] { - "vulcanize-out.html" + "vulcanize-out.html" + versionString }; var importsHtml = string.Join("", imports.Select(i => "").ToArray()); @@ -315,7 +319,9 @@ namespace MediaBrowser.WebDashboard.Api // In chrome it is causing the body to be hidden while loading, which leads to width-check methods to return 0 for everything //imports = ""; - html = html.Replace("", "" + GetMetaTags(mode) + GetCommonCss(mode, version) + GetInitialJavascript(mode, version) + importsHtml + GetCommonJavascript(mode, version)); + html = html.Replace("", "" + GetMetaTags(mode) + GetCommonCss(mode, appVersion)); + + html = html.Replace("", GetInitialJavascript(mode, appVersion) + importsHtml + GetCommonJavascript(mode, appVersion) + ""); var bytes = Encoding.UTF8.GetBytes(html); @@ -411,13 +417,12 @@ namespace MediaBrowser.WebDashboard.Api /// The mode. /// The version. /// System.String. - private string GetCommonCss(string mode, Version version) + private string GetCommonCss(string mode, string version) { var versionString = !string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase) ? "?v=" + version : string.Empty; var files = new[] { - "thirdparty/fontawesome/css/font-awesome.min.css" + versionString, "css/all.css" + versionString }; @@ -432,7 +437,7 @@ namespace MediaBrowser.WebDashboard.Api /// The mode. /// The version. /// System.String. - private string GetInitialJavascript(string mode, Version version) + private string GetInitialJavascript(string mode, string version) { var builder = new StringBuilder(); @@ -456,7 +461,7 @@ namespace MediaBrowser.WebDashboard.Api /// The mode. /// The version. /// System.String. - private string GetCommonJavascript(string mode, Version version) + private string GetCommonJavascript(string mode, string version) { var builder = new StringBuilder(); @@ -490,22 +495,7 @@ namespace MediaBrowser.WebDashboard.Api await AppendResource(memoryStream, "bower_components/jquery/dist/jquery.min.js", newLineBytes).ConfigureAwait(false); - await AppendResource(memoryStream, "bower_components/requirejs/require.js", newLineBytes).ConfigureAwait(false); - - await AppendResource(memoryStream, "thirdparty/jquerymobile-1.4.5/jquery.mobile.custom.min.js", newLineBytes).ConfigureAwait(false); - - await AppendResource(memoryStream, "thirdparty/browser.js", newLineBytes).ConfigureAwait(false); - - await AppendResource(memoryStream, "thirdparty/jquery.unveil-custom.js", newLineBytes).ConfigureAwait(false); - - var excludePhrases = new List(); - - if (string.Equals(mode, "cordova", StringComparison.OrdinalIgnoreCase)) - { - excludePhrases.Add("paypal"); - } - - await AppendLocalization(memoryStream, culture, excludePhrases).ConfigureAwait(false); + //await AppendLocalization(memoryStream, culture, excludePhrases).ConfigureAwait(false); await memoryStream.WriteAsync(newLineBytes, 0, newLineBytes.Length).ConfigureAwait(false); if (!string.IsNullOrWhiteSpace(mode)) @@ -523,8 +513,12 @@ namespace MediaBrowser.WebDashboard.Api var builder = new StringBuilder(); - var apiClientFiles = new[] + var commonFiles = new[] { + "bower_components/requirejs/require.js", + "thirdparty/jquerymobile-1.4.5/jquery.mobile.custom.js", + "thirdparty/browser.js", + "thirdparty/jquery.unveil-custom.js", "apiclient/logger.js", "apiclient/md5.js", "apiclient/sha1.js", @@ -537,9 +531,9 @@ namespace MediaBrowser.WebDashboard.Api "apiclient/apiclient.js" }.ToList(); - apiClientFiles.Add("apiclient/connectionmanager.js"); + commonFiles.Add("apiclient/connectionmanager.js"); - foreach (var file in apiClientFiles) + foreach (var file in commonFiles) { using (var fs = _fileSystem.GetFileStream(GetDashboardResourcePath(file), FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true)) { @@ -601,6 +595,7 @@ namespace MediaBrowser.WebDashboard.Api return new[] { "extensions.js", + "globalize.js", "site.js", "librarybrowser.js", "librarylist.js", @@ -615,7 +610,7 @@ namespace MediaBrowser.WebDashboard.Api "nowplayingbar.js", "alphapicker.js", "directorybrowser.js", - "moviecollections.js", + "collectioneditor.js", "notifications.js", "remotecontrol.js", "search.js", @@ -623,34 +618,6 @@ namespace MediaBrowser.WebDashboard.Api }; } - private async Task AppendLocalization(Stream stream, string culture, List excludePhrases) - { - var dictionary = _localization.GetJavaScriptLocalizationDictionary(culture); - - if (excludePhrases.Count > 0) - { - var removes = new List(); - - foreach (var pair in dictionary) - { - if (excludePhrases.Any(i => pair.Key.IndexOf(i, StringComparison.OrdinalIgnoreCase) != -1 || pair.Value.IndexOf(i, StringComparison.OrdinalIgnoreCase) != -1)) - { - removes.Add(pair.Key); - } - } - - foreach (var remove in removes) - { - dictionary.Remove(remove); - } - } - - var js = "window.localizationGlossary=" + _jsonSerializer.SerializeToString(dictionary); - - var bytes = Encoding.UTF8.GetBytes(js); - await stream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false); - } - /// /// Appends the resource. /// @@ -683,13 +650,11 @@ namespace MediaBrowser.WebDashboard.Api private async Task GetAllCss(bool enableMinification) { var memoryStream = new MemoryStream(); - var newLineBytes = Encoding.UTF8.GetBytes(Environment.NewLine); - - await AppendResource(memoryStream, "thirdparty/jquerymobile-1.4.5/jquery.mobile.custom.theme.min.css", newLineBytes).ConfigureAwait(false); - await AppendResource(memoryStream, "thirdparty/jquerymobile-1.4.5/jquery.mobile.custom.structure.min.css", newLineBytes).ConfigureAwait(false); var files = new[] { + "thirdparty/jquerymobile-1.4.5/jquery.mobile.custom.theme.css", + "thirdparty/jquerymobile-1.4.5/jquery.mobile.custom.structure.css", "css/site.css", "css/chromecast.css", "css/nowplayingbar.css", @@ -700,7 +665,6 @@ namespace MediaBrowser.WebDashboard.Api "css/card.css", "css/notifications.css", "css/search.css", - "css/pluginupdates.css", "css/remotecontrol.css", "css/userimage.css", "css/nowplaying.css", diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 04ba6d7007..8abd591e5a 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -90,9 +90,18 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + PreserveNewest + + PreserveNewest + PreserveNewest @@ -135,6 +144,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -150,6 +162,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -177,36 +192,60 @@ PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest PreserveNewest + + PreserveNewest + + + PreserveNewest + PreserveNewest PreserveNewest + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + PreserveNewest + + PreserveNewest + + + PreserveNewest + PreserveNewest @@ -222,6 +261,15 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + PreserveNewest @@ -282,6 +330,18 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + PreserveNewest @@ -326,12 +386,18 @@ PreserveNewest - + PreserveNewest PreserveNewest + + PreserveNewest + + + PreserveNewest + PreserveNewest @@ -517,9 +583,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest @@ -532,12 +595,6 @@ PreserveNewest - - PreserveNewest - - - PreserveNewest - PreserveNewest @@ -703,27 +760,15 @@ PreserveNewest - - PreserveNewest - PreserveNewest PreserveNewest - - PreserveNewest - PreserveNewest - - PreserveNewest - - - PreserveNewest - PreserveNewest @@ -775,9 +820,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest @@ -823,9 +865,6 @@ PreserveNewest - - PreserveNewest - PreserveNewest @@ -982,12 +1021,6 @@ PreserveNewest - - PreserveNewest - - - PreserveNewest - PreserveNewest @@ -1726,28 +1759,25 @@ PreserveNewest - + PreserveNewest PreserveNewest - - PreserveNewest - - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest - + PreserveNewest @@ -1774,6 +1804,12 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + PreserveNewest @@ -1801,24 +1837,9 @@ PreserveNewest - - PreserveNewest - PreserveNewest - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - PreserveNewest @@ -1944,9 +1965,6 @@ - - PreserveNewest - PreserveNewest @@ -2204,11 +2222,6 @@ PreserveNewest - - - PreserveNewest - - PreserveNewest @@ -2417,6 +2430,228 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + PreserveNewest @@ -2435,6 +2670,12 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + PreserveNewest diff --git a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs index ad514492d7..ac39d9d987 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs @@ -1,8 +1,8 @@ using MediaBrowser.Common.Configuration; -using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Extensions; using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Extensions; using MediaBrowser.Model.Logging; using MediaBrowser.XbmcMetadata.Configuration; using MediaBrowser.XbmcMetadata.Savers; @@ -12,6 +12,7 @@ using System.Globalization; using System.IO; using System.Linq; using System.Text; +using System.Text.RegularExpressions; using System.Threading; using System.Xml; @@ -47,7 +48,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers /// The cancellation token. /// /// - public void Fetch(LocalMetadataResult item, string metadataFile, CancellationToken cancellationToken) + public void Fetch(MetadataResult item, string metadataFile, CancellationToken cancellationToken) { if (item == null) { @@ -82,8 +83,10 @@ namespace MediaBrowser.XbmcMetadata.Parsers /// The metadata file. /// The settings. /// The cancellation token. - private void Fetch(LocalMetadataResult item, string metadataFile, XmlReaderSettings settings, CancellationToken cancellationToken) + private void Fetch(MetadataResult item, string metadataFile, XmlReaderSettings settings, CancellationToken cancellationToken) { + item.ResetPeople(); + if (!SupportsUrlAfterClosingXmlTag) { using (var streamReader = BaseNfoSaver.GetStreamReader(metadataFile)) @@ -115,7 +118,15 @@ namespace MediaBrowser.XbmcMetadata.Parsers var xml = streamReader.ReadToEnd(); - var index = xml.LastIndexOf('>'); + // Find last closing Tag + // Need to do this in two steps to account for random > characters after the closing xml + var index = xml.LastIndexOf(@"', index); + } if (index != -1) { @@ -147,42 +158,48 @@ namespace MediaBrowser.XbmcMetadata.Parsers ms.Write(bytes, 0, bytes.Length); ms.Position = 0; - // Use XmlReader for best performance - using (var reader = XmlReader.Create(ms, settings)) + // These are not going to be valid xml so no sense in causing the provider to fail and spamming the log with exceptions + try { - reader.MoveToContent(); - - // Loop through each element - while (reader.Read()) + // Use XmlReader for best performance + using (var reader = XmlReader.Create(ms, settings)) { - cancellationToken.ThrowIfCancellationRequested(); + reader.MoveToContent(); - if (reader.NodeType == XmlNodeType.Element) + // Loop through each element + while (reader.Read()) { - FetchDataFromXmlNode(reader, item); + cancellationToken.ThrowIfCancellationRequested(); + + if (reader.NodeType == XmlNodeType.Element) + { + FetchDataFromXmlNode(reader, item); + } } } } - + catch (XmlException) + { + + } } } } private void ParseProviderLinks(T item, string xml) { - var imdbId = xml.Split('/') - .FirstOrDefault(i => i.StartsWith("tt", StringComparison.OrdinalIgnoreCase)); - - if (!string.IsNullOrWhiteSpace(imdbId)) + //Look for a match for the Regex pattern "tt" followed by 7 digits + Match m = Regex.Match(xml, @"tt([0-9]{7})", RegexOptions.IgnoreCase); + if (m.Success) { - item.SetProviderId(MetadataProviders.Imdb, imdbId); + item.SetProviderId(MetadataProviders.Imdb, m.Value); } // TODO: Support Tmdb // http://www.themoviedb.org/movie/36557 } - protected virtual void FetchDataFromXmlNode(XmlReader reader, LocalMetadataResult itemResult) + protected virtual void FetchDataFromXmlNode(XmlReader reader, MetadataResult itemResult) { var item = itemResult.Item; @@ -574,7 +591,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers { continue; } - PeopleHelper.AddPerson(itemResult.People, p); + itemResult.AddPerson(p); } break; } @@ -593,7 +610,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers { continue; } - PeopleHelper.AddPerson(itemResult.People, p); + itemResult.AddPerson(p); } } break; @@ -607,7 +624,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers { continue; } - PeopleHelper.AddPerson(itemResult.People, p); + itemResult.AddPerson(p); } break; } @@ -618,7 +635,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers { var person = GetPersonFromXmlNode(subtree); - PeopleHelper.AddPerson(itemResult.People, person); + itemResult.AddPerson(person); } break; } @@ -925,17 +942,14 @@ namespace MediaBrowser.XbmcMetadata.Parsers { var val = reader.ReadElementContentAsString(); - if (!string.IsNullOrWhiteSpace(val)) + if (!string.IsNullOrWhiteSpace(val) && !string.IsNullOrWhiteSpace(userDataUserId)) { bool parsedValue; if (bool.TryParse(val, out parsedValue)) { - if (!string.IsNullOrWhiteSpace(userDataUserId)) - { - var userData = GetOrAdd(itemResult.UserDataLIst, userDataUserId); + var userData = GetOrAdd(itemResult, userDataUserId); - userData.Played = parsedValue; - } + userData.Played = parsedValue; } } break; @@ -945,21 +959,18 @@ namespace MediaBrowser.XbmcMetadata.Parsers { var val = reader.ReadElementContentAsString(); - if (!string.IsNullOrWhiteSpace(val)) + if (!string.IsNullOrWhiteSpace(val) && !string.IsNullOrWhiteSpace(userDataUserId)) { int parsedValue; if (int.TryParse(val, NumberStyles.Integer, _usCulture, out parsedValue)) { - if (!string.IsNullOrWhiteSpace(userDataUserId)) - { - var userData = GetOrAdd(itemResult.UserDataLIst, userDataUserId); + var userData = GetOrAdd(itemResult, userDataUserId); - userData.PlayCount = parsedValue; + userData.PlayCount = parsedValue; - if (parsedValue > 0) - { - userData.Played = true; - } + if (parsedValue > 0) + { + userData.Played = true; } } } @@ -970,17 +981,14 @@ namespace MediaBrowser.XbmcMetadata.Parsers { var val = reader.ReadElementContentAsString(); - if (!string.IsNullOrWhiteSpace(val)) + if (!string.IsNullOrWhiteSpace(val) && !string.IsNullOrWhiteSpace(userDataUserId)) { DateTime parsedValue; if (DateTime.TryParseExact(val, "yyyy-MM-dd HH:mm:ss", _usCulture, DateTimeStyles.None, out parsedValue)) { - if (!string.IsNullOrWhiteSpace(userDataUserId)) - { - var userData = GetOrAdd(itemResult.UserDataLIst, userDataUserId); + var userData = GetOrAdd(itemResult, userDataUserId); - userData.LastPlayedDate = parsedValue; - } + userData.LastPlayedDate = parsedValue; } } break; @@ -992,7 +1000,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers { if (!string.IsNullOrWhiteSpace(userDataUserId)) { - var userData = GetOrAdd(itemResult.UserDataLIst, userDataUserId); + var userData = GetOrAdd(itemResult, userDataUserId); FetchFromResumeNode(subtree, item, userData); } @@ -1004,17 +1012,14 @@ namespace MediaBrowser.XbmcMetadata.Parsers { var val = reader.ReadElementContentAsString(); - if (!string.IsNullOrWhiteSpace(val)) + if (!string.IsNullOrWhiteSpace(val) && !string.IsNullOrWhiteSpace(userDataUserId)) { bool parsedValue; if (bool.TryParse(val, out parsedValue)) { - if (!string.IsNullOrWhiteSpace(userDataUserId)) - { - var userData = GetOrAdd(itemResult.UserDataLIst, userDataUserId); + var userData = GetOrAdd(itemResult, userDataUserId); - userData.IsFavorite = parsedValue; - } + userData.IsFavorite = parsedValue; } } break; @@ -1024,17 +1029,14 @@ namespace MediaBrowser.XbmcMetadata.Parsers { var val = reader.ReadElementContentAsString(); - if (!string.IsNullOrWhiteSpace(val)) + if (!string.IsNullOrWhiteSpace(val) && !string.IsNullOrWhiteSpace(userDataUserId)) { double parsedValue; if (double.TryParse(val, NumberStyles.Any, _usCulture, out parsedValue)) { - if (!string.IsNullOrWhiteSpace(userDataUserId)) - { - var userData = GetOrAdd(itemResult.UserDataLIst, userDataUserId); + var userData = GetOrAdd(itemResult, userDataUserId); - userData.Rating = parsedValue; - } + userData.Rating = parsedValue; } } break; @@ -1046,21 +1048,9 @@ namespace MediaBrowser.XbmcMetadata.Parsers } } - private UserItemData GetOrAdd(List userDataList, string userId) + private UserItemData GetOrAdd(MetadataResult result, string userId) { - var userData = userDataList.FirstOrDefault(i => string.Equals(userId, i.UserId.ToString("N"), StringComparison.OrdinalIgnoreCase)); - - if (userData == null) - { - userData = new UserItemData() - { - UserId = new Guid(userId) - }; - - userDataList.Add(userData); - } - - return userData; + return result.GetOrAddUserData(userId); } private void FetchFromResumeNode(XmlReader reader, T item, UserItemData userData) diff --git a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs index 355f19de72..6dd70f2988 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/EpisodeNfoParser.cs @@ -17,7 +17,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers { } - public void Fetch(LocalMetadataResult item, + public void Fetch(MetadataResult item, List images, string metadataFile, CancellationToken cancellationToken) @@ -32,7 +32,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers /// /// The reader. /// The item result. - protected override void FetchDataFromXmlNode(XmlReader reader, LocalMetadataResult itemResult) + protected override void FetchDataFromXmlNode(XmlReader reader, MetadataResult itemResult) { var item = itemResult.Item; diff --git a/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs index ab46198755..8d5c2bf20e 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/MovieNfoParser.cs @@ -28,7 +28,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers /// /// The reader. /// The item result. - protected override void FetchDataFromXmlNode(XmlReader reader, LocalMetadataResult /// The reader. /// The item result. - protected override void FetchDataFromXmlNode(XmlReader reader, LocalMetadataResult itemResult) + protected override void FetchDataFromXmlNode(XmlReader reader, MetadataResult itemResult) { var item = itemResult.Item; diff --git a/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs index 5025f4d187..03dba56e03 100644 --- a/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs +++ b/MediaBrowser.XbmcMetadata/Parsers/SeriesNfoParser.cs @@ -1,12 +1,10 @@ using MediaBrowser.Common.Configuration; -using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using System; -using System.Collections.Generic; using System.Xml; namespace MediaBrowser.XbmcMetadata.Parsers @@ -22,7 +20,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers /// /// The reader. /// The item result. - protected override void FetchDataFromXmlNode(XmlReader reader, LocalMetadataResult itemResult) + protected override void FetchDataFromXmlNode(XmlReader reader, MetadataResult itemResult) { var item = itemResult.Item; diff --git a/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs index ac76db7711..f6b78f3425 100644 --- a/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/AlbumNfoProvider.cs @@ -21,7 +21,7 @@ namespace MediaBrowser.XbmcMetadata.Providers _config = config; } - protected override void Fetch(LocalMetadataResult result, string path, CancellationToken cancellationToken) + protected override void Fetch(MetadataResult result, string path, CancellationToken cancellationToken) { new BaseNfoParser(_logger, _config).Fetch(result, path, cancellationToken); } diff --git a/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs index 8a958c3a69..64cbfe486f 100644 --- a/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/ArtistNfoProvider.cs @@ -21,7 +21,7 @@ namespace MediaBrowser.XbmcMetadata.Providers _config = config; } - protected override void Fetch(LocalMetadataResult result, string path, CancellationToken cancellationToken) + protected override void Fetch(MetadataResult result, string path, CancellationToken cancellationToken) { new BaseNfoParser(_logger, _config).Fetch(result, path, cancellationToken); } diff --git a/MediaBrowser.XbmcMetadata/Providers/BaseNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/BaseNfoProvider.cs index 1e82dfce77..d7815dfb7e 100644 --- a/MediaBrowser.XbmcMetadata/Providers/BaseNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/BaseNfoProvider.cs @@ -14,11 +14,11 @@ namespace MediaBrowser.XbmcMetadata.Providers { protected IFileSystem FileSystem; - public async Task> GetMetadata(ItemInfo info, + public async Task> GetMetadata(ItemInfo info, IDirectoryService directoryService, CancellationToken cancellationToken) { - var result = new LocalMetadataResult(); + var result = new MetadataResult(); var file = GetXmlFile(info, directoryService); @@ -48,7 +48,7 @@ namespace MediaBrowser.XbmcMetadata.Providers return result; } - protected abstract void Fetch(LocalMetadataResult result, string path, CancellationToken cancellationToken); + protected abstract void Fetch(MetadataResult result, string path, CancellationToken cancellationToken); protected BaseNfoProvider(IFileSystem fileSystem) { diff --git a/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs b/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs index 79315cbf3a..fa9ef7797a 100644 --- a/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs +++ b/MediaBrowser.XbmcMetadata/Providers/BaseVideoNfoProvider.cs @@ -24,9 +24,9 @@ namespace MediaBrowser.XbmcMetadata.Providers _config = config; } - protected override void Fetch(LocalMetadataResult result, string path, CancellationToken cancellationToken) + protected override void Fetch(MetadataResult result, string path, CancellationToken cancellationToken) { - var tmpItem = new LocalMetadataResult