diff options
| author | Tim Hobbs <jesus.tesh@gmail.com> | 2014-03-13 06:26:20 -0700 |
|---|---|---|
| committer | Tim Hobbs <jesus.tesh@gmail.com> | 2014-03-13 06:26:20 -0700 |
| commit | 9e33966ee120134d89f7aa21204b7ffc814dd29a (patch) | |
| tree | 297982b6cd4269ada15d93cec859b7cba27c0c1a | |
| parent | dd4a1ff4b50f58c27af223202454f80d45de4af1 (diff) | |
| parent | b7bcc2450694105de9f9fc8cc07d2cfc4d9d7c96 (diff) | |
Merge remote-tracking branch 'upstream/master'
160 files changed, 2741 insertions, 1682 deletions
diff --git a/MediaBrowser.Api/BaseApiService.cs b/MediaBrowser.Api/BaseApiService.cs index 5fba539fe..08686b43a 100644 --- a/MediaBrowser.Api/BaseApiService.cs +++ b/MediaBrowser.Api/BaseApiService.cs @@ -164,7 +164,10 @@ namespace MediaBrowser.Api return name; } - return libraryManager.GetAllArtists() + return libraryManager.RootFolder.RecursiveChildren + .OfType<Audio>() + .SelectMany(i => i.AllArtists) + .Distinct(StringComparer.OrdinalIgnoreCase) .FirstOrDefault(i => { i = _dashReplaceChars.Aggregate(i, (current, c) => current.Replace(c, SlugChar)); diff --git a/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs b/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs index afba8c360..c9c4cbc43 100644 --- a/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs +++ b/MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs @@ -194,8 +194,10 @@ namespace MediaBrowser.Api.DefaultTheme .Select(i => _dtoService.GetBaseItemDto(i, fields, user)) .ToList(); - var artists = _libraryManager.GetAllArtists(allItems) - .Randomize() + var artists = allItems.OfType<Audio>() + .SelectMany(i => i.AllArtists) + .Distinct(StringComparer.OrdinalIgnoreCase) + .Randomize() .Select(i => { try diff --git a/MediaBrowser.Api/GamesService.cs b/MediaBrowser.Api/GamesService.cs index eabda673a..e371791f8 100644 --- a/MediaBrowser.Api/GamesService.cs +++ b/MediaBrowser.Api/GamesService.cs @@ -155,7 +155,7 @@ namespace MediaBrowser.Api var games = items.OfType<Game>().ToList(); - summary.ClientInstalledGameCount = games.Count(i => !i.IsPlaceHolder); + summary.ClientInstalledGameCount = games.Count(i => i.IsPlaceHolder); summary.GameCount = games.Count; diff --git a/MediaBrowser.Api/ItemUpdateService.cs b/MediaBrowser.Api/ItemUpdateService.cs index 1eaf4acb1..b15e67ffa 100644 --- a/MediaBrowser.Api/ItemUpdateService.cs +++ b/MediaBrowser.Api/ItemUpdateService.cs @@ -98,7 +98,7 @@ namespace MediaBrowser.Api UpdateItem(request, item); - await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + await item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); if (dontFetchMetaChanged && item.IsFolder) { @@ -107,7 +107,7 @@ namespace MediaBrowser.Api foreach (var child in folder.RecursiveChildren.ToList()) { child.DontFetchMeta = newLockData; - await _libraryManager.UpdateItem(child, ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + await child.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); } } } @@ -125,7 +125,7 @@ namespace MediaBrowser.Api UpdateItem(request, item); - await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + await item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); } public void Post(UpdateArtist request) @@ -141,7 +141,7 @@ namespace MediaBrowser.Api UpdateItem(request, item); - await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + await item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); } public void Post(UpdateStudio request) @@ -157,7 +157,7 @@ namespace MediaBrowser.Api UpdateItem(request, item); - await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + await item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); } public void Post(UpdateMusicGenre request) @@ -173,7 +173,7 @@ namespace MediaBrowser.Api UpdateItem(request, item); - await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + await item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); } public void Post(UpdateGameGenre request) @@ -189,7 +189,7 @@ namespace MediaBrowser.Api UpdateItem(request, item); - await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + await item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); } public void Post(UpdateGenre request) @@ -205,7 +205,7 @@ namespace MediaBrowser.Api UpdateItem(request, item); - await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + await item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); } private void UpdateItem(BaseItemDto request, BaseItem item) diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index bcc487a5d..e19fbb967 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -66,7 +66,8 @@ <Compile Include="..\SharedVersion.cs"> <Link>Properties\SharedVersion.cs</Link> </Compile> - <Compile Include="AlbumsService.cs" /> + <Compile Include="Movies\CollectionService.cs" /> + <Compile Include="Music\AlbumsService.cs" /> <Compile Include="AppThemeService.cs" /> <Compile Include="BaseApiService.cs" /> <Compile Include="ConfigurationService.cs" /> @@ -81,7 +82,7 @@ <Compile Include="Images\ImageRequest.cs" /> <Compile Include="Images\ImageService.cs" /> <Compile Include="Images\ImageWriter.cs" /> - <Compile Include="InstantMixService.cs" /> + <Compile Include="Music\InstantMixService.cs" /> <Compile Include="ItemLookupService.cs" /> <Compile Include="ItemRefreshService.cs" /> <Compile Include="ItemUpdateService.cs" /> @@ -91,7 +92,7 @@ <Compile Include="Library\LibraryStructureService.cs" /> <Compile Include="LiveTv\LiveTvService.cs" /> <Compile Include="LocalizationService.cs" /> - <Compile Include="MoviesService.cs" /> + <Compile Include="Movies\MoviesService.cs" /> <Compile Include="NewsService.cs" /> <Compile Include="NotificationsService.cs" /> <Compile Include="PackageReviewService.cs" /> @@ -118,7 +119,7 @@ <Compile Include="SessionsService.cs" /> <Compile Include="SimilarItemsHelper.cs" /> <Compile Include="SystemService.cs" /> - <Compile Include="TrailersService.cs" /> + <Compile Include="Movies\TrailersService.cs" /> <Compile Include="TvShowsService.cs" /> <Compile Include="UserLibrary\ArtistsService.cs" /> <Compile Include="UserLibrary\BaseItemsByNameService.cs" /> diff --git a/MediaBrowser.Api/Movies/CollectionService.cs b/MediaBrowser.Api/Movies/CollectionService.cs new file mode 100644 index 000000000..456449b7b --- /dev/null +++ b/MediaBrowser.Api/Movies/CollectionService.cs @@ -0,0 +1,80 @@ +using MediaBrowser.Controller.Collections; +using ServiceStack; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace MediaBrowser.Api.Movies +{ + [Route("/Collections", "POST")] + [Api(Description = "Creates a new collection")] + public class CreateCollection : IReturnVoid + { + [ApiMember(Name = "IsLocked", Description = "Whether or not to lock the new collection.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "POST")] + public bool IsLocked { get; set; } + + [ApiMember(Name = "Name", Description = "The name of the new collection.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public string Name { get; set; } + + [ApiMember(Name = "ParentId", Description = "Optional - create the collection within a specific folder", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] + public Guid? ParentId { get; set; } + } + + [Route("/Collections/{Id}/Items", "POST")] + [Api(Description = "Adds items to a collection")] + public class AddToCollection : IReturnVoid + { + [ApiMember(Name = "Ids", Description = "Item id, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string Ids { get; set; } + + [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")] + public Guid Id { get; set; } + } + + [Route("/Collections/{Id}/Items", "DELETE")] + [Api(Description = "Removes items from a collection")] + public class RemoveFromCollection : IReturnVoid + { + [ApiMember(Name = "Ids", Description = "Item id, comma delimited", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")] + public string Ids { get; set; } + + [ApiMember(Name = "Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] + public Guid Id { get; set; } + } + + public class CollectionService : BaseApiService + { + private readonly ICollectionManager _collectionManager; + + public CollectionService(ICollectionManager collectionManager) + { + _collectionManager = collectionManager; + } + + public void Post(CreateCollection request) + { + var task = _collectionManager.CreateCollection(new CollectionCreationOptions + { + IsLocked = request.IsLocked, + Name = request.Name, + ParentId = request.ParentId + }); + + Task.WaitAll(task); + } + + public void Post(AddToCollection request) + { + var task = _collectionManager.AddToCollection(request.Id, request.Ids.Split(',').Select(i => new Guid(i))); + + Task.WaitAll(task); + } + + public void Delete(RemoveFromCollection request) + { + var task = _collectionManager.RemoveFromCollection(request.Id, request.Ids.Split(',').Select(i => new Guid(i))); + + Task.WaitAll(task); + } + } +} diff --git a/MediaBrowser.Api/Movies/MoviesService.cs b/MediaBrowser.Api/Movies/MoviesService.cs new file mode 100644 index 000000000..3360d9736 --- /dev/null +++ b/MediaBrowser.Api/Movies/MoviesService.cs @@ -0,0 +1,324 @@ +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Querying; +using ServiceStack; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MediaBrowser.Api.Movies +{ + /// <summary> + /// Class GetSimilarMovies + /// </summary> + [Route("/Movies/{Id}/Similar", "GET")] + [Api(Description = "Finds movies and trailers similar to a given movie.")] + public class GetSimilarMovies : BaseGetSimilarItemsFromItem + { + [ApiMember(Name = "IncludeTrailers", Description = "Whether or not to include trailers within the results. Defaults to true.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool IncludeTrailers { get; set; } + + public GetSimilarMovies() + { + IncludeTrailers = true; + } + } + + [Route("/Movies/Recommendations", "GET")] + [Api(Description = "Gets movie recommendations")] + public class GetMovieRecommendations : IReturn<RecommendationDto[]>, IHasItemFields + { + [ApiMember(Name = "CategoryLimit", Description = "The max number of categories to return", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int CategoryLimit { get; set; } + + [ApiMember(Name = "ItemLimit", Description = "The max number of items to return per category", IsRequired = false, DataType = "int", ParameterType = "query", Verb = "GET")] + public int ItemLimit { get; set; } + + /// <summary> + /// Gets or sets the user id. + /// </summary> + /// <value>The user id.</value> + [ApiMember(Name = "UserId", Description = "Optional. Filter by user id, and attach user data", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public Guid? UserId { get; set; } + + public GetMovieRecommendations() + { + CategoryLimit = 5; + ItemLimit = 8; + } + + public string Fields { get; set; } + } + + /// <summary> + /// Class MoviesService + /// </summary> + public class MoviesService : BaseApiService + { + /// <summary> + /// The _user manager + /// </summary> + private readonly IUserManager _userManager; + + /// <summary> + /// The _user data repository + /// </summary> + private readonly IUserDataManager _userDataRepository; + /// <summary> + /// The _library manager + /// </summary> + private readonly ILibraryManager _libraryManager; + + private readonly IItemRepository _itemRepo; + private readonly IDtoService _dtoService; + + /// <summary> + /// Initializes a new instance of the <see cref="MoviesService"/> class. + /// </summary> + /// <param name="userManager">The user manager.</param> + /// <param name="userDataRepository">The user data repository.</param> + /// <param name="libraryManager">The library manager.</param> + public MoviesService(IUserManager userManager, IUserDataManager userDataRepository, ILibraryManager libraryManager, IItemRepository itemRepo, IDtoService dtoService) + { + _userManager = userManager; + _userDataRepository = userDataRepository; + _libraryManager = libraryManager; + _itemRepo = itemRepo; + _dtoService = dtoService; + } + + /// <summary> + /// Gets the specified request. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>System.Object.</returns> + public object Get(GetSimilarMovies request) + { + var result = SimilarItemsHelper.GetSimilarItemsResult(_userManager, + _itemRepo, + _libraryManager, + _userDataRepository, + _dtoService, + Logger, + request, item => item is Movie || (item is Trailer && request.IncludeTrailers), + SimilarItemsHelper.GetSimiliarityScore); + + return ToOptimizedSerializedResultUsingCache(result); + } + + public object Get(GetMovieRecommendations request) + { + var user = request.UserId.HasValue ? _userManager.GetUserById(request.UserId.Value) : null; + + var folder = user.RootFolder; + var movies = folder.RecursiveChildren.OfType<Movie>().ToList(); + + var result = GetRecommendationCategories(user, movies, request.CategoryLimit, request.ItemLimit, request.GetItemFields().ToList()); + + return ToOptimizedResult(result); + } + + private IEnumerable<RecommendationDto> GetRecommendationCategories(User user, List<Movie> allMovies, int categoryLimit, int itemLimit, List<ItemFields> fields) + { + var categories = new List<RecommendationDto>(); + + var recentlyPlayedMovies = allMovies + .Select(i => + { + var userdata = _userDataRepository.GetUserData(user.Id, i.GetUserDataKey()); + return new Tuple<Movie, bool, DateTime>(i, userdata.Played, userdata.LastPlayedDate ?? DateTime.MinValue); + }) + .Where(i => i.Item2) + .OrderByDescending(i => i.Item3) + .Select(i => i.Item1) + .ToList(); + + var excludeFromLiked = recentlyPlayedMovies.Take(10); + var likedMovies = allMovies + .Select(i => + { + var score = 0; + var userData = _userDataRepository.GetUserData(user.Id, i.GetUserDataKey()); + + if (userData.IsFavorite) + { + score = 2; + } + else + { + score = userData.Likes.HasValue ? userData.Likes.Value ? 1 : -1 : 0; + } + + return new Tuple<Movie, int>(i, score); + }) + .OrderByDescending(i => i.Item2) + .ThenBy(i => Guid.NewGuid()) + .Where(i => i.Item2 > 0) + .Select(i => i.Item1) + .Where(i => !excludeFromLiked.Contains(i)); + + var mostRecentMovies = recentlyPlayedMovies.Take(6).ToList(); + // Get recently played directors + var recentDirectors = GetDirectors(mostRecentMovies) + .OrderBy(i => Guid.NewGuid()) + .ToList(); + + // Get recently played actors + var recentActors = GetActors(mostRecentMovies) + .OrderBy(i => Guid.NewGuid()) + .ToList(); + + var similarToRecentlyPlayed = GetSimilarTo(user, allMovies, recentlyPlayedMovies.Take(7).OrderBy(i => Guid.NewGuid()), itemLimit, fields, RecommendationType.SimilarToRecentlyPlayed).GetEnumerator(); + var similarToLiked = GetSimilarTo(user, allMovies, likedMovies, itemLimit, fields, RecommendationType.SimilarToLikedItem).GetEnumerator(); + + var hasDirectorFromRecentlyPlayed = GetWithDirector(user, allMovies, recentDirectors, itemLimit, fields, RecommendationType.HasDirectorFromRecentlyPlayed).GetEnumerator(); + var hasActorFromRecentlyPlayed = GetWithActor(user, allMovies, recentActors, itemLimit, fields, RecommendationType.HasActorFromRecentlyPlayed).GetEnumerator(); + + var categoryTypes = new List<IEnumerator<RecommendationDto>> + { + // Give this extra weight + similarToRecentlyPlayed, + similarToRecentlyPlayed, + + // Give this extra weight + similarToLiked, + similarToLiked, + + hasDirectorFromRecentlyPlayed, + hasActorFromRecentlyPlayed + }; + + while (categories.Count < categoryLimit) + { + var allEmpty = true; + + foreach (var category in categoryTypes) + { + if (category.MoveNext()) + { + categories.Add(category.Current); + allEmpty = false; + + if (categories.Count >= categoryLimit) + { + break; + } + } + } + + if (allEmpty) + { + break; + } + } + + //// Get the lead actor for all movies + //var allActors = GetActors(allMovies) + // .ToList(); + + //foreach (var actor in recentActors) + //{ + + //} + + return categories.OrderBy(i => i.RecommendationType).ThenBy(i => Guid.NewGuid()); + } + + private IEnumerable<RecommendationDto> GetWithDirector(User user, List<Movie> allMovies, IEnumerable<string> directors, int itemLimit, List<ItemFields> fields, RecommendationType type) + { + var userId = user.Id; + + foreach (var director in directors) + { + var items = allMovies + .Where(i => !_userDataRepository.GetUserData(userId, i.GetUserDataKey()).Played && i.People.Any(p => string.Equals(p.Type, PersonType.Director, StringComparison.OrdinalIgnoreCase) && string.Equals(p.Name, director, StringComparison.OrdinalIgnoreCase))) + .Take(itemLimit) + .ToList(); + + if (items.Count > 0) + { + yield return new RecommendationDto + { + BaselineItemName = director, + CategoryId = director.GetMD5().ToString("N"), + RecommendationType = type, + Items = items.Select(i => _dtoService.GetBaseItemDto(i, fields, user)).ToArray() + }; + } + } + } + + private IEnumerable<RecommendationDto> GetWithActor(User user, List<Movie> allMovies, IEnumerable<string> names, int itemLimit, List<ItemFields> fields, RecommendationType type) + { + var userId = user.Id; + + foreach (var name in names) + { + var items = allMovies + .Where(i => !_userDataRepository.GetUserData(userId, i.GetUserDataKey()).Played && i.People.Any(p => string.Equals(p.Name, name, StringComparison.OrdinalIgnoreCase))) + .Take(itemLimit) + .ToList(); + + if (items.Count > 0) + { + yield return new RecommendationDto + { + BaselineItemName = name, + CategoryId = name.GetMD5().ToString("N"), + RecommendationType = type, + Items = items.Select(i => _dtoService.GetBaseItemDto(i, fields, user)).ToArray() + }; + } + } + } + + private IEnumerable<RecommendationDto> GetSimilarTo(User user, List<Movie> allMovies, IEnumerable<Movie> baselineItems, int itemLimit, List<ItemFields> fields, RecommendationType type) + { + var userId = user.Id; + + foreach (var item in baselineItems) + { + var similar = SimilarItemsHelper + .GetSimilaritems(item, allMovies, SimilarItemsHelper.GetSimiliarityScore) + .Where(i => !_userDataRepository.GetUserData(userId, i.GetUserDataKey()).Played) + .Take(itemLimit) + .ToList(); + + if (similar.Count > 0) + { + yield return new RecommendationDto + { + BaselineItemName = item.Name, + CategoryId = item.Id.ToString("N"), + RecommendationType = type, + Items = similar.Select(i => _dtoService.GetBaseItemDto(i, fields, user)).ToArray() + }; + } + } + } + + private IEnumerable<string> GetActors(IEnumerable<BaseItem> items) + { + // Get the two leading actors for all movies + return items + .SelectMany(i => i.People.Where(p => !string.Equals(PersonType.Director, p.Type, StringComparison.OrdinalIgnoreCase)).Take(2)) + .Select(i => i.Name) + .Distinct(StringComparer.OrdinalIgnoreCase); + } + + private IEnumerable<string> GetDirectors(IEnumerable<BaseItem> items) + { + return items + .Select(i => i.People.FirstOrDefault(p => string.Equals(PersonType.Director, p.Type, StringComparison.OrdinalIgnoreCase))) + .Where(i => i != null) + .Select(i => i.Name) + .Distinct(StringComparer.OrdinalIgnoreCase); + } + } +} diff --git a/MediaBrowser.Api/TrailersService.cs b/MediaBrowser.Api/Movies/TrailersService.cs index ca465b5e3..4e17bc7b5 100644 --- a/MediaBrowser.Api/TrailersService.cs +++ b/MediaBrowser.Api/Movies/TrailersService.cs @@ -5,7 +5,7 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using ServiceStack; -namespace MediaBrowser.Api +namespace MediaBrowser.Api.Movies { /// <summary> /// Class GetSimilarTrailers diff --git a/MediaBrowser.Api/MoviesService.cs b/MediaBrowser.Api/MoviesService.cs deleted file mode 100644 index 2a99bca8b..000000000 --- a/MediaBrowser.Api/MoviesService.cs +++ /dev/null @@ -1,82 +0,0 @@ -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Persistence; -using ServiceStack; - -namespace MediaBrowser.Api -{ - /// <summary> - /// Class GetSimilarMovies - /// </summary> - [Route("/Movies/{Id}/Similar", "GET")] - [Api(Description = "Finds movies and trailers similar to a given movie.")] - public class GetSimilarMovies : BaseGetSimilarItemsFromItem - { - [ApiMember(Name = "IncludeTrailers", Description = "Whether or not to include trailers within the results. Defaults to true.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool IncludeTrailers { get; set; } - - public GetSimilarMovies() - { - IncludeTrailers = true; - } - } - - /// <summary> - /// Class MoviesService - /// </summary> - public class MoviesService : BaseApiService - { - /// <summary> - /// The _user manager - /// </summary> - private readonly IUserManager _userManager; - - /// <summary> - /// The _user data repository - /// </summary> - private readonly IUserDataManager _userDataRepository; - /// <summary> - /// The _library manager - /// </summary> - private readonly ILibraryManager _libraryManager; - - private readonly IItemRepository _itemRepo; - private readonly IDtoService _dtoService; - - /// <summary> - /// Initializes a new instance of the <see cref="MoviesService"/> class. - /// </summary> - /// <param name="userManager">The user manager.</param> - /// <param name="userDataRepository">The user data repository.</param> - /// <param name="libraryManager">The library manager.</param> - public MoviesService(IUserManager userManager, IUserDataManager userDataRepository, ILibraryManager libraryManager, IItemRepository itemRepo, IDtoService dtoService) - { - _userManager = userManager; - _userDataRepository = userDataRepository; - _libraryManager = libraryManager; - _itemRepo = itemRepo; - _dtoService = dtoService; - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetSimilarMovies request) - { - var result = SimilarItemsHelper.GetSimilarItemsResult(_userManager, - _itemRepo, - _libraryManager, - _userDataRepository, - _dtoService, - Logger, - request, item => item is Movie || (item is Trailer && request.IncludeTrailers), - SimilarItemsHelper.GetSimiliarityScore); - - return ToOptimizedSerializedResultUsingCache(result); - } - } -} diff --git a/MediaBrowser.Api/AlbumsService.cs b/MediaBrowser.Api/Music/AlbumsService.cs index 5787ad180..a80dd796a 100644 --- a/MediaBrowser.Api/AlbumsService.cs +++ b/MediaBrowser.Api/Music/AlbumsService.cs @@ -8,7 +8,7 @@ using System; using System.Collections.Generic; using System.Linq; -namespace MediaBrowser.Api +namespace MediaBrowser.Api.Music { [Route("/Albums/{Id}/Similar", "GET")] [Api(Description = "Finds albums similar to a given album.")] diff --git a/MediaBrowser.Api/InstantMixService.cs b/MediaBrowser.Api/Music/InstantMixService.cs index 624137677..a8446a7ef 100644 --- a/MediaBrowser.Api/InstantMixService.cs +++ b/MediaBrowser.Api/Music/InstantMixService.cs @@ -7,7 +7,7 @@ using System; using System.Collections.Generic; using System.Linq; -namespace MediaBrowser.Api +namespace MediaBrowser.Api.Music { [Route("/Songs/{Id}/InstantMix", "GET")] [Api(Description = "Creates an instant playlist based on a given song")] diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs index 926dfe955..3993866cf 100644 --- a/MediaBrowser.Api/Playback/BaseStreamingService.cs +++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs @@ -279,8 +279,19 @@ namespace MediaBrowser.Api.Playback /// </summary> /// <returns>System.Int32.</returns> /// <exception cref="System.Exception">Unrecognized MediaEncodingQuality value.</exception> - protected int GetNumberOfThreads(bool isWebm) + protected int GetNumberOfThreads(StreamState state, bool isWebm) { + // Use more when this is true. -re will keep cpu usage under control + if (state.ReadInputAtNativeFramerate) + { + if (isWebm) + { + return Math.Max(Environment.ProcessorCount - 1, 1); + } + + return 0; + } + // Webm: http://www.webmproject.org/docs/encoder-parameters/ // The decoder will usually automatically use an appropriate number of threads according to how many cores are available but it can only use multiple threads // for the coefficient data if the encoder selected --token-parts > 0 at encode time. @@ -491,16 +502,16 @@ namespace MediaBrowser.Api.Playback return string.Format("{4} -vf \"{0}scale=trunc({1}/2)*2:trunc({2}/2)*2{3}\"", yadifParam, widthParam, heightParam, assSubtitleParam, copyTsParam); } - - // If Max dimensions were supplied - //this makes my brain hurt. For width selects lowest even number between input width and width req size and selects lowest even number from in width*display aspect and requested size - if (request.MaxWidth.HasValue && request.MaxHeight.HasValue) - { - var MaxwidthParam = request.MaxWidth.Value.ToString(UsCulture); - var MaxheightParam = request.MaxHeight.Value.ToString(UsCulture); - - return string.Format("{4} -vf \"{0}scale=trunc(min(iw\\,{1})/2)*2:trunc(min((iw/dar)\\,{2})/2)*2{3}\"", yadifParam, MaxwidthParam, MaxheightParam, assSubtitleParam, copyTsParam); - } + + // If Max dimensions were supplied + //this makes my brain hurt. For width selects lowest even number between input width and width req size and selects lowest even number from in width*display aspect and requested size + if (request.MaxWidth.HasValue && request.MaxHeight.HasValue) + { + var MaxwidthParam = request.MaxWidth.Value.ToString(UsCulture); + var MaxheightParam = request.MaxHeight.Value.ToString(UsCulture); + + return string.Format("{4} -vf \"{0}scale=trunc(min(iw\\,{1})/2)*2:trunc(min((iw/dar)\\,{2})/2)*2{3}\"", yadifParam, MaxwidthParam, MaxheightParam, assSubtitleParam, copyTsParam); + } var isH264Output = outputVideoCodec.Equals("libx264", StringComparison.OrdinalIgnoreCase); @@ -603,7 +614,7 @@ namespace MediaBrowser.Api.Playback private string GetExtractedAssPath(StreamState state, bool performConversion) { var path = EncodingManager.GetSubtitleCachePath(state.MediaPath, state.SubtitleStream.Index, ".ass"); - + if (performConversion) { InputType type; @@ -987,20 +998,15 @@ namespace MediaBrowser.Api.Playback if (state.VideoStream != null) { - var isUpscaling = false; - - if (state.VideoRequest.Height.HasValue && state.VideoStream.Height.HasValue && - state.VideoRequest.Height.Value > state.VideoStream.Height.Value) - { - isUpscaling = true; - } + var isUpscaling = state.VideoRequest.Height.HasValue && state.VideoStream.Height.HasValue && + state.VideoRequest.Height.Value > state.VideoStream.Height.Value; if (state.VideoRequest.Width.HasValue && state.VideoStream.Width.HasValue && state.VideoRequest.Width.Value > state.VideoStream.Width.Value) { isUpscaling = true; } - + // Don't allow bitrate increases unless upscaling if (!isUpscaling) { @@ -1199,65 +1205,73 @@ namespace MediaBrowser.Api.Playback } else if (i == 1) { + request.Static = string.Equals("true", val, StringComparison.OrdinalIgnoreCase); + } + else if (i == 2) + { if (videoRequest != null) { videoRequest.VideoCodec = (VideoCodecs)Enum.Parse(typeof(VideoCodecs), val, true); } } - else if (i == 2) + else if (i == 3) { request.AudioCodec = (AudioCodecs)Enum.Parse(typeof(AudioCodecs), val, true); } - else if (i == 3) + else if (i == 4) { if (videoRequest != null) { videoRequest.AudioStreamIndex = int.Parse(val, UsCulture); } } - else if (i == 4) + else if (i == 5) { if (videoRequest != null) { videoRequest.SubtitleStreamIndex = int.Parse(val, UsCulture); } } - else if (i == 5) + else if (i == 6) { if (videoRequest != null) { videoRequest.VideoBitRate = int.Parse(val, UsCulture); } } - else if (i == 6) + else if (i == 7) { request.AudioBitRate = int.Parse(val, UsCulture); } - else if (i == 7) + else if (i == 8) { request.AudioChannels = int.Parse(val, UsCulture); } - else if (i == 8) + else if (i == 9) { if (videoRequest != null) { request.StartTimeTicks = long.Parse(val, UsCulture); } } - else if (i == 9) + else if (i == 10) { if (videoRequest != null) { videoRequest.Profile = val; } } - else if (i == 10) + else if (i == 11) { if (videoRequest != null) { videoRequest.Level = val; } } + else if (i == 12) + { + request.ForcedMimeType = val; + } } } @@ -1309,37 +1323,39 @@ namespace MediaBrowser.Api.Playback state.IsInputVideo = string.Equals(recording.MediaType, MediaType.Video, StringComparison.OrdinalIgnoreCase); state.PlayableStreamFileNames = new List<string>(); - if (!string.IsNullOrEmpty(recording.RecordingInfo.Path) && File.Exists(recording.RecordingInfo.Path)) + var path = recording.RecordingInfo.Path; + var mediaUrl = recording.RecordingInfo.Url; + + if (string.IsNullOrWhiteSpace(path) && string.IsNullOrWhiteSpace(mediaUrl)) { - state.MediaPath = recording.RecordingInfo.Path; + var streamInfo = await LiveTvManager.GetRecordingStream(request.Id, cancellationToken).ConfigureAwait(false); + + state.LiveTvStreamId = streamInfo.Id; + + path = streamInfo.Path; + mediaUrl = streamInfo.Url; + } + + if (!string.IsNullOrEmpty(path) && File.Exists(path)) + { + state.MediaPath = path; state.IsRemote = false; + + state.SendInputOverStandardInput = recording.RecordingInfo.Status == RecordingStatus.InProgress; } - else if (!string.IsNullOrEmpty(recording.RecordingInfo.Url)) + else if (!string.IsNullOrEmpty(mediaUrl)) { - state.MediaPath = recording.RecordingInfo.Url; + state.MediaPath = mediaUrl; state.IsRemote = true; } - else - { - var streamInfo = await LiveTvManager.GetRecordingStream(request.Id, cancellationToken).ConfigureAwait(false); - - state.LiveTvStreamId = streamInfo.Id; - if (!string.IsNullOrEmpty(streamInfo.Path) && File.Exists(streamInfo.Path)) - { - state.MediaPath = streamInfo.Path; - state.IsRemote = false; - } - else if (!string.IsNullOrEmpty(streamInfo.Url)) - { - state.MediaPath = streamInfo.Url; - state.IsRemote = true; - } + //state.RunTimeTicks = recording.RunTimeTicks; + if (recording.RecordingInfo.Status == RecordingStatus.InProgress && !state.IsRemote) + { + await Task.Delay(1000, cancellationToken).ConfigureAwait(false); } - //state.RunTimeTicks = recording.RunTimeTicks; state.ReadInputAtNativeFramerate = recording.RecordingInfo.Status == RecordingStatus.InProgress; - state.SendInputOverStandardInput = recording.RecordingInfo.Status == RecordingStatus.InProgress; state.AudioSync = "1000"; state.DeInterlace = true; } @@ -1359,6 +1375,8 @@ namespace MediaBrowser.Api.Playback { state.MediaPath = streamInfo.Path; state.IsRemote = false; + + await Task.Delay(1000, cancellationToken).ConfigureAwait(false); } else if (!string.IsNullOrEmpty(streamInfo.Url)) { @@ -1366,7 +1384,6 @@ namespace MediaBrowser.Api.Playback state.IsRemote = true; } - state.SendInputOverStandardInput = true; state.ReadInputAtNativeFramerate = true; state.AudioSync = "1000"; state.DeInterlace = true; @@ -1411,6 +1428,11 @@ namespace MediaBrowser.Api.Playback state.SubtitleStream = GetMediaStream(mediaStreams, videoRequest.SubtitleStreamIndex, MediaStreamType.Subtitle, false); state.AudioStream = GetMediaStream(mediaStreams, videoRequest.AudioStreamIndex, MediaStreamType.Audio); + if (state.VideoStream != null && state.VideoStream.IsInterlaced) + { + state.DeInterlace = true; + } + EnforceResolutionLimit(state, videoRequest); } else @@ -1420,8 +1442,8 @@ namespace MediaBrowser.Api.Playback state.HasMediaStreams = mediaStreams.Count > 0; - state.SegmentLength = state.ReadInputAtNativeFramerate ? 3 : 10; - state.HlsListSize = state.ReadInputAtNativeFramerate ? 20 : 1440; + state.SegmentLength = state.ReadInputAtNativeFramerate ? 5 : 10; + state.HlsListSize = state.ReadInputAtNativeFramerate ? 100 : 1440; return state; } diff --git a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs index 5324d2c80..aec271ff2 100644 --- a/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs +++ b/MediaBrowser.Api/Playback/Hls/BaseHlsService.cs @@ -278,7 +278,7 @@ namespace MediaBrowser.Api.Playback.Hls var itsOffset = itsOffsetMs == 0 ? string.Empty : string.Format("-itsoffset {0} ", TimeSpan.FromMilliseconds(itsOffsetMs).TotalSeconds); - var threads = GetNumberOfThreads(false); + var threads = GetNumberOfThreads(state, false); var inputModifier = GetInputModifier(state); diff --git a/MediaBrowser.Api/Playback/Progressive/AudioService.cs b/MediaBrowser.Api/Playback/Progressive/AudioService.cs index 909dd0f40..4d8d3a581 100644 --- a/MediaBrowser.Api/Playback/Progressive/AudioService.cs +++ b/MediaBrowser.Api/Playback/Progressive/AudioService.cs @@ -102,7 +102,7 @@ namespace MediaBrowser.Api.Playback.Progressive const string vn = " -vn"; - var threads = GetNumberOfThreads(false); + var threads = GetNumberOfThreads(state, false); var inputModifier = GetInputModifier(state); diff --git a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs index 023c59730..8ae61b521 100644 --- a/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs +++ b/MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs @@ -214,12 +214,16 @@ namespace MediaBrowser.Api.Playback.Progressive if (request.Static) { - return ResultFactory.GetStaticFileResult(Request, state.MediaPath, FileShare.Read, responseHeaders, isHeadRequest); + var contentType = state.GetMimeType(state.MediaPath); + + return ResultFactory.GetStaticFileResult(Request, state.MediaPath, contentType, FileShare.Read, responseHeaders, isHeadRequest); } if (outputPathExists && !ApiEntryPoint.Instance.HasActiveTranscodingJob(outputPath, TranscodingJobType.Progressive)) { - return ResultFactory.GetStaticFileResult(Request, outputPath, FileShare.Read, responseHeaders, isHeadRequest); + var contentType = state.GetMimeType(outputPath); + + return ResultFactory.GetStaticFileResult(Request, outputPath, contentType, FileShare.Read, responseHeaders, isHeadRequest); } return GetStreamResult(state, responseHeaders, isHeadRequest).Result; @@ -287,7 +291,7 @@ namespace MediaBrowser.Api.Playback.Progressive responseHeaders["Accept-Ranges"] = "none"; - var contentType = MimeTypes.GetMimeType(outputPath); + var contentType = state.GetMimeType(outputPath); // Headers only if (isHeadRequest) diff --git a/MediaBrowser.Api/Playback/Progressive/VideoService.cs b/MediaBrowser.Api/Playback/Progressive/VideoService.cs index 6deea4ffc..43dc6f0d4 100644 --- a/MediaBrowser.Api/Playback/Progressive/VideoService.cs +++ b/MediaBrowser.Api/Playback/Progressive/VideoService.cs @@ -102,7 +102,7 @@ namespace MediaBrowser.Api.Playback.Progressive format = " -f mp4 -movflags frag_keyframe+empty_moov"; } - var threads = GetNumberOfThreads(string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase)); + var threads = GetNumberOfThreads(state, string.Equals(videoCodec, "libvpx", StringComparison.OrdinalIgnoreCase)); var inputModifier = GetInputModifier(state); diff --git a/MediaBrowser.Api/Playback/StreamRequest.cs b/MediaBrowser.Api/Playback/StreamRequest.cs index a73a8f0d9..6b0375e2d 100644 --- a/MediaBrowser.Api/Playback/StreamRequest.cs +++ b/MediaBrowser.Api/Playback/StreamRequest.cs @@ -66,6 +66,8 @@ namespace MediaBrowser.Api.Playback public bool ThrowDebugError { get; set; } public string Params { get; set; } + + public string ForcedMimeType { get; set; } } public class VideoStreamRequest : StreamRequest diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs index 3874fa603..961ac0a2a 100644 --- a/MediaBrowser.Api/Playback/StreamState.cs +++ b/MediaBrowser.Api/Playback/StreamState.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Model.Entities; +using MediaBrowser.Common.Net; +using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using System.Collections.Generic; using System.IO; @@ -72,5 +73,20 @@ namespace MediaBrowser.Api.Playback public string InputVideoCodec { get; set; } public string InputAudioCodec { get; set; } + + public string GetMimeType(string outputPath) + { + if (!string.IsNullOrWhiteSpace(Request.ForcedMimeType)) + { + if (VideoRequest == null) + { + return "audio/" + Request.ForcedMimeType; + } + + return "video/" + Request.ForcedMimeType; + } + + return MimeTypes.GetMimeType(outputPath); + } } } diff --git a/MediaBrowser.Api/SearchService.cs b/MediaBrowser.Api/SearchService.cs index 18bd8c695..f46c6b8e3 100644 --- a/MediaBrowser.Api/SearchService.cs +++ b/MediaBrowser.Api/SearchService.cs @@ -1,4 +1,5 @@ -using MediaBrowser.Controller.Drawing; +using System; +using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; @@ -63,6 +64,9 @@ namespace MediaBrowser.Api [ApiMember(Name = "IncludeArtists", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] public bool IncludeArtists { 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; } + public GetSearchHints() { IncludeArtists = true; @@ -130,7 +134,8 @@ namespace MediaBrowser.Api IncludePeople = request.IncludePeople, IncludeStudios = request.IncludeStudios, StartIndex = request.StartIndex, - UserId = request.UserId + UserId = request.UserId, + IncludeItemTypes = (request.IncludeItemTypes ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray() }).ConfigureAwait(false); @@ -206,7 +211,8 @@ namespace MediaBrowser.Api result.SongCount = songs.Count; - result.Artists = _libraryManager.GetAllArtists(songs) + result.Artists = songs.SelectMany(i => i.AllArtists) + .Distinct(StringComparer.OrdinalIgnoreCase) .ToArray(); result.AlbumArtist = songs.Select(i => i.AlbumArtist).FirstOrDefault(i => !string.IsNullOrEmpty(i)); diff --git a/MediaBrowser.Api/SessionsService.cs b/MediaBrowser.Api/SessionsService.cs index f662e3817..b8ca70ba5 100644 --- a/MediaBrowser.Api/SessionsService.cs +++ b/MediaBrowser.Api/SessionsService.cs @@ -211,7 +211,7 @@ namespace MediaBrowser.Api [ApiMember(Name = "PlayableMediaTypes", Description = "A list of playable media types, comma delimited. Audio, Video, Book, Game, Photo.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")] public string PlayableMediaTypes { get; set; } } - + /// <summary> /// Class SessionsService /// </summary> @@ -368,4 +368,4 @@ namespace MediaBrowser.Api .ToList(); } } -} +}
\ No newline at end of file diff --git a/MediaBrowser.Api/SimilarItemsHelper.cs b/MediaBrowser.Api/SimilarItemsHelper.cs index d1dc801bc..1f02a63a0 100644 --- a/MediaBrowser.Api/SimilarItemsHelper.cs +++ b/MediaBrowser.Api/SimilarItemsHelper.cs @@ -73,7 +73,7 @@ namespace MediaBrowser.Api var item = string.IsNullOrEmpty(request.Id) ? (request.UserId.HasValue ? user.RootFolder : - (Folder)libraryManager.RootFolder) : dtoService.GetItemByDtoId(request.Id, request.UserId); + libraryManager.RootFolder) : dtoService.GetItemByDtoId(request.Id, request.UserId); var fields = request.GetItemFields().ToList(); @@ -81,7 +81,7 @@ namespace MediaBrowser.Api ? libraryManager.RootFolder.GetRecursiveChildren(i => i.Id != item.Id) : user.RootFolder.GetRecursiveChildren(user, i => i.Id != item.Id); - var items = GetSimilaritems(item, inputItems, includeInSearch, getSimilarityScore) + var items = GetSimilaritems(item, inputItems.Where(includeInSearch), getSimilarityScore) .ToList(); IEnumerable<BaseItem> returnItems = items; @@ -106,12 +106,12 @@ namespace MediaBrowser.Api /// </summary> /// <param name="item">The item.</param> /// <param name="inputItems">The input items.</param> - /// <param name="includeInSearch">The include in search.</param> /// <param name="getSimilarityScore">The get similarity score.</param> /// <returns>IEnumerable{BaseItem}.</returns> - internal static IEnumerable<BaseItem> GetSimilaritems(BaseItem item, IEnumerable<BaseItem> inputItems, Func<BaseItem, bool> includeInSearch, Func<BaseItem, BaseItem, int> getSimilarityScore) + internal static IEnumerable<BaseItem> GetSimilaritems(BaseItem item, IEnumerable<BaseItem> inputItems, Func<BaseItem, BaseItem, int> getSimilarityScore) { - inputItems = inputItems.Where(includeInSearch); + var itemId = item.Id; + inputItems = inputItems.Where(i => i.Id != itemId); return inputItems.Select(i => new Tuple<BaseItem, int>(i, getSimilarityScore(item, i))) .Where(i => i.Item2 > 2) @@ -153,7 +153,7 @@ namespace MediaBrowser.Api if (!string.IsNullOrEmpty(item1.OfficialRating) && string.Equals(item1.OfficialRating, item2.OfficialRating, StringComparison.OrdinalIgnoreCase)) { - points += 1; + points += 10; } // Find common genres diff --git a/MediaBrowser.Api/TvShowsService.cs b/MediaBrowser.Api/TvShowsService.cs index 629f9b233..9e58c9f53 100644 --- a/MediaBrowser.Api/TvShowsService.cs +++ b/MediaBrowser.Api/TvShowsService.cs @@ -18,7 +18,7 @@ namespace MediaBrowser.Api /// Class GetNextUpEpisodes /// </summary> [Route("/Shows/NextUp", "GET")] - [Api(("Gets a list of currently installed plugins"))] + [Api(("Gets a list of next up episodes"))] public class GetNextUpEpisodes : IReturn<ItemsResult>, IHasItemFields { /// <summary> @@ -53,6 +53,39 @@ namespace MediaBrowser.Api public string SeriesId { get; set; } } + [Route("/Shows/Upcoming", "GET")] + [Api(("Gets a list of upcoming episodes"))] + public class GetUpcomingEpisodes : IReturn<ItemsResult>, IHasItemFields + { + /// <summary> + /// Gets or sets the user id. + /// </summary> + /// <value>The user id.</value> + [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")] + public Guid UserId { get; set; } + + /// <summary> + /// Skips over a given number of items within the results. Use for paging. + /// </summary> + /// <value>The start index.</value> + [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; } + + /// <summary> + /// The maximum number of items to return + /// </summary> + /// <value>The limit.</value> + [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; } + + /// <summary> + /// Fields to return within the items, in addition to basic information + /// </summary> + /// <value>The fields.</value> + [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, OverviewHtml, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)] + public string Fields { get; set; } + } + [Route("/Shows/{Id}/Similar", "GET")] [Api(Description = "Finds tv shows similar to a given one.")] public class GetSimilarShows : BaseGetSimilarItemsFromItem @@ -85,7 +118,7 @@ namespace MediaBrowser.Api [ApiMember(Name = "SeasonId", Description = "Optional. Filter by season id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] public string SeasonId { 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; } @@ -186,6 +219,39 @@ namespace MediaBrowser.Api return ToOptimizedSerializedResultUsingCache(result); } + public object Get(GetUpcomingEpisodes request) + { + var user = _userManager.GetUserById(request.UserId); + + var items = GetAllLibraryItems(request.UserId, _userManager, _libraryManager) + .OfType<Episode>(); + + var itemsList = _libraryManager.Sort(items, user, new[] { "PremiereDate", "AirTime", "SortName" }, SortOrder.Ascending) + .Cast<Episode>() + .ToList(); + + var unairedEpisodes = itemsList.Where(i => i.IsUnaired).ToList(); + + var minPremiereDate = DateTime.Now.Date.AddDays(-1).ToUniversalTime(); + var previousEpisodes = itemsList.Where(i => !i.IsUnaired && (i.PremiereDate ?? DateTime.MinValue) >= minPremiereDate).ToList(); + + previousEpisodes.AddRange(unairedEpisodes); + + var pagedItems = ApplyPaging(previousEpisodes, request.StartIndex, request.Limit); + + var fields = request.GetItemFields().ToList(); + + var returnItems = pagedItems.Select(i => _dtoService.GetBaseItemDto(i, fields, user)).ToArray(); + + var result = new ItemsResult + { + TotalRecordCount = itemsList.Count, + Items = returnItems + }; + + return ToOptimizedSerializedResultUsingCache(result); + } + /// <summary> /// Gets the specified request. /// </summary> @@ -198,7 +264,7 @@ namespace MediaBrowser.Api var itemsList = GetNextUpEpisodes(request) .ToList(); - var pagedItems = ApplyPaging(request, itemsList); + var pagedItems = ApplyPaging(itemsList, request.StartIndex, request.Limit); var fields = request.GetItemFields().ToList(); @@ -234,11 +300,13 @@ namespace MediaBrowser.Api return FilterSeries(request, series) .AsParallel() - .Select(i => GetNextUp(i, currentUser, request).Item1) - .Where(i => i != null) + .Select(i => GetNextUp(i, currentUser)) + .Where(i => i.Item1 != null) .OrderByDescending(i => { - var seriesUserData = _userDataManager.GetUserData(user.Id, i.Series.GetUserDataKey()); + var episode = i.Item1; + + var seriesUserData = _userDataManager.GetUserData(user.Id, episode.Series.GetUserDataKey()); if (seriesUserData.IsFavorite) { @@ -252,7 +320,9 @@ namespace MediaBrowser.Api return 0; }) - .ThenByDescending(i => i.PremiereDate ?? DateTime.MinValue); + .ThenByDescending(i =>i.Item2) + .ThenByDescending(i => i.Item1.PremiereDate ?? DateTime.MinValue) + .Select(i => i.Item1); } /// <summary> @@ -260,9 +330,8 @@ namespace MediaBrowser.Api /// </summary> /// <param name="series">The series.</param> /// <param name="user">The user.</param> - /// <param name="request">The request.</param> /// <returns>Task{Episode}.</returns> - private Tuple<Episode, DateTime> GetNextUp(Series series, User user, GetNextUpEpisodes request) + private Tuple<Episode, DateTime> GetNextUp(Series series, User user) { // Get them in display order, then reverse var allEpisodes = series.GetSeasons(user, true, true) @@ -321,21 +390,22 @@ namespace MediaBrowser.Api /// <summary> /// Applies the paging. /// </summary> - /// <param name="request">The request.</param> /// <param name="items">The items.</param> + /// <param name="startIndex">The start index.</param> + /// <param name="limit">The limit.</param> /// <returns>IEnumerable{BaseItem}.</returns> - private IEnumerable<BaseItem> ApplyPaging(GetNextUpEpisodes request, IEnumerable<BaseItem> items) + private IEnumerable<BaseItem> ApplyPaging(IEnumerable<BaseItem> items, int? startIndex, int? limit) { // Start at - if (request.StartIndex.HasValue) + if (startIndex.HasValue) { - items = items.Skip(request.StartIndex.Value); + items = items.Skip(startIndex.Value); } // Return limit - if (request.Limit.HasValue) + if (limit.HasValue) { - items = items.Take(request.Limit.Value); + items = items.Take(limit.Value); } return items; @@ -409,7 +479,7 @@ namespace MediaBrowser.Api return items; } - + public object Get(GetEpisodes request) { var user = _userManager.GetUserById(request.UserId); @@ -435,7 +505,7 @@ namespace MediaBrowser.Api { throw new ResourceNotFoundException("No season exists with Id " + request.SeasonId); } - + episodes = season.GetEpisodes(user); } diff --git a/MediaBrowser.Api/UserLibrary/ArtistsService.cs b/MediaBrowser.Api/UserLibrary/ArtistsService.cs index a96630efe..9972ac3ef 100644 --- a/MediaBrowser.Api/UserLibrary/ArtistsService.cs +++ b/MediaBrowser.Api/UserLibrary/ArtistsService.cs @@ -85,10 +85,10 @@ namespace MediaBrowser.Api.UserLibrary { var user = UserManager.GetUserById(request.UserId.Value); - return DtoService.GetBaseItemDto(item, fields.ToList(), user); + return DtoService.GetItemByNameDto(item, fields.ToList(), user); } - return DtoService.GetBaseItemDto(item, fields.ToList()); + return DtoService.GetItemByNameDto(item, fields.ToList()); } /// <summary> @@ -111,7 +111,10 @@ namespace MediaBrowser.Api.UserLibrary /// <returns>IEnumerable{Tuple{System.StringFunc{System.Int32}}}.</returns> protected override IEnumerable<MusicArtist> GetAllItems(GetItemsByName request, IEnumerable<BaseItem> items) { - return LibraryManager.GetAllArtists(items) + return items + .OfType<Audio>() + .SelectMany(i => i.AllArtists) + .Distinct(StringComparer.OrdinalIgnoreCase) .Select(name => { try @@ -126,10 +129,5 @@ namespace MediaBrowser.Api.UserLibrary }).Where(i => i != null); } - - protected override IEnumerable<BaseItem> GetLibraryItems(MusicArtist item, IEnumerable<BaseItem> libraryItems) - { - return libraryItems.OfType<IHasArtist>().Where(i => i.HasArtist(item.Name)).Cast<BaseItem>(); - } } } diff --git a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs index 5c2c9967e..d21014dfe 100644 --- a/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs +++ b/MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs @@ -56,15 +56,21 @@ namespace MediaBrowser.Api.UserLibrary { User user = null; BaseItem item; + List<BaseItem> libraryItems; if (request.UserId.HasValue) { user = UserManager.GetUserById(request.UserId.Value); item = string.IsNullOrEmpty(request.ParentId) ? user.RootFolder : DtoService.GetItemByDtoId(request.ParentId, user.Id); + + libraryItems = user.RootFolder.GetRecursiveChildren(user).ToList(); + } else { item = string.IsNullOrEmpty(request.ParentId) ? LibraryManager.RootFolder : DtoService.GetItemByDtoId(request.ParentId); + + libraryItems = LibraryManager.RootFolder.RecursiveChildren.ToList(); } IEnumerable<BaseItem> items; @@ -93,7 +99,7 @@ namespace MediaBrowser.Api.UserLibrary var filteredItems = FilterItems(request, extractedItems, user); - filteredItems = FilterByLibraryItems(request, filteredItems, user); + filteredItems = FilterByLibraryItems(request, filteredItems, user, libraryItems); filteredItems = ItemsService.ApplySortOrder(request, filteredItems, user, LibraryManager).Cast<TItemType>(); @@ -122,45 +128,39 @@ namespace MediaBrowser.Api.UserLibrary var fields = request.GetItemFields().ToList(); - var dtos = ibnItems.Select(i => GetDto(i, user, fields)); + var tuples = ibnItems.Select(i => new Tuple<TItemType, List<BaseItem>>(i, i.GetTaggedItems(libraryItems).ToList())); + + var dtos = tuples.Select(i => GetDto(i.Item1, user, fields, i.Item2)); result.Items = dtos.Where(i => i != null).ToArray(); return result; } - private IEnumerable<TItemType> FilterByLibraryItems(GetItemsByName request, IEnumerable<TItemType> items, User user) + private IEnumerable<TItemType> FilterByLibraryItems(GetItemsByName request, IEnumerable<TItemType> items, User user, IEnumerable<BaseItem> libraryItems) { var filters = request.GetFilters().ToList(); if (filters.Contains(ItemFilter.IsPlayed)) { - var libraryItems = user.RootFolder.GetRecursiveChildren(user).ToList(); - - items = items.Where(i => GetLibraryItems(i, libraryItems).All(l => l.IsPlayed(user))); + items = items.Where(i => i.GetTaggedItems(libraryItems).All(l => l.IsPlayed(user))); } if (filters.Contains(ItemFilter.IsUnplayed)) { - var libraryItems = user.RootFolder.GetRecursiveChildren(user).ToList(); - - items = items.Where(i => GetLibraryItems(i, libraryItems).All(l => l.IsUnplayed(user))); + items = items.Where(i => i.GetTaggedItems(libraryItems).All(l => l.IsUnplayed(user))); } if (request.IsPlayed.HasValue) { var val = request.IsPlayed.Value; - var libraryItems = user.RootFolder.GetRecursiveChildren(user).ToList(); - - items = items.Where(i => GetLibraryItems(i, libraryItems).All(l => l.IsPlayed(user)) == val); + items = items.Where(i => i.GetTaggedItems(libraryItems).All(l => l.IsPlayed(user)) == val); } return items; } - protected abstract IEnumerable<BaseItem> GetLibraryItems(TItemType item, IEnumerable<BaseItem> libraryItems); - /// <summary> /// Filters the items. /// </summary> @@ -174,6 +174,10 @@ namespace MediaBrowser.Api.UserLibrary { items = items.Where(i => string.Compare(request.NameStartsWithOrGreater, i.SortName, StringComparison.CurrentCultureIgnoreCase) < 1); } + if (!string.IsNullOrEmpty(request.NameStartsWith)) + { + items = items.Where(i => string.Compare(request.NameStartsWith, i.SortName.Substring(0, 1), StringComparison.CurrentCultureIgnoreCase) == 0); + } if (!string.IsNullOrEmpty(request.NameLessThan)) { @@ -288,11 +292,11 @@ namespace MediaBrowser.Api.UserLibrary /// <param name="item">The item.</param> /// <param name="user">The user.</param> /// <param name="fields">The fields.</param> + /// <param name="libraryItems">The library items.</param> /// <returns>Task{DtoBaseItem}.</returns> - private BaseItemDto GetDto(TItemType item, User user, List<ItemFields> fields) + private BaseItemDto GetDto(TItemType item, User user, List<ItemFields> fields, List<BaseItem> libraryItems) { - var dto = user == null ? DtoService.GetBaseItemDto(item, fields) : - DtoService.GetBaseItemDto(item, fields, user); + var dto = DtoService.GetItemByNameDto(item, fields, libraryItems, user); return dto; } @@ -313,9 +317,12 @@ namespace MediaBrowser.Api.UserLibrary [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/GameGenresService.cs b/MediaBrowser.Api/UserLibrary/GameGenresService.cs index d282ee091..34eadc3c3 100644 --- a/MediaBrowser.Api/UserLibrary/GameGenresService.cs +++ b/MediaBrowser.Api/UserLibrary/GameGenresService.cs @@ -76,10 +76,10 @@ namespace MediaBrowser.Api.UserLibrary { var user = UserManager.GetUserById(request.UserId.Value); - return DtoService.GetBaseItemDto(item, fields.ToList(), user); + return DtoService.GetItemByNameDto(item, fields.ToList(), user); } - return DtoService.GetBaseItemDto(item, fields.ToList()); + return DtoService.GetItemByNameDto(item, fields.ToList()); } /// <summary> @@ -109,10 +109,5 @@ namespace MediaBrowser.Api.UserLibrary .Distinct(StringComparer.OrdinalIgnoreCase) .Select(name => LibraryManager.GetGameGenre(name)); } - - protected override IEnumerable<BaseItem> GetLibraryItems(GameGenre item, IEnumerable<BaseItem> libraryItems) - { - return libraryItems.Where(i => (i is Game) && i.Genres.Contains(item.Name, StringComparer.OrdinalIgnoreCase)); - } } } diff --git a/MediaBrowser.Api/UserLibrary/GenresService.cs b/MediaBrowser.Api/UserLibrary/GenresService.cs index 092c63882..5d362c61a 100644 --- a/MediaBrowser.Api/UserLibrary/GenresService.cs +++ b/MediaBrowser.Api/UserLibrary/GenresService.cs @@ -81,10 +81,10 @@ namespace MediaBrowser.Api.UserLibrary { var user = UserManager.GetUserById(request.UserId.Value); - return DtoService.GetBaseItemDto(item, fields.ToList(), user); + return DtoService.GetItemByNameDto(item, fields.ToList(), user); } - return DtoService.GetBaseItemDto(item, fields.ToList()); + return DtoService.GetItemByNameDto(item, fields.ToList()); } /// <summary> @@ -112,10 +112,5 @@ namespace MediaBrowser.Api.UserLibrary .Distinct(StringComparer.OrdinalIgnoreCase) .Select(name => LibraryManager.GetGenre(name)); } - - protected override IEnumerable<BaseItem> GetLibraryItems(Genre item, IEnumerable<BaseItem> libraryItems) - { - return libraryItems.Where(i => !(i is Game) && !(i is IHasMusicGenres) && i.Genres.Contains(item.Name, StringComparer.OrdinalIgnoreCase)); - } } } diff --git a/MediaBrowser.Api/UserLibrary/ItemsService.cs b/MediaBrowser.Api/UserLibrary/ItemsService.cs index 871c9aecb..b040d3dd8 100644 --- a/MediaBrowser.Api/UserLibrary/ItemsService.cs +++ b/MediaBrowser.Api/UserLibrary/ItemsService.cs @@ -111,6 +111,12 @@ namespace MediaBrowser.Api.UserLibrary [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; } @@ -768,6 +774,15 @@ namespace MediaBrowser.Api.UserLibrary { items = items.Where(i => string.Compare(request.NameStartsWithOrGreater, i.SortName, StringComparison.CurrentCultureIgnoreCase) < 1); } + if (!string.IsNullOrEmpty(request.NameStartsWith)) + { + items = items.Where(i => string.Compare(request.NameStartsWith, i.SortName.Substring(0, 1), StringComparison.CurrentCultureIgnoreCase) == 0); + } + + if (!string.IsNullOrEmpty(request.NameLessThan)) + { + items = items.Where(i => string.Compare(request.NameLessThan, i.SortName, StringComparison.CurrentCultureIgnoreCase) == 1); + } if (!string.IsNullOrEmpty(request.AlbumArtistStartsWithOrGreater)) { diff --git a/MediaBrowser.Api/UserLibrary/MusicGenresService.cs b/MediaBrowser.Api/UserLibrary/MusicGenresService.cs index 3f960ccbe..9b7a941d8 100644 --- a/MediaBrowser.Api/UserLibrary/MusicGenresService.cs +++ b/MediaBrowser.Api/UserLibrary/MusicGenresService.cs @@ -76,10 +76,10 @@ namespace MediaBrowser.Api.UserLibrary { var user = UserManager.GetUserById(request.UserId.Value); - return DtoService.GetBaseItemDto(item, fields.ToList(), user); + return DtoService.GetItemByNameDto(item, fields.ToList(), user); } - return DtoService.GetBaseItemDto(item, fields.ToList()); + return DtoService.GetItemByNameDto(item, fields.ToList()); } /// <summary> @@ -109,10 +109,5 @@ namespace MediaBrowser.Api.UserLibrary .Distinct(StringComparer.OrdinalIgnoreCase) .Select(name => LibraryManager.GetMusicGenre(name)); } - - protected override IEnumerable<BaseItem> GetLibraryItems(MusicGenre item, IEnumerable<BaseItem> libraryItems) - { - return libraryItems.Where(i => (i is IHasMusicGenres) && i.Genres.Contains(item.Name, StringComparer.OrdinalIgnoreCase)); - } } } diff --git a/MediaBrowser.Api/UserLibrary/PersonsService.cs b/MediaBrowser.Api/UserLibrary/PersonsService.cs index 32700d21a..f7ea4198d 100644 --- a/MediaBrowser.Api/UserLibrary/PersonsService.cs +++ b/MediaBrowser.Api/UserLibrary/PersonsService.cs @@ -93,10 +93,10 @@ namespace MediaBrowser.Api.UserLibrary { var user = UserManager.GetUserById(request.UserId.Value); - return DtoService.GetBaseItemDto(item, fields.ToList(), user); + return DtoService.GetItemByNameDto(item, fields.ToList(), user); } - return DtoService.GetBaseItemDto(item, fields.ToList()); + return DtoService.GetItemByNameDto(item, fields.ToList()); } /// <summary> @@ -163,10 +163,5 @@ namespace MediaBrowser.Api.UserLibrary people.Where(p => personTypes.Contains(p.Type ?? string.Empty, StringComparer.OrdinalIgnoreCase) || personTypes.Contains(p.Role ?? string.Empty, StringComparer.OrdinalIgnoreCase)); } - - protected override IEnumerable<BaseItem> GetLibraryItems(Person item, IEnumerable<BaseItem> libraryItems) - { - return libraryItems.Where(i => i.People.Any(p => string.Equals(p.Name, item.Name, StringComparison.OrdinalIgnoreCase))); - } } } diff --git a/MediaBrowser.Api/UserLibrary/StudiosService.cs b/MediaBrowser.Api/UserLibrary/StudiosService.cs index cf4e333e1..fc7fdd160 100644 --- a/MediaBrowser.Api/UserLibrary/StudiosService.cs +++ b/MediaBrowser.Api/UserLibrary/StudiosService.cs @@ -81,10 +81,10 @@ namespace MediaBrowser.Api.UserLibrary { var user = UserManager.GetUserById(request.UserId.Value); - return DtoService.GetBaseItemDto(item, fields.ToList(), user); + return DtoService.GetItemByNameDto(item, fields.ToList(), user); } - return DtoService.GetBaseItemDto(item, fields.ToList()); + return DtoService.GetItemByNameDto(item, fields.ToList()); } /// <summary> @@ -114,10 +114,5 @@ namespace MediaBrowser.Api.UserLibrary .Distinct(StringComparer.OrdinalIgnoreCase) .Select(name => LibraryManager.GetStudio(name)); } - - protected override IEnumerable<BaseItem> GetLibraryItems(Studio item, IEnumerable<BaseItem> libraryItems) - { - return libraryItems.Where(i => i.Studios.Contains(item.Name, StringComparer.OrdinalIgnoreCase)); - } } } diff --git a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs index c2abc6ad1..e026aec03 100644 --- a/MediaBrowser.Api/UserLibrary/UserLibraryService.cs +++ b/MediaBrowser.Api/UserLibrary/UserLibraryService.cs @@ -680,18 +680,34 @@ namespace MediaBrowser.Api.UserLibrary /// <param name="request">The request.</param> public object Post(MarkPlayedItem request) { + var result = MarkPlayed(request).Result; + + return ToOptimizedResult(result); + } + + private async Task<UserItemDataDto> MarkPlayed(MarkPlayedItem request) + { var user = _userManager.GetUserById(request.UserId); DateTime? datePlayed = null; - + if (!string.IsNullOrEmpty(request.DatePlayed)) { datePlayed = DateTime.ParseExact(request.DatePlayed, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal); } - var task = UpdatePlayedStatus(user, request.Id, true, datePlayed); + var session = GetSession(); + + var dto = await UpdatePlayedStatus(user, request.Id, true, datePlayed).ConfigureAwait(false); - return ToOptimizedResult(task.Result); + foreach (var additionalUserInfo in session.AdditionalUsers) + { + var additionalUser = _userManager.GetUserById(new Guid(additionalUserInfo.UserId)); + + await UpdatePlayedStatus(additionalUser, request.Id, true, datePlayed).ConfigureAwait(false); + } + + return dto; } private SessionInfo GetSession() @@ -780,11 +796,27 @@ namespace MediaBrowser.Api.UserLibrary /// <param name="request">The request.</param> public object Delete(MarkUnplayedItem request) { + var task = MarkUnplayed(request); + + return ToOptimizedResult(task.Result); + } + + private async Task<UserItemDataDto> MarkUnplayed(MarkUnplayedItem request) + { var user = _userManager.GetUserById(request.UserId); - var task = UpdatePlayedStatus(user, request.Id, false, null); + var session = GetSession(); + + var dto = await UpdatePlayedStatus(user, request.Id, false, null).ConfigureAwait(false); - return ToOptimizedResult(task.Result); + foreach (var additionalUserInfo in session.AdditionalUsers) + { + var additionalUser = _userManager.GetUserById(new Guid(additionalUserInfo.UserId)); + + await UpdatePlayedStatus(additionalUser, request.Id, false, null).ConfigureAwait(false); + } + + return dto; } /// <summary> diff --git a/MediaBrowser.Api/UserLibrary/YearsService.cs b/MediaBrowser.Api/UserLibrary/YearsService.cs index 7024d5256..b8b0aa9e9 100644 --- a/MediaBrowser.Api/UserLibrary/YearsService.cs +++ b/MediaBrowser.Api/UserLibrary/YearsService.cs @@ -7,7 +7,6 @@ using MediaBrowser.Model.Querying; using ServiceStack; using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; namespace MediaBrowser.Api.UserLibrary @@ -81,10 +80,10 @@ namespace MediaBrowser.Api.UserLibrary { var user = UserManager.GetUserById(request.UserId.Value); - return DtoService.GetBaseItemDto(item, fields.ToList(), user); + return DtoService.GetItemByNameDto(item, fields.ToList(), user); } - return DtoService.GetBaseItemDto(item, fields.ToList()); + return DtoService.GetItemByNameDto(item, fields.ToList()); } /// <summary> @@ -115,19 +114,5 @@ namespace MediaBrowser.Api.UserLibrary .Distinct() .Select(year => LibraryManager.GetYear(year)); } - - protected readonly CultureInfo UsCulture = new CultureInfo("en-US"); - - protected override IEnumerable<BaseItem> GetLibraryItems(Year item, IEnumerable<BaseItem> libraryItems) - { - int year; - - if (!int.TryParse(item.Name, NumberStyles.Integer, UsCulture, out year)) - { - return libraryItems; - } - - return libraryItems.Where(i => i.ProductionYear.HasValue && i.ProductionYear.Value == year); - } } } diff --git a/MediaBrowser.Controller/Channels/ChannelItemInfo.cs b/MediaBrowser.Controller/Channels/ChannelItemInfo.cs new file mode 100644 index 000000000..496fbfbf4 --- /dev/null +++ b/MediaBrowser.Controller/Channels/ChannelItemInfo.cs @@ -0,0 +1,67 @@ +using MediaBrowser.Controller.Entities; +using System.Collections.Generic; + +namespace MediaBrowser.Controller.Channels +{ + public class ChannelItemInfo + { + public string Name { get; set; } + + public string Id { get; set; } + + public ChannelItemType Type { get; set; } + + public string OfficialRating { get; set; } + + public string Overview { get; set; } + + public List<string> Genres { get; set; } + + public List<PersonInfo> People { get; set; } + + public float? CommunityRating { get; set; } + + public long? RunTimeTicks { get; set; } + + public bool IsInfinite { get; set; } + + public string ImageUrl { get; set; } + + public ChannelMediaType MediaType { get; set; } + + public ChannelMediaContentType ContentType { get; set; } + + public ChannelItemInfo() + { + Genres = new List<string>(); + People = new List<PersonInfo>(); + } + } + + public enum ChannelItemType + { + Media = 0, + + Category = 1 + } + + public enum ChannelMediaType + { + Audio = 0, + + Video = 1 + } + + public enum ChannelMediaContentType + { + Clip = 0, + + Podcast = 1, + + Trailer = 2, + + Movie = 3, + + Episode = 4 + } +} diff --git a/MediaBrowser.Controller/Channels/IChannel.cs b/MediaBrowser.Controller/Channels/IChannel.cs new file mode 100644 index 000000000..ba1bd4083 --- /dev/null +++ b/MediaBrowser.Controller/Channels/IChannel.cs @@ -0,0 +1,59 @@ +using MediaBrowser.Controller.Entities; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Channels +{ + public interface IChannel + { + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + string Name { get; } + + /// <summary> + /// Gets the home page URL. + /// </summary> + /// <value>The home page URL.</value> + string HomePageUrl { get; } + + /// <summary> + /// Gets the capabilities. + /// </summary> + /// <returns>ChannelCapabilities.</returns> + ChannelCapabilities GetCapabilities(); + + /// <summary> + /// Searches the specified search term. + /// </summary> + /// <param name="searchTerm">The search term.</param> + /// <param name="user">The user.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task{IEnumerable{ChannelItemInfo}}.</returns> + Task<IEnumerable<ChannelItemInfo>> Search(string searchTerm, User user, CancellationToken cancellationToken); + + /// <summary> + /// Gets the channel items. + /// </summary> + /// <param name="user">The user.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task{IEnumerable{ChannelItem}}.</returns> + Task<IEnumerable<ChannelItemInfo>> GetChannelItems(User user, CancellationToken cancellationToken); + + /// <summary> + /// Gets the channel items. + /// </summary> + /// <param name="categoryId">The category identifier.</param> + /// <param name="user">The user.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task{IEnumerable{ChannelItem}}.</returns> + Task<IEnumerable<ChannelItemInfo>> GetChannelItems(string categoryId, User user, CancellationToken cancellationToken); + } + + public class ChannelCapabilities + { + public bool CanSearch { get; set; } + } +} diff --git a/MediaBrowser.Controller/Channels/IChannelManager.cs b/MediaBrowser.Controller/Channels/IChannelManager.cs new file mode 100644 index 000000000..561ab555b --- /dev/null +++ b/MediaBrowser.Controller/Channels/IChannelManager.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Channels +{ + public interface IChannelManager + { + } +} diff --git a/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs b/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs new file mode 100644 index 000000000..e147e0905 --- /dev/null +++ b/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs @@ -0,0 +1,22 @@ +using MediaBrowser.Model.Entities; +using System; +using System.Collections.Generic; + +namespace MediaBrowser.Controller.Collections +{ + public class CollectionCreationOptions : IHasProviderIds + { + public string Name { get; set; } + + public Guid? ParentId { get; set; } + + public bool IsLocked { get; set; } + + public Dictionary<string, string> ProviderIds { get; set; } + + public CollectionCreationOptions() + { + ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); + } + } +} diff --git a/MediaBrowser.Controller/Collections/ICollectionManager.cs b/MediaBrowser.Controller/Collections/ICollectionManager.cs new file mode 100644 index 000000000..d7bc178ad --- /dev/null +++ b/MediaBrowser.Controller/Collections/ICollectionManager.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Collections +{ + public interface ICollectionManager + { + /// <summary> + /// Creates the collection. + /// </summary> + /// <param name="options">The options.</param> + /// <returns>Task.</returns> + Task CreateCollection(CollectionCreationOptions options); + + /// <summary> + /// Adds to collection. + /// </summary> + /// <param name="collectionId">The collection identifier.</param> + /// <param name="itemIds">The item ids.</param> + /// <returns>Task.</returns> + Task AddToCollection(Guid collectionId, IEnumerable<Guid> itemIds); + + /// <summary> + /// Removes from collection. + /// </summary> + /// <param name="collectionId">The collection identifier.</param> + /// <param name="itemIds">The item ids.</param> + /// <returns>Task.</returns> + Task RemoveFromCollection(Guid collectionId, IEnumerable<Guid> itemIds); + } +} diff --git a/MediaBrowser.Controller/Dto/IDtoService.cs b/MediaBrowser.Controller/Dto/IDtoService.cs index fd5ccac5b..606b51628 100644 --- a/MediaBrowser.Controller/Dto/IDtoService.cs +++ b/MediaBrowser.Controller/Dto/IDtoService.cs @@ -73,5 +73,26 @@ namespace MediaBrowser.Controller.Dto /// <param name="owner">The owner.</param> /// <returns>Task{BaseItemDto}.</returns> BaseItemDto GetBaseItemDto(BaseItem item, List<ItemFields> fields, User user = null, BaseItem owner = null); + + /// <summary> + /// Gets the item by name dto. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="fields">The fields.</param> + /// <param name="user">The user.</param> + /// <returns>BaseItemDto.</returns> + BaseItemDto GetItemByNameDto<T>(T item, List<ItemFields> fields, User user = null) + where T : BaseItem, IItemByName; + + /// <summary> + /// Gets the item by name dto. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="fields">The fields.</param> + /// <param name="taggedItems">The tagged items.</param> + /// <param name="user">The user.</param> + /// <returns>BaseItemDto.</returns> + BaseItemDto GetItemByNameDto<T>(T item, List<ItemFields> fields, List<BaseItem> taggedItems, User user = null) + where T : BaseItem, IItemByName; } } diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs index 73e276f3b..8eb6236d1 100644 --- a/MediaBrowser.Controller/Entities/Audio/Audio.cs +++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs @@ -66,6 +66,24 @@ namespace MediaBrowser.Controller.Entities.Audio /// <value>The artist.</value> public List<string> Artists { get; set; } + [IgnoreDataMember] + public List<string> AllArtists + { + get + { + var list = new List<string>(); + + if (!string.IsNullOrEmpty(AlbumArtist)) + { + list.Add(AlbumArtist); + } + list.AddRange(Artists); + + return list; + + } + } + /// <summary> /// Gets or sets the album. /// </summary> diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs index 11cf441f7..0a5d8eec0 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs @@ -1,12 +1,10 @@ using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using System; using System.Collections.Generic; using System.Linq; -using System.Runtime.Serialization; using System.Threading; using System.Threading.Tasks; @@ -17,9 +15,6 @@ namespace MediaBrowser.Controller.Entities.Audio /// </summary> public class MusicArtist : Folder, IMetadataContainer, IItemByName, IHasMusicGenres, IHasDualAccess, IHasTags, IHasProductionLocations, IHasLookupInfo<ArtistInfo> { - [IgnoreDataMember] - public List<ItemByNameCounts> UserItemCountList { get; set; } - public bool IsAccessedByName { get; set; } /// <summary> @@ -65,7 +60,6 @@ namespace MediaBrowser.Controller.Entities.Audio public MusicArtist() { - UserItemCountList = new List<ItemByNameCounts>(); Tags = new List<string>(); ProductionLocations = new List<string>(); } @@ -230,5 +224,10 @@ namespace MediaBrowser.Controller.Entities.Audio return info; } + + public IEnumerable<BaseItem> GetTaggedItems(IEnumerable<BaseItem> inputItems) + { + return inputItems.OfType<IHasArtist>().Where(i => i.HasArtist(Name)).Cast<BaseItem>(); + } } } diff --git a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs index 5e1d4c3c9..bce9da4d1 100644 --- a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs +++ b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs @@ -1,7 +1,6 @@ -using System.Runtime.Serialization; -using MediaBrowser.Model.Dto; -using System; +using System; using System.Collections.Generic; +using System.Linq; namespace MediaBrowser.Controller.Entities.Audio { @@ -10,11 +9,6 @@ namespace MediaBrowser.Controller.Entities.Audio /// </summary> public class MusicGenre : BaseItem, IItemByName { - public MusicGenre() - { - UserItemCountList = new List<ItemByNameCounts>(); - } - /// <summary> /// Gets the user data key. /// </summary> @@ -24,9 +18,6 @@ namespace MediaBrowser.Controller.Entities.Audio return "MusicGenre-" + Name; } - [IgnoreDataMember] - public List<ItemByNameCounts> UserItemCountList { get; set; } - /// <summary> /// Returns the folder containing the item. /// If the item is a folder, it returns the folder itself @@ -51,5 +42,10 @@ namespace MediaBrowser.Controller.Entities.Audio return false; } } + + public IEnumerable<BaseItem> GetTaggedItems(IEnumerable<BaseItem> inputItems) + { + return inputItems.Where(i => (i is IHasMusicGenres) && i.Genres.Contains(Name, StringComparer.OrdinalIgnoreCase)); + } } } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 923673bd8..23f8ac31a 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -125,6 +125,15 @@ namespace MediaBrowser.Controller.Entities } [IgnoreDataMember] + public virtual bool IsHidden + { + get + { + return false; + } + } + + [IgnoreDataMember] public virtual bool IsOwnedItem { get @@ -1175,7 +1184,7 @@ namespace MediaBrowser.Controller.Entities return GetImageInfo(type, imageIndex) != null; } - public void SetImagePath(ImageType type, int index, FileInfo file) + public void SetImagePath(ImageType type, int index, FileSystemInfo file) { if (type == ImageType.Chapter) { @@ -1330,7 +1339,7 @@ namespace MediaBrowser.Controller.Entities /// <param name="images">The images.</param> /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns> /// <exception cref="System.ArgumentException">Cannot call AddImages with chapter images</exception> - public bool AddImages(ImageType imageType, IEnumerable<FileInfo> images) + public bool AddImages(ImageType imageType, IEnumerable<FileSystemInfo> images) { if (imageType == ImageType.Chapter) { diff --git a/MediaBrowser.Controller/Entities/BasePluginFolder.cs b/MediaBrowser.Controller/Entities/BasePluginFolder.cs index 8f7071000..29d66718c 100644 --- a/MediaBrowser.Controller/Entities/BasePluginFolder.cs +++ b/MediaBrowser.Controller/Entities/BasePluginFolder.cs @@ -1,5 +1,4 @@ -using MediaBrowser.Model.Entities; - + namespace MediaBrowser.Controller.Entities { /// <summary> @@ -8,18 +7,6 @@ namespace MediaBrowser.Controller.Entities /// </summary> public abstract class BasePluginFolder : Folder, ICollectionFolder, IByReferenceItem { - /// <summary> - /// Gets or sets the type of the location. - /// </summary> - /// <value>The type of the location.</value> - public override LocationType LocationType - { - get - { - return LocationType.Virtual; - } - } - protected BasePluginFolder() { DisplayMediaType = "CollectionFolder"; diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 627f657ab..ee371680e 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -264,7 +264,7 @@ namespace MediaBrowser.Controller.Entities [IgnoreDataMember] public IEnumerable<BaseItem> Children { - get { return ActualChildren; } + get { return ActualChildren.Where(i => !i.IsHidden); } } /// <summary> @@ -745,9 +745,9 @@ namespace MediaBrowser.Controller.Entities var list = new List<BaseItem>(); - AddChildrenToList(user, includeLinkedChildren, list, false, null); + var hasLinkedChildren = AddChildrenToList(user, includeLinkedChildren, list, false, null); - return list; + return hasLinkedChildren ? list.DistinctBy(i => i.Id).ToList() : list; } /// <summary> @@ -905,13 +905,6 @@ namespace MediaBrowser.Controller.Entities /// <returns>BaseItem.</returns> private BaseItem GetLinkedChild(LinkedChild info) { - if (string.IsNullOrEmpty(info.Path)) - { - throw new ArgumentException("Encountered linked child with empty path."); - } - - BaseItem item = null; - // First get using the cached Id if (info.ItemId.HasValue) { @@ -920,20 +913,19 @@ namespace MediaBrowser.Controller.Entities return null; } - item = LibraryManager.GetItemById(info.ItemId.Value); - } + var itemById = LibraryManager.GetItemById(info.ItemId.Value); - // If still null, search by path - if (item == null) - { - item = LibraryManager.RootFolder.FindByPath(info.Path); + if (itemById != null) + { + return itemById; + } } + var item = FindLinkedChild(info); + // If still null, log if (item == null) { - Logger.Warn("Unable to find linked item at {0}", info.Path); - // Don't keep searching over and over info.ItemId = Guid.Empty; } @@ -946,6 +938,43 @@ namespace MediaBrowser.Controller.Entities return item; } + private BaseItem FindLinkedChild(LinkedChild info) + { + if (!string.IsNullOrEmpty(info.Path)) + { + var itemByPath = LibraryManager.RootFolder.FindByPath(info.Path); + + if (itemByPath == null) + { + Logger.Warn("Unable to find linked item at path {0}", info.Path); + } + + return itemByPath; + } + + if (!string.IsNullOrWhiteSpace(info.ItemName) && !string.IsNullOrWhiteSpace(info.ItemType)) + { + return LibraryManager.RootFolder.RecursiveChildren.FirstOrDefault(i => + { + if (string.Equals(i.Name, info.ItemName, StringComparison.OrdinalIgnoreCase)) + { + if (string.Equals(i.GetType().Name, info.ItemType, StringComparison.OrdinalIgnoreCase)) + { + if (info.ItemYear.HasValue) + { + return info.ItemYear.Value == (i.ProductionYear ?? -1); + } + return true; + } + } + + return false; + }); + } + + return null; + } + protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken) { var changesFound = false; @@ -1106,5 +1135,10 @@ namespace MediaBrowser.Controller.Entities return GetRecursiveChildren(user).Where(i => !i.IsFolder && i.LocationType != LocationType.Virtual) .All(i => i.IsUnplayed(user)); } + + public IEnumerable<BaseItem> GetHiddenChildren() + { + return ActualChildren.Where(i => i.IsHidden); + } } } diff --git a/MediaBrowser.Controller/Entities/GameGenre.cs b/MediaBrowser.Controller/Entities/GameGenre.cs index 3a3c575cd..825468954 100644 --- a/MediaBrowser.Controller/Entities/GameGenre.cs +++ b/MediaBrowser.Controller/Entities/GameGenre.cs @@ -1,16 +1,11 @@ -using MediaBrowser.Model.Dto; +using System; using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Linq; namespace MediaBrowser.Controller.Entities { public class GameGenre : BaseItem, IItemByName { - public GameGenre() - { - UserItemCountList = new List<ItemByNameCounts>(); - } - /// <summary> /// Gets the user data key. /// </summary> @@ -20,9 +15,6 @@ namespace MediaBrowser.Controller.Entities return "GameGenre-" + Name; } - [IgnoreDataMember] - public List<ItemByNameCounts> UserItemCountList { get; set; } - /// <summary> /// Returns the folder containing the item. /// If the item is a folder, it returns the folder itself @@ -47,5 +39,10 @@ namespace MediaBrowser.Controller.Entities return false; } } + + public IEnumerable<BaseItem> GetTaggedItems(IEnumerable<BaseItem> inputItems) + { + return inputItems.Where(i => (i is Game) && i.Genres.Contains(Name, StringComparer.OrdinalIgnoreCase)); + } } } diff --git a/MediaBrowser.Controller/Entities/Genre.cs b/MediaBrowser.Controller/Entities/Genre.cs index c15ca0aa2..05442f2b7 100644 --- a/MediaBrowser.Controller/Entities/Genre.cs +++ b/MediaBrowser.Controller/Entities/Genre.cs @@ -1,6 +1,7 @@ -using MediaBrowser.Model.Dto; +using MediaBrowser.Controller.Entities.Audio; +using System; using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Linq; namespace MediaBrowser.Controller.Entities { @@ -9,11 +10,6 @@ namespace MediaBrowser.Controller.Entities /// </summary> public class Genre : BaseItem, IItemByName { - public Genre() - { - UserItemCountList = new List<ItemByNameCounts>(); - } - /// <summary> /// Gets the user data key. /// </summary> @@ -23,9 +19,6 @@ namespace MediaBrowser.Controller.Entities return "Genre-" + Name; } - [IgnoreDataMember] - public List<ItemByNameCounts> UserItemCountList { get; set; } - /// <summary> /// Returns the folder containing the item. /// If the item is a folder, it returns the folder itself @@ -50,5 +43,10 @@ namespace MediaBrowser.Controller.Entities return false; } } + + public IEnumerable<BaseItem> GetTaggedItems(IEnumerable<BaseItem> inputItems) + { + return inputItems.Where(i => !(i is Game) && !(i is IHasMusicGenres) && i.Genres.Contains(Name, StringComparer.OrdinalIgnoreCase)); + } } } diff --git a/MediaBrowser.Controller/Entities/IHasImages.cs b/MediaBrowser.Controller/Entities/IHasImages.cs index 8e66605dd..bac226369 100644 --- a/MediaBrowser.Controller/Entities/IHasImages.cs +++ b/MediaBrowser.Controller/Entities/IHasImages.cs @@ -62,7 +62,7 @@ namespace MediaBrowser.Controller.Entities /// <param name="type">The type.</param> /// <param name="index">The index.</param> /// <param name="file">The file.</param> - void SetImagePath(ImageType type, int index, FileInfo file); + void SetImagePath(ImageType type, int index, FileSystemInfo file); /// <summary> /// Determines whether the specified type has image. @@ -129,7 +129,7 @@ namespace MediaBrowser.Controller.Entities /// <param name="imageType">Type of the image.</param> /// <param name="images">The images.</param> /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns> - bool AddImages(ImageType imageType, IEnumerable<FileInfo> images); + bool AddImages(ImageType imageType, IEnumerable<FileSystemInfo> images); /// <summary> /// Determines whether [is save local metadata enabled]. @@ -180,7 +180,7 @@ namespace MediaBrowser.Controller.Entities /// <param name="item">The item.</param> /// <param name="imageType">Type of the image.</param> /// <param name="file">The file.</param> - public static void SetImagePath(this IHasImages item, ImageType imageType, FileInfo file) + public static void SetImagePath(this IHasImages item, ImageType imageType, FileSystemInfo file) { item.SetImagePath(imageType, 0, file); } diff --git a/MediaBrowser.Controller/Entities/IItemByName.cs b/MediaBrowser.Controller/Entities/IItemByName.cs index 1e83c7466..70d5b840f 100644 --- a/MediaBrowser.Controller/Entities/IItemByName.cs +++ b/MediaBrowser.Controller/Entities/IItemByName.cs @@ -1,7 +1,4 @@ -using MediaBrowser.Model.Dto; -using System; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Generic; namespace MediaBrowser.Controller.Entities { @@ -10,37 +7,16 @@ namespace MediaBrowser.Controller.Entities /// </summary> public interface IItemByName { - List<ItemByNameCounts> UserItemCountList { get; set; } + /// <summary> + /// Gets the tagged items. + /// </summary> + /// <param name="inputItems">The input items.</param> + /// <returns>IEnumerable{BaseItem}.</returns> + IEnumerable<BaseItem> GetTaggedItems(IEnumerable<BaseItem> inputItems); } public interface IHasDualAccess : IItemByName { bool IsAccessedByName { get; } } - - public static class ItemByNameExtensions - { - public static ItemByNameCounts GetItemByNameCounts(this IItemByName item, Guid userId) - { - if (userId == Guid.Empty) - { - throw new ArgumentNullException("userId"); - } - - return item.UserItemCountList.FirstOrDefault(i => i.UserId == userId); - } - - public static void SetItemByNameCounts(this IItemByName item, Guid userId, ItemByNameCounts counts) - { - var current = item.UserItemCountList.FirstOrDefault(i => i.UserId == userId); - - if (current != null) - { - item.UserItemCountList.Remove(current); - } - - counts.UserId = userId; - item.UserItemCountList.Add(counts); - } - } } diff --git a/MediaBrowser.Controller/Entities/LinkedChild.cs b/MediaBrowser.Controller/Entities/LinkedChild.cs index cc5f7bf38..1ae04e40f 100644 --- a/MediaBrowser.Controller/Entities/LinkedChild.cs +++ b/MediaBrowser.Controller/Entities/LinkedChild.cs @@ -9,6 +9,10 @@ namespace MediaBrowser.Controller.Entities public string Path { get; set; } public LinkedChildType Type { get; set; } + public string ItemName { get; set; } + public string ItemType { get; set; } + public int? ItemYear { get; set; } + /// <summary> /// Serves as a cache /// </summary> @@ -18,8 +22,8 @@ namespace MediaBrowser.Controller.Entities public enum LinkedChildType { - Manual = 1, - Shortcut = 2 + Manual = 0, + Shortcut = 1 } public class LinkedChildComparer : IEqualityComparer<LinkedChild> @@ -35,7 +39,7 @@ namespace MediaBrowser.Controller.Entities public int GetHashCode(LinkedChild obj) { - return (obj.Path + obj.Type.ToString()).GetHashCode(); + return (obj.Path + obj.Type).GetHashCode(); } } } diff --git a/MediaBrowser.Controller/Entities/Person.cs b/MediaBrowser.Controller/Entities/Person.cs index c1dc81136..1def47391 100644 --- a/MediaBrowser.Controller/Entities/Person.cs +++ b/MediaBrowser.Controller/Entities/Person.cs @@ -1,7 +1,7 @@ using MediaBrowser.Controller.Providers; -using MediaBrowser.Model.Dto; +using System; using System.Collections.Generic; -using System.Runtime.Serialization; +using System.Linq; namespace MediaBrowser.Controller.Entities { @@ -10,19 +10,11 @@ namespace MediaBrowser.Controller.Entities /// </summary> public class Person : BaseItem, IItemByName, IHasLookupInfo<PersonLookupInfo> { - public Person() - { - UserItemCountList = new List<ItemByNameCounts>(); - } - /// <summary> /// Gets or sets the place of birth. /// </summary> /// <value>The place of birth.</value> public string PlaceOfBirth { get; set; } - - [IgnoreDataMember] - public List<ItemByNameCounts> UserItemCountList { get; set; } /// <summary> /// Gets the user data key. @@ -62,6 +54,11 @@ namespace MediaBrowser.Controller.Entities return false; } } + + public IEnumerable<BaseItem> GetTaggedItems(IEnumerable<BaseItem> inputItems) + { + return inputItems.Where(i => i.People.Any(p => string.Equals(p.Name, Name, StringComparison.OrdinalIgnoreCase))); + } } /// <summary> diff --git a/MediaBrowser.Controller/Entities/Studio.cs b/MediaBrowser.Controller/Entities/Studio.cs index 5c3946f9b..8271a3df2 100644 --- a/MediaBrowser.Controller/Entities/Studio.cs +++ b/MediaBrowser.Controller/Entities/Studio.cs @@ -1,7 +1,6 @@ -using System.Runtime.Serialization; -using MediaBrowser.Model.Dto; -using System; +using System; using System.Collections.Generic; +using System.Linq; namespace MediaBrowser.Controller.Entities { @@ -10,11 +9,6 @@ namespace MediaBrowser.Controller.Entities /// </summary> public class Studio : BaseItem, IItemByName { - public Studio() - { - UserItemCountList = new List<ItemByNameCounts>(); - } - /// <summary> /// Gets the user data key. /// </summary> @@ -24,9 +18,6 @@ namespace MediaBrowser.Controller.Entities return "Studio-" + Name; } - [IgnoreDataMember] - public List<ItemByNameCounts> UserItemCountList { get; set; } - /// <summary> /// Returns the folder containing the item. /// If the item is a folder, it returns the folder itself @@ -51,5 +42,10 @@ namespace MediaBrowser.Controller.Entities return false; } } + + public IEnumerable<BaseItem> GetTaggedItems(IEnumerable<BaseItem> inputItems) + { + return inputItems.Where(i => i.Studios.Contains(Name, StringComparer.OrdinalIgnoreCase)); + } } } diff --git a/MediaBrowser.Controller/Entities/UserRootFolder.cs b/MediaBrowser.Controller/Entities/UserRootFolder.cs index 1829e10c7..0290fa39a 100644 --- a/MediaBrowser.Controller/Entities/UserRootFolder.cs +++ b/MediaBrowser.Controller/Entities/UserRootFolder.cs @@ -1,4 +1,5 @@ using MediaBrowser.Controller.Providers; +using System; using System.Collections.Generic; using System.Linq; @@ -23,7 +24,7 @@ namespace MediaBrowser.Controller.Entities { var hasChanges = base.BeforeMetadataRefresh(); - if (string.Equals("default", Name, System.StringComparison.OrdinalIgnoreCase)) + if (string.Equals("default", Name, StringComparison.OrdinalIgnoreCase)) { Name = "Media Folders"; hasChanges = true; diff --git a/MediaBrowser.Controller/Entities/Year.cs b/MediaBrowser.Controller/Entities/Year.cs index c6ca028ae..8deb930e8 100644 --- a/MediaBrowser.Controller/Entities/Year.cs +++ b/MediaBrowser.Controller/Entities/Year.cs @@ -1,7 +1,6 @@ -using System.Runtime.Serialization; -using MediaBrowser.Model.Dto; -using System; -using System.Collections.Generic; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; namespace MediaBrowser.Controller.Entities { @@ -10,14 +9,6 @@ namespace MediaBrowser.Controller.Entities /// </summary> public class Year : BaseItem, IItemByName { - public Year() - { - UserItemCountList = new List<ItemByNameCounts>(); - } - - [IgnoreDataMember] - public List<ItemByNameCounts> UserItemCountList { get; set; } - /// <summary> /// Gets the user data key. /// </summary> @@ -51,5 +42,19 @@ namespace MediaBrowser.Controller.Entities return false; } } + + public IEnumerable<BaseItem> GetTaggedItems(IEnumerable<BaseItem> inputItems) + { + int year; + + var usCulture = new CultureInfo("en-US"); + + if (!int.TryParse(Name, NumberStyles.Integer, usCulture, out year)) + { + return inputItems; + } + + return inputItems.Where(i => i.ProductionYear.HasValue && i.ProductionYear.Value == year); + } } } diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 747ae48ad..9bde9aa29 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -305,19 +305,6 @@ namespace MediaBrowser.Controller.Library string FindCollectionType(BaseItem item); /// <summary> - /// Gets all artists. - /// </summary> - /// <returns>IEnumerable{System.String}.</returns> - IEnumerable<string> GetAllArtists(); - - /// <summary> - /// Gets all artists. - /// </summary> - /// <param name="items">The items.</param> - /// <returns>IEnumerable{System.String}.</returns> - IEnumerable<string> GetAllArtists(IEnumerable<BaseItem> items); - - /// <summary> /// Normalizes the root path list. /// </summary> /// <param name="paths">The paths.</param> diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs index 0502ec419..c3b0748cf 100644 --- a/MediaBrowser.Controller/Library/IUserManager.cs +++ b/MediaBrowser.Controller/Library/IUserManager.cs @@ -51,9 +51,8 @@ namespace MediaBrowser.Controller.Library /// Refreshes metadata for each user /// </summary> /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="force">if set to <c>true</c> [force].</param> /// <returns>Task.</returns> - Task RefreshUsersMetadata(CancellationToken cancellationToken, bool force = false); + Task RefreshUsersMetadata(CancellationToken cancellationToken); /// <summary> /// Renames the user. diff --git a/MediaBrowser.Controller/Library/TVUtils.cs b/MediaBrowser.Controller/Library/TVUtils.cs index c64e4fa0c..7841a32ae 100644 --- a/MediaBrowser.Controller/Library/TVUtils.cs +++ b/MediaBrowser.Controller/Library/TVUtils.cs @@ -27,94 +27,94 @@ namespace MediaBrowser.Controller.Library /// <summary> /// A season folder must contain one of these somewhere in the name /// </summary> - private static readonly string[] SeasonFolderNames = new[] - { - "season", - "sæson", - "temporada", - "saison", - "staffel", - "series", - "сезон" - }; + private static readonly string[] SeasonFolderNames = + { + "season", + "sæson", + "temporada", + "saison", + "staffel", + "series", + "сезон" + }; /// <summary> /// Used to detect paths that represent episodes, need to make sure they don't also /// match movie titles like "2001 A Space..." /// Currently we limit the numbers here to 2 digits to try and avoid this /// </summary> - private static readonly Regex[] EpisodeExpressions = new[] - { - new Regex( - @".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})[^\\\/]*$", - RegexOptions.Compiled), - new Regex( - @".*(\\|\/)[sS](?<seasonnumber>\d{1,4})[x,X]?[eE](?<epnumber>\d{1,3})[^\\\/]*$", - RegexOptions.Compiled), - new Regex( - @".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))[^\\\/]*$", - RegexOptions.Compiled), - new Regex( - @".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})[^\\\/]*$", - RegexOptions.Compiled) - }; - private static readonly Regex[] MultipleEpisodeExpressions = new[] - { - new Regex( - @".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})((-| - )\d{1,4}[eExX](?<endingepnumber>\d{1,3}))+[^\\\/]*$", - RegexOptions.Compiled), - new Regex( - @".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})((-| - )\d{1,4}[xX][eE](?<endingepnumber>\d{1,3}))+[^\\\/]*$", - RegexOptions.Compiled), - new Regex( - @".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})((-| - )?[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$", - RegexOptions.Compiled), - new Regex( - @".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})(-[xE]?[eE]?(?<endingepnumber>\d{1,3}))+[^\\\/]*$", - RegexOptions.Compiled), - new Regex( - @".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))((-| - )\d{1,4}[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$", - RegexOptions.Compiled), - new Regex( - @".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))((-| - )\d{1,4}[xX][eE](?<endingepnumber>\d{1,3}))+[^\\\/]*$", - RegexOptions.Compiled), - new Regex( - @".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))((-| - )?[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$", - RegexOptions.Compiled), - new Regex( - @".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))(-[xX]?[eE]?(?<endingepnumber>\d{1,3}))+[^\\\/]*$", - RegexOptions.Compiled), - new Regex( - @".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})((-| - )?[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$", - RegexOptions.Compiled), - new Regex( - @".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})(-[xX]?[eE]?(?<endingepnumber>\d{1,3}))+[^\\\/]*$", - RegexOptions.Compiled) - }; + private static readonly Regex[] EpisodeExpressions = + { + new Regex( + @".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})[^\\\/]*$", + RegexOptions.Compiled), + new Regex( + @".*(\\|\/)[sS](?<seasonnumber>\d{1,4})[x,X]?[eE](?<epnumber>\d{1,3})[^\\\/]*$", + RegexOptions.Compiled), + new Regex( + @".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))[^\\\/]*$", + RegexOptions.Compiled), + new Regex( + @".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})[^\\\/]*$", + RegexOptions.Compiled) + }; + private static readonly Regex[] MultipleEpisodeExpressions = + { + new Regex( + @".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})((-| - )\d{1,4}[eExX](?<endingepnumber>\d{1,3}))+[^\\\/]*$", + RegexOptions.Compiled), + new Regex( + @".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})((-| - )\d{1,4}[xX][eE](?<endingepnumber>\d{1,3}))+[^\\\/]*$", + RegexOptions.Compiled), + new Regex( + @".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})((-| - )?[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$", + RegexOptions.Compiled), + new Regex( + @".*(\\|\/)[sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3})(-[xE]?[eE]?(?<endingepnumber>\d{1,3}))+[^\\\/]*$", + RegexOptions.Compiled), + new Regex( + @".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))((-| - )\d{1,4}[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$", + RegexOptions.Compiled), + new Regex( + @".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))((-| - )\d{1,4}[xX][eE](?<endingepnumber>\d{1,3}))+[^\\\/]*$", + RegexOptions.Compiled), + new Regex( + @".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))((-| - )?[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$", + RegexOptions.Compiled), + new Regex( + @".*(\\|\/)(?<seriesname>((?![sS]?\d{1,4}[xX]\d{1,3})[^\\\/])*)?([sS]?(?<seasonnumber>\d{1,4})[xX](?<epnumber>\d{1,3}))(-[xX]?[eE]?(?<endingepnumber>\d{1,3}))+[^\\\/]*$", + RegexOptions.Compiled), + new Regex( + @".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})((-| - )?[xXeE](?<endingepnumber>\d{1,3}))+[^\\\/]*$", + RegexOptions.Compiled), + new Regex( + @".*(\\|\/)(?<seriesname>[^\\\/]*)[sS](?<seasonnumber>\d{1,4})[xX\.]?[eE](?<epnumber>\d{1,3})(-[xX]?[eE]?(?<endingepnumber>\d{1,3}))+[^\\\/]*$", + RegexOptions.Compiled) + }; /// <summary> /// To avoid the following matching movies they are only valid when contained in a folder which has been matched as a being season /// </summary> - private static readonly Regex[] EpisodeExpressionsInASeasonFolder = new[] - { - new Regex( - @".*(\\|\/)(?<epnumber>\d{1,2})\s?-\s?[^\\\/]*$", - RegexOptions.Compiled), - // 01 - blah.avi, 01-blah.avi - new Regex( - @".*(\\|\/)(?<epnumber>\d{1,2})[^\d\\]*[^\\\/]*$", - RegexOptions.Compiled), - // 01.avi, 01.blah.avi "01 - 22 blah.avi" - new Regex( - @".*(\\|\/)(?<seasonnumber>\d)(?<epnumber>\d{1,2})[^\d\\]+[^\\\/]*$", - RegexOptions.Compiled), - // 01.avi, 01.blah.avi - new Regex( - @".*(\\|\/)\D*\d+(?<epnumber>\d{2})", - RegexOptions.Compiled) - // hell0 - 101 - hello.avi - - }; + private static readonly Regex[] EpisodeExpressionsInASeasonFolder = + { + new Regex( + @".*(\\|\/)(?<epnumber>\d{1,2})\s?-\s?[^\\\/]*$", + RegexOptions.Compiled), + // 01 - blah.avi, 01-blah.avi + new Regex( + @".*(\\|\/)(?<epnumber>\d{1,2})[^\d\\]*[^\\\/]*$", + RegexOptions.Compiled), + // 01.avi, 01.blah.avi "01 - 22 blah.avi" + new Regex( + @".*(\\|\/)(?<seasonnumber>\d)(?<epnumber>\d{1,2})[^\d\\]+[^\\\/]*$", + RegexOptions.Compiled), + // 01.avi, 01.blah.avi + new Regex( + @".*(\\|\/)\D*\d+(?<epnumber>\d{2})", + RegexOptions.Compiled) + // hell0 - 101 - hello.avi + + }; /// <summary> /// Gets the season number from path. @@ -151,8 +151,8 @@ namespace MediaBrowser.Controller.Library /// <returns>System.Nullable{System.Int32}.</returns> private static int? GetSeasonNumberFromPathSubstring(string path) { - int numericStart = -1; - int length = 0; + var numericStart = -1; + var length = 0; // Find out where the numbers start, and then keep going until they end for (var i = 0; i < path.Length; i++) diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs index ad42c5091..dddd26358 100644 --- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs +++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs @@ -138,13 +138,6 @@ namespace MediaBrowser.Controller.LiveTv /// <param name="id">The identifier.</param> /// <returns>Channel.</returns> LiveTvChannel GetInternalChannel(string id); - - /// <summary> - /// Gets the internal program. - /// </summary> - /// <param name="id">The identifier.</param> - /// <returns>LiveTvProgram.</returns> - LiveTvProgram GetInternalProgram(string id); /// <summary> /// Gets the recording. diff --git a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs index ff5d6a4d2..3e6832123 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs @@ -1,20 +1,13 @@ using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Configuration; -using MediaBrowser.Model.Dto; using MediaBrowser.Model.LiveTv; using System.Collections.Generic; -using System.Runtime.Serialization; using System.Linq; namespace MediaBrowser.Controller.LiveTv { public class LiveTvChannel : BaseItem, IItemByName { - public LiveTvChannel() - { - UserItemCountList = new List<ItemByNameCounts>(); - } - /// <summary> /// Gets the user data key. /// </summary> @@ -24,9 +17,6 @@ namespace MediaBrowser.Controller.LiveTv return GetClientTypeName() + "-" + Name; } - [IgnoreDataMember] - public List<ItemByNameCounts> UserItemCountList { get; set; } - /// <summary> /// Returns the folder containing the item. /// If the item is a folder, it returns the folder itself @@ -119,5 +109,10 @@ namespace MediaBrowser.Controller.LiveTv { return "Channel"; } + + public IEnumerable<BaseItem> GetTaggedItems(IEnumerable<BaseItem> inputItems) + { + return new List<BaseItem>(); + } } } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index ff446f2ef..16c54861e 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -68,6 +68,11 @@ <Compile Include="..\SharedVersion.cs"> <Link>Properties\SharedVersion.cs</Link> </Compile> + <Compile Include="Channels\ChannelItemInfo.cs" /> + <Compile Include="Channels\IChannel.cs" /> + <Compile Include="Channels\IChannelManager.cs" /> + <Compile Include="Collections\CollectionCreationOptions.cs" /> + <Compile Include="Collections\ICollectionManager.cs" /> <Compile Include="Drawing\IImageProcessor.cs" /> <Compile Include="Drawing\ImageFormat.cs" /> <Compile Include="Drawing\ImageProcessingOptions.cs" /> diff --git a/MediaBrowser.Controller/Net/IHttpResultFactory.cs b/MediaBrowser.Controller/Net/IHttpResultFactory.cs index b7dff96cd..f7984c32c 100644 --- a/MediaBrowser.Controller/Net/IHttpResultFactory.cs +++ b/MediaBrowser.Controller/Net/IHttpResultFactory.cs @@ -96,6 +96,18 @@ namespace MediaBrowser.Controller.Net object GetStaticFileResult(IRequest requestContext, string path, FileShare fileShare = FileShare.Read, IDictionary<string, string> responseHeaders = null, bool isHeadRequest = false); /// <summary> + /// Gets the static file result. + /// </summary> + /// <param name="requestContext">The request context.</param> + /// <param name="path">The path.</param> + /// <param name="contentType">Type of the content.</param> + /// <param name="fileShare">The file share.</param> + /// <param name="responseHeaders">The response headers.</param> + /// <param name="isHeadRequest">if set to <c>true</c> [is head request].</param> + /// <returns>System.Object.</returns> + object GetStaticFileResult(IRequest requestContext, string path, string contentType, FileShare fileShare = FileShare.Read, IDictionary<string, string> responseHeaders = null, bool isHeadRequest = false); + + /// <summary> /// Gets the optimized serialized result using cache. /// </summary> /// <typeparam name="T"></typeparam> diff --git a/MediaBrowser.Controller/Providers/DirectoryService.cs b/MediaBrowser.Controller/Providers/DirectoryService.cs index 751de9fb4..9d41b6d25 100644 --- a/MediaBrowser.Controller/Providers/DirectoryService.cs +++ b/MediaBrowser.Controller/Providers/DirectoryService.cs @@ -1,6 +1,6 @@ -using System.Collections.Concurrent; -using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Logging; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; @@ -10,10 +10,8 @@ namespace MediaBrowser.Controller.Providers public interface IDirectoryService { List<FileSystemInfo> GetFileSystemEntries(string path); - IEnumerable<FileInfo> GetFiles(string path); - IEnumerable<DirectoryInfo> GetDirectories(string path); - FileInfo GetFile(string path); - DirectoryInfo GetDirectory(string path); + IEnumerable<FileSystemInfo> GetFiles(string path); + FileSystemInfo GetFile(string path); } public class DirectoryService : IDirectoryService @@ -50,31 +48,17 @@ namespace MediaBrowser.Controller.Providers return entries; } - public IEnumerable<FileInfo> GetFiles(string path) + public IEnumerable<FileSystemInfo> GetFiles(string path) { - return GetFileSystemEntries(path).OfType<FileInfo>(); + return GetFileSystemEntries(path).Where(i => (i.Attributes & FileAttributes.Directory) != FileAttributes.Directory); } - public IEnumerable<DirectoryInfo> GetDirectories(string path) - { - return GetFileSystemEntries(path).OfType<DirectoryInfo>(); - } - - public FileInfo GetFile(string path) + public FileSystemInfo GetFile(string path) { var directory = Path.GetDirectoryName(path); var filename = Path.GetFileName(path); return GetFiles(directory).FirstOrDefault(i => string.Equals(i.Name, filename, StringComparison.OrdinalIgnoreCase)); } - - - public DirectoryInfo GetDirectory(string path) - { - var directory = Path.GetDirectoryName(path); - var name = Path.GetFileName(path); - - return GetDirectories(directory).FirstOrDefault(i => string.Equals(i.Name, name, StringComparison.OrdinalIgnoreCase)); - } } } diff --git a/MediaBrowser.Controller/Providers/ILocalImageProvider.cs b/MediaBrowser.Controller/Providers/ILocalImageProvider.cs index ec24e1d60..68afb84b8 100644 --- a/MediaBrowser.Controller/Providers/ILocalImageProvider.cs +++ b/MediaBrowser.Controller/Providers/ILocalImageProvider.cs @@ -23,7 +23,7 @@ namespace MediaBrowser.Controller.Providers public class LocalImageInfo { - public FileInfo FileInfo { get; set; } + public FileSystemInfo FileInfo { get; set; } public ImageType Type { get; set; } } diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs index 892ec9345..ee29671c0 100644 --- a/MediaBrowser.Controller/Session/ISessionManager.cs +++ b/MediaBrowser.Controller/Session/ISessionManager.cs @@ -77,6 +77,13 @@ namespace MediaBrowser.Controller.Session Task OnPlaybackStopped(PlaybackStopInfo info); /// <summary> + /// Reports the session ended. + /// </summary> + /// <param name="sessionId">The session identifier.</param> + /// <returns>Task.</returns> + Task ReportSessionEnded(Guid sessionId); + + /// <summary> /// Sends the system command. /// </summary> /// <param name="sessionId">The session id.</param> diff --git a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj index 59420c5a1..622ccefbc 100644 --- a/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj +++ b/MediaBrowser.Dlna/MediaBrowser.Dlna.csproj @@ -53,7 +53,7 @@ </Compile> <Compile Include="PlayTo\Argument.cs" /> <Compile Include="PlayTo\Configuration\DlnaProfile.cs" /> - <Compile Include="PlayTo\Configuration\PluginConfiguration.cs" /> + <Compile Include="PlayTo\Configuration\PlayToConfiguration.cs" /> <Compile Include="PlayTo\Configuration\TranscodeSetting.cs" /> <Compile Include="PlayTo\CurrentIdEventArgs.cs" /> <Compile Include="PlayTo\Device.cs"> diff --git a/MediaBrowser.Dlna/PlayTo/Configuration/PluginConfiguration.cs b/MediaBrowser.Dlna/PlayTo/Configuration/PlayToConfiguration.cs index 1bd8781bb..d3fcb24ad 100644 --- a/MediaBrowser.Dlna/PlayTo/Configuration/PluginConfiguration.cs +++ b/MediaBrowser.Dlna/PlayTo/Configuration/PlayToConfiguration.cs @@ -29,7 +29,12 @@ FriendlyName = "^TV$", ModelNumber = @"1\.0", ModelName = "Samsung DTV DMR", - TranscodeSettings = TranscodeSettings.GetDefaultTranscodingSettings() + TranscodeSettings = new[] + { + new TranscodeSettings {Container = "mkv", MimeType = "x-mkv"}, + new TranscodeSettings {Container = "flac", TargetContainer = "mp3"}, + new TranscodeSettings {Container = "m4a", TargetContainer = "mp3"} + } }; var profile1 = new DlnaProfile @@ -38,7 +43,12 @@ ClientType = "DLNA", FriendlyName = @"(^\[TV\][A-Z]{2}\d{2}(E|F)[A-Z]?\d{3,4}.*)|^\[TV\] Samsung", ModelNumber = @"(1\.0)|(AllShare1\.0)", - TranscodeSettings = TranscodeSettings.GetDefaultTranscodingSettings() + TranscodeSettings = new[] + { + new TranscodeSettings {Container = "mkv", MimeType = "x-mkv"}, + new TranscodeSettings {Container = "flac", TargetContainer = "mp3"}, + new TranscodeSettings {Container = "m4a", TargetContainer = "mp3"} + } }; var profile2 = new DlnaProfile @@ -47,7 +57,12 @@ ClientType = "DLNA", FriendlyName = @"(^TV-\d{2}C\d{3}.*)|(^\[TV\][A-Z]{2}\d{2}(D)[A-Z]?\d{3,4}.*)|^\[TV\] Samsung", ModelNumber = @"(1\.0)|(AllShare1\.0)", - TranscodeSettings = TranscodeSettings.GetDefaultTranscodingSettings() + TranscodeSettings = new[] + { + new TranscodeSettings {Container = "mkv", MimeType = "x-mkv"}, + new TranscodeSettings {Container = "flac", TargetContainer = "mp3"}, + new TranscodeSettings {Container = "m4a", TargetContainer = "mp3"} + } }; var profile3 = new DlnaProfile diff --git a/MediaBrowser.Dlna/PlayTo/Configuration/TranscodeSetting.cs b/MediaBrowser.Dlna/PlayTo/Configuration/TranscodeSetting.cs index f5cceaaaa..4713dc385 100644 --- a/MediaBrowser.Dlna/PlayTo/Configuration/TranscodeSetting.cs +++ b/MediaBrowser.Dlna/PlayTo/Configuration/TranscodeSetting.cs @@ -1,4 +1,5 @@ using System; +using System.Text.RegularExpressions; namespace MediaBrowser.Dlna.PlayTo.Configuration { @@ -21,6 +22,14 @@ namespace MediaBrowser.Dlna.PlayTo.Configuration public string TargetContainer { get; set; } /// <summary> + /// Gets or sets the Mimetype to enforce + /// </summary> + /// <value> + /// The MimeType. + /// </value> + public string MimeType { get; set; } + + /// <summary> /// The default transcoding settings /// </summary> private static readonly TranscodeSettings[] DefaultTranscodingSettings = @@ -46,19 +55,19 @@ namespace MediaBrowser.Dlna.PlayTo.Configuration { if (!string.IsNullOrEmpty(profile.FriendlyName)) { - if (!string.Equals(deviceProperties.Name, profile.FriendlyName, StringComparison.OrdinalIgnoreCase)) + if (!Regex.IsMatch(deviceProperties.Name, profile.FriendlyName)) continue; } if (!string.IsNullOrEmpty(profile.ModelNumber)) { - if (!string.Equals(deviceProperties.ModelNumber, profile.ModelNumber, StringComparison.OrdinalIgnoreCase)) + if (!Regex.IsMatch(deviceProperties.ModelNumber, profile.ModelNumber)) continue; } if (!string.IsNullOrEmpty(profile.ModelName)) { - if (!string.Equals(deviceProperties.ModelName, profile.ModelName, StringComparison.OrdinalIgnoreCase)) + if (!Regex.IsMatch(deviceProperties.ModelName, profile.ModelName)) continue; } diff --git a/MediaBrowser.Dlna/PlayTo/Device.cs b/MediaBrowser.Dlna/PlayTo/Device.cs index 9c2859a36..690f7525b 100644 --- a/MediaBrowser.Dlna/PlayTo/Device.cs +++ b/MediaBrowser.Dlna/PlayTo/Device.cs @@ -76,8 +76,8 @@ namespace MediaBrowser.Dlna.PlayTo _transportState = value; - if (value == "PLAYING" || value == "STOPPED") - NotifyPlaybackChanged(value == "STOPPED"); + if (value == TRANSPORTSTATE.PLAYING || value == TRANSPORTSTATE.STOPPED) + NotifyPlaybackChanged(value == TRANSPORTSTATE.STOPPED); } } @@ -85,7 +85,7 @@ namespace MediaBrowser.Dlna.PlayTo { get { - return TransportState == "PLAYING"; + return TransportState == TRANSPORTSTATE.PLAYING; } } @@ -93,7 +93,7 @@ namespace MediaBrowser.Dlna.PlayTo { get { - return (TransportState == "TRANSITIONING"); + return (TransportState == TRANSPORTSTATE.TRANSITIONING); } } @@ -101,7 +101,7 @@ namespace MediaBrowser.Dlna.PlayTo { get { - return TransportState == "PAUSED" || TransportState == "PAUSED_PLAYBACK"; + return TransportState == TRANSPORTSTATE.PAUSED || TransportState == TRANSPORTSTATE.PAUSED_PLAYBACK; } } @@ -109,7 +109,7 @@ namespace MediaBrowser.Dlna.PlayTo { get { - return (TransportState == "STOPPED"); + return TransportState == TRANSPORTSTATE.STOPPED; } } @@ -127,23 +127,39 @@ namespace MediaBrowser.Dlna.PlayTo _logger = logger; } - private int GetTimerIntervalMs() + private int GetPlaybackTimerIntervalMs() { - return 10000; + return 2000; + } + + private int GetInactiveTimerIntervalMs() + { + return 20000; } public void Start() { UpdateTime = DateTime.UtcNow; - var interval = GetTimerIntervalMs(); + var interval = GetPlaybackTimerIntervalMs(); _timer = new Timer(TimerCallback, null, interval, interval); } private void RestartTimer() { - var interval = GetTimerIntervalMs(); + var interval = GetPlaybackTimerIntervalMs(); + + _timer.Change(interval, interval); + } + + + /// <summary> + /// Restarts the timer in inactive mode. + /// </summary> + private void RestartTimerInactive() + { + var interval = GetInactiveTimerIntervalMs(); _timer.Change(interval, interval); } @@ -230,11 +246,9 @@ namespace MediaBrowser.Dlna.PlayTo { StopTimer(); - TransportState = "STOPPED"; + await SetStop().ConfigureAwait(false); CurrentId = "0"; - await Task.Delay(50).ConfigureAwait(false); - var command = AvCommands.ServiceActions.FirstOrDefault(c => c.Name == "SetAVTransportURI"); if (command == null) return false; @@ -261,7 +275,7 @@ namespace MediaBrowser.Dlna.PlayTo await SetPlay().ConfigureAwait(false); } - _count = 5; + _lapsCount = GetLapsCount(); RestartTimer(); return true; @@ -322,7 +336,7 @@ namespace MediaBrowser.Dlna.PlayTo var result = await new SsdpHttpClient(_httpClient).SendCommandAsync(Properties.BaseUrl, service, command.Name, RendererCommands.BuildPost(command, service.ServiceType, 1)) .ConfigureAwait(false); - _count = 5; + _lapsCount = GetLapsCount(); return true; } @@ -338,7 +352,6 @@ namespace MediaBrowser.Dlna.PlayTo .ConfigureAwait(false); await Task.Delay(50).ConfigureAwait(false); - _count = 4; return true; } @@ -362,8 +375,13 @@ namespace MediaBrowser.Dlna.PlayTo #region Get data - // TODO: What is going on here - int _count = 5; + private int GetLapsCount() + { + // No need to get all data every lap, just every X time. + return 10; + } + + int _lapsCount = 0; private async void TimerCallback(object sender) { @@ -374,18 +392,24 @@ namespace MediaBrowser.Dlna.PlayTo try { - var hasTrack = await GetPositionInfo().ConfigureAwait(false); + await GetTransportInfo().ConfigureAwait(false); - // TODO: Why make these requests if hasTrack==false? - if (_count > 5) + //If we're not playing anything no need to get additional data + if (TransportState != TRANSPORTSTATE.STOPPED) { - await GetTransportInfo().ConfigureAwait(false); - if (!hasTrack) + var hasTrack = await GetPositionInfo().ConfigureAwait(false); + + // TODO: Why make these requests if hasTrack==false? + // TODO ANSWER Some vendors don't include track in GetPositionInfo, use GetMediaInfo instead. + if (_lapsCount > GetLapsCount()) { - await GetMediaInfo().ConfigureAwait(false); + if (!hasTrack) + { + await GetMediaInfo().ConfigureAwait(false); + } + await GetVolume().ConfigureAwait(false); + _lapsCount = 0; } - await GetVolume().ConfigureAwait(false); - _count = 0; } } catch (Exception ex) @@ -393,11 +417,16 @@ namespace MediaBrowser.Dlna.PlayTo _logger.ErrorException("Error updating device info", ex); } - _count++; + _lapsCount++; + if (_disposed) return; - RestartTimer(); + //If we're not playing anything make sure we don't get data more often than neccessry to keep the Session alive + if (TransportState != TRANSPORTSTATE.STOPPED) + RestartTimer(); + else + RestartTimerInactive(); } private async Task GetVolume() @@ -747,5 +776,16 @@ namespace MediaBrowser.Dlna.PlayTo { return String.Format("{0} - {1}", Properties.Name, Properties.BaseUrl); } + + private class TRANSPORTSTATE + { + public const string STOPPED = "STOPPED"; + public const string PLAYING = "PLAYING"; + public const string TRANSITIONING = "TRANSITIONING"; + public const string PAUSED_PLAYBACK = "PAUSED_PLAYBACK"; + public const string PAUSED = "PAUSED"; + } + } } + diff --git a/MediaBrowser.Dlna/PlayTo/DlnaController.cs b/MediaBrowser.Dlna/PlayTo/DlnaController.cs index 894e32599..2be82b985 100644 --- a/MediaBrowser.Dlna/PlayTo/DlnaController.cs +++ b/MediaBrowser.Dlna/PlayTo/DlnaController.cs @@ -17,7 +17,7 @@ using Timer = System.Timers.Timer; namespace MediaBrowser.Dlna.PlayTo { - public class PlayToController : ISessionController + public class PlayToController : ISessionController, IDisposable { private Device _device; private BaseItem _currentItem = null; @@ -131,6 +131,14 @@ namespace MediaBrowser.Dlna.PlayTo ((Timer)sender).Stop(); + + if (!IsSessionActive) + { + //Session is inactive, mark it for Disposal and don't start the elapsed timer. + await _sessionManager.ReportSessionEnded(this._session.Id); + return; + } + await ReportProgress().ConfigureAwait(false); if (!_disposed && IsSessionActive) @@ -479,3 +487,4 @@ namespace MediaBrowser.Dlna.PlayTo } } } + diff --git a/MediaBrowser.Dlna/PlayTo/PlayToServerEntryPoint.cs b/MediaBrowser.Dlna/PlayTo/PlayToServerEntryPoint.cs index b1afeb0f4..a6746cfc5 100644 --- a/MediaBrowser.Dlna/PlayTo/PlayToServerEntryPoint.cs +++ b/MediaBrowser.Dlna/PlayTo/PlayToServerEntryPoint.cs @@ -1,40 +1,107 @@ using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Logging; +using System; namespace MediaBrowser.Dlna.PlayTo { public class PlayToServerEntryPoint : IServerEntryPoint { - private bool _disposed; + private PlayToManager _manager; + private readonly IServerConfigurationManager _config; + private readonly ILogger _logger; + private readonly ISessionManager _sessionManager; + private readonly IHttpClient _httpClient; + private readonly IItemRepository _itemRepo; + private readonly ILibraryManager _libraryManager; + private readonly INetworkManager _networkManager; + private readonly IUserManager _userManager; - private readonly PlayToManager _manager; - - public PlayToServerEntryPoint(ILogManager logManager, ISessionManager sessionManager, IUserManager userManager, IHttpClient httpClient, INetworkManager networkManager, IItemRepository itemRepository, ILibraryManager libraryManager) + public PlayToServerEntryPoint(ILogManager logManager, IServerConfigurationManager config, ISessionManager sessionManager, IHttpClient httpClient, IItemRepository itemRepo, ILibraryManager libraryManager, INetworkManager networkManager, IUserManager userManager) { - _manager = new PlayToManager(logManager.GetLogger("PlayTo"), sessionManager, httpClient, itemRepository, libraryManager, networkManager, userManager); + _config = config; + _sessionManager = sessionManager; + _httpClient = httpClient; + _itemRepo = itemRepo; + _libraryManager = libraryManager; + _networkManager = networkManager; + _userManager = userManager; + _logger = logManager.GetLogger("PlayTo"); } public void Run() { - //_manager.Start(); + _config.ConfigurationUpdated += ConfigurationUpdated; + ReloadPlayToManager(); } - #region Dispose + void ConfigurationUpdated(object sender, EventArgs e) + { + ReloadPlayToManager(); + } - public void Dispose() + private void ReloadPlayToManager() + { + var isStarted = _manager != null; + + if (_config.Configuration.DlnaOptions.EnablePlayTo && !isStarted) + { + StartPlayToManager(); + } + else if (!_config.Configuration.DlnaOptions.EnablePlayTo && isStarted) + { + DisposePlayToManager(); + } + } + + private readonly object _syncLock = new object(); + private void StartPlayToManager() + { + lock (_syncLock) + { + try + { + _manager = new PlayToManager(_logger, _sessionManager, _httpClient, _itemRepo, _libraryManager, _networkManager, _userManager); + _manager.Start(); + } + catch (Exception ex) + { + _logger.ErrorException("Error starting PlayTo manager", ex); + } + } + } + + private void DisposePlayToManager() { - if (!_disposed) + lock (_syncLock) { - _disposed = true; - _manager.Stop(); - _manager.Dispose(); + if (_manager != null) + { + try + { + _manager.Stop(); + _manager.Dispose(); + } + catch (Exception ex) + { + _logger.ErrorException("Error disposing PlayTo manager", ex); + } + _manager = null; + } } } + #region Dispose + + public void Dispose() + { + DisposePlayToManager(); + } + #endregion } } diff --git a/MediaBrowser.Dlna/PlayTo/PlaylistItem.cs b/MediaBrowser.Dlna/PlayTo/PlaylistItem.cs index d2d04c4a8..6523eb9f5 100644 --- a/MediaBrowser.Dlna/PlayTo/PlaylistItem.cs +++ b/MediaBrowser.Dlna/PlayTo/PlaylistItem.cs @@ -16,6 +16,8 @@ namespace MediaBrowser.Dlna.PlayTo public string FileFormat { get; set; } + public string MimeType { get; set; } + public int PlayState { get; set; } public string StreamUrl { get; set; } @@ -51,10 +53,21 @@ namespace MediaBrowser.Dlna.PlayTo { if (string.IsNullOrWhiteSpace(transcodeSetting.Container)) continue; - if (path.EndsWith(transcodeSetting.Container)) + if (path.EndsWith(transcodeSetting.Container) && !string.IsNullOrWhiteSpace(transcodeSetting.TargetContainer)) { playlistItem.Transcode = true; playlistItem.FileFormat = transcodeSetting.TargetContainer; + + if (string.IsNullOrWhiteSpace(transcodeSetting.MimeType)) + playlistItem.MimeType = transcodeSetting.MimeType; + + return playlistItem; + } + if (path.EndsWith(transcodeSetting.Container) && !string.IsNullOrWhiteSpace(transcodeSetting.MimeType)) + { + playlistItem.Transcode = false; + playlistItem.FileFormat = transcodeSetting.Container; + playlistItem.MimeType = transcodeSetting.MimeType; return playlistItem; } } diff --git a/MediaBrowser.Dlna/PlayTo/StreamHelper.cs b/MediaBrowser.Dlna/PlayTo/StreamHelper.cs index eed0bb7d7..3492ed182 100644 --- a/MediaBrowser.Dlna/PlayTo/StreamHelper.cs +++ b/MediaBrowser.Dlna/PlayTo/StreamHelper.cs @@ -96,9 +96,12 @@ namespace MediaBrowser.Dlna.PlayTo /// <returns>The url to send to the device</returns> internal static string GetVideoUrl(DeviceProperties deviceProperties, PlaylistItem item, List<MediaStream> streams, string serverAddress) { + string dlnaCommand = string.Empty; if (!item.Transcode) - return string.Format("{0}/Videos/{1}/stream.{2}?Static=True", serverAddress, item.ItemId, item.FileFormat); - + { + dlnaCommand = BuildDlnaUrl(deviceProperties.UUID, !item.Transcode, null, null, null, null, null, null, null, null, null, null, item.MimeType); + return string.Format("{0}/Videos/{1}/stream.{2}?{3}", serverAddress, item.ItemId, item.FileFormat, dlnaCommand); + } var videostream = streams.Where(m => m.Type == MediaStreamType.Video).OrderBy(m => m.IsDefault).FirstOrDefault(); var audiostream = streams.Where(m => m.Type == MediaStreamType.Audio).OrderBy(m => m.IsDefault).FirstOrDefault(); @@ -117,7 +120,7 @@ namespace MediaBrowser.Dlna.PlayTo audioChannels = 2; } - string dlnaCommand = BuildDlnaUrl(deviceProperties.UUID, videoCodec, audioCodec, null, null, videoBitrate, audioChannels, audioBitrate, item.StartPositionTicks, "baseline", "3"); + dlnaCommand = BuildDlnaUrl(deviceProperties.UUID, !item.Transcode, videoCodec, audioCodec, null, null, videoBitrate, audioChannels, audioBitrate, item.StartPositionTicks, "baseline", "3", item.MimeType); return string.Format("{0}/Videos/{1}/stream.{2}?{3}", serverAddress, item.ItemId, item.FileFormat, dlnaCommand); } @@ -162,12 +165,12 @@ namespace MediaBrowser.Dlna.PlayTo /// <summary> /// Builds the dlna URL. /// </summary> - private static string BuildDlnaUrl(string deviceID, VideoCodecs? videoCodec, AudioCodecs? audioCodec, int? subtitleIndex, int? audiostreamIndex, int? videoBitrate, int? audiochannels, int? audioBitrate, long? startPositionTicks, string profile, string videoLevel) + private static string BuildDlnaUrl(string deviceID, bool isStatic, VideoCodecs? videoCodec, AudioCodecs? audioCodec, int? subtitleIndex, int? audiostreamIndex, int? videoBitrate, int? audiochannels, int? audioBitrate, long? startPositionTicks, string profile, string videoLevel, string mimeType) { var usCulture = new CultureInfo("en-US"); var dlnaparam = string.Format("Params={0};", deviceID); - + dlnaparam += isStatic ? "true;" : "false;"; dlnaparam += videoCodec.HasValue ? videoCodec.Value + ";" : ";"; dlnaparam += audioCodec.HasValue ? audioCodec.Value + ";" : ";"; dlnaparam += audiostreamIndex.HasValue ? audiostreamIndex.Value.ToString(usCulture) + ";" : ";"; @@ -178,6 +181,7 @@ namespace MediaBrowser.Dlna.PlayTo dlnaparam += startPositionTicks.HasValue ? startPositionTicks.Value.ToString(usCulture) + ";" : ";"; dlnaparam += profile + ";"; dlnaparam += videoLevel + ";"; + dlnaparam += mimeType + ";"; return dlnaparam; } diff --git a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index 04296de35..c8df3931d 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -80,6 +80,9 @@ <Compile Include="..\MediaBrowser.Model\Configuration\BaseApplicationConfiguration.cs"> <Link>Configuration\BaseApplicationConfiguration.cs</Link> </Compile> + <Compile Include="..\MediaBrowser.Model\Configuration\DlnaOptions.cs"> + <Link>Configuration\DlnaOptions.cs</Link> + </Compile> <Compile Include="..\MediaBrowser.Model\Configuration\ManualLoginCategory.cs"> <Link>Configuration\ManualLoginCategory.cs</Link> </Compile> @@ -131,6 +134,9 @@ <Compile Include="..\MediaBrowser.Model\Dto\ItemIndex.cs"> <Link>Dto\ItemIndex.cs</Link> </Compile> + <Compile Include="..\MediaBrowser.Model\Dto\RecommendationDto.cs"> + <Link>Dto\RecommendationDto.cs</Link> + </Compile> <Compile Include="..\MediaBrowser.Model\Dto\StreamOptions.cs"> <Link>Dto\StreamOptions.cs</Link> </Compile> diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index 56f7fb99d..6e8f23089 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -67,6 +67,9 @@ <Compile Include="..\MediaBrowser.Model\Configuration\BaseApplicationConfiguration.cs"> <Link>Configuration\BaseApplicationConfiguration.cs</Link> </Compile> + <Compile Include="..\MediaBrowser.Model\Configuration\DlnaOptions.cs"> + <Link>Configuration\DlnaOptions.cs</Link> + </Compile> <Compile Include="..\MediaBrowser.Model\Configuration\ManualLoginCategory.cs"> <Link>Configuration\ManualLoginCategory.cs</Link> </Compile> @@ -118,6 +121,9 @@ <Compile Include="..\MediaBrowser.Model\Dto\ItemIndex.cs"> <Link>Dto\ItemIndex.cs</Link> </Compile> + <Compile Include="..\MediaBrowser.Model\Dto\RecommendationDto.cs"> + <Link>Dto\RecommendationDto.cs</Link> + </Compile> <Compile Include="..\MediaBrowser.Model\Dto\StreamOptions.cs"> <Link>Dto\StreamOptions.cs</Link> </Compile> diff --git a/MediaBrowser.Model/ApiClient/IApiClient.cs b/MediaBrowser.Model/ApiClient/IApiClient.cs index d4d2a8124..46d3dcc3f 100644 --- a/MediaBrowser.Model/ApiClient/IApiClient.cs +++ b/MediaBrowser.Model/ApiClient/IApiClient.cs @@ -244,7 +244,7 @@ namespace MediaBrowser.Model.ApiClient /// <param name="query">The query.</param> /// <returns>Task{ItemsResult}.</returns> Task<ItemsResult> GetSeasonsAsync(SeasonQuery query); - + /// <summary> /// Queries for items /// </summary> @@ -346,7 +346,14 @@ namespace MediaBrowser.Model.ApiClient /// </summary> /// <param name="query">The query.</param> /// <returns>Task{ItemsResult}.</returns> - Task<ItemsResult> GetNextUpAsync(NextUpQuery query); + Task<ItemsResult> GetNextUpEpisodesAsync(NextUpQuery query); + + /// <summary> + /// Gets the upcoming episodes asynchronous. + /// </summary> + /// <param name="query">The query.</param> + /// <returns>Task{ItemsResult}.</returns> + Task<ItemsResult> GetUpcomingEpisodesAsync(NextUpQuery query); /// <summary> /// Gets a genre @@ -772,7 +779,7 @@ namespace MediaBrowser.Model.ApiClient /// <param name="options">The options.</param> /// <returns>System.String.</returns> string GetImageUrl(ProgramInfoDto item, ImageOptions options); - + /// <summary> /// Gets an image url that can be used to download an image from the api /// </summary> @@ -902,7 +909,7 @@ namespace MediaBrowser.Model.ApiClient /// <param name="options">The options.</param> /// <returns>System.String.</returns> string GetThumbImageUrl(BaseItemDto item, ImageOptions options); - + /// <summary> /// Gets the url needed to stream an audio file /// </summary> @@ -958,7 +965,7 @@ namespace MediaBrowser.Model.ApiClient /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task{ChannelInfoDto}.</returns> Task<ChannelInfoDto> GetLiveTvChannelAsync(string id, string userId, CancellationToken cancellationToken); - + /// <summary> /// Gets the live tv recordings asynchronous. /// </summary> @@ -975,7 +982,7 @@ namespace MediaBrowser.Model.ApiClient /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task{RecordingInfoDto}.</returns> Task<RecordingInfoDto> GetLiveTvRecordingAsync(string id, string userId, CancellationToken cancellationToken); - + /// <summary> /// Gets the live tv recording groups asynchronous. /// </summary> @@ -992,7 +999,7 @@ namespace MediaBrowser.Model.ApiClient /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task{RecordingGroupDto}.</returns> Task<RecordingGroupDto> GetLiveTvRecordingGroupAsync(string id, string userId, CancellationToken cancellationToken); - + /// <summary> /// Gets the live tv timers asynchronous. /// </summary> @@ -1010,13 +1017,54 @@ namespace MediaBrowser.Model.ApiClient Task<QueryResult<ProgramInfoDto>> GetLiveTvProgramsAsync(ProgramQuery query, CancellationToken cancellationToken); /// <summary> + /// Gets the live tv program asynchronous. + /// </summary> + /// <param name="id">The identifier.</param> + /// <param name="userId">The user identifier.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task{ProgramInfoDto}.</returns> + Task<ProgramInfoDto> GetLiveTvProgramAsync(string id, string userId, CancellationToken cancellationToken); + + /// <summary> /// Gets the recommended live tv programs asynchronous. /// </summary> /// <param name="query">The query.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task{QueryResult{ProgramInfoDto}}.</returns> Task<QueryResult<ProgramInfoDto>> GetRecommendedLiveTvProgramsAsync(RecommendedProgramQuery query, CancellationToken cancellationToken); - + + /// <summary> + /// Creates the live tv timer asynchronous. + /// </summary> + /// <param name="timer">The timer.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + Task CreateLiveTvTimerAsync(TimerInfoDto timer, CancellationToken cancellationToken); + + /// <summary> + /// Updates the live tv timer asynchronous. + /// </summary> + /// <param name="timer">The timer.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + Task UpdateLiveTvTimerAsync(TimerInfoDto timer, CancellationToken cancellationToken); + + /// <summary> + /// Creates the live tv series timer asynchronous. + /// </summary> + /// <param name="timer">The timer.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + Task CreateLiveTvSeriesTimerAsync(SeriesTimerInfoDto timer, CancellationToken cancellationToken); + + /// <summary> + /// Updates the live tv series timer asynchronous. + /// </summary> + /// <param name="timer">The timer.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task.</returns> + Task UpdateLiveTvSeriesTimerAsync(SeriesTimerInfoDto timer, CancellationToken cancellationToken); + /// <summary> /// Gets the live tv timer asynchronous. /// </summary> @@ -1024,7 +1072,7 @@ namespace MediaBrowser.Model.ApiClient /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task{TimerInfoDto}.</returns> Task<TimerInfoDto> GetLiveTvTimerAsync(string id, CancellationToken cancellationToken); - + /// <summary> /// Gets the live tv series timers asynchronous. /// </summary> @@ -1056,7 +1104,7 @@ namespace MediaBrowser.Model.ApiClient /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> Task CancelLiveTvSeriesTimerAsync(string id, CancellationToken cancellationToken); - + /// <summary> /// Deletes the live tv recording asynchronous. /// </summary> @@ -1064,5 +1112,27 @@ namespace MediaBrowser.Model.ApiClient /// <param name="cancellationToken">The cancellation token.</param> /// <returns>Task.</returns> Task DeleteLiveTvRecordingAsync(string id, CancellationToken cancellationToken); + + /// <summary> + /// Gets the default timer information. + /// </summary> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task{SeriesTimerInfoDto}.</returns> + Task<SeriesTimerInfoDto> GetDefaultLiveTvTimerInfo(CancellationToken cancellationToken); + + /// <summary> + /// Gets the live tv guide information. + /// </summary> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task{GuideInfo}.</returns> + Task<GuideInfo> GetLiveTvGuideInfo(CancellationToken cancellationToken); + + /// <summary> + /// Gets the default timer information. + /// </summary> + /// <param name="programId">The program identifier.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Task{SeriesTimerInfoDto}.</returns> + Task<SeriesTimerInfoDto> GetDefaultLiveTvTimerInfo(string programId, CancellationToken cancellationToken); } }
\ No newline at end of file diff --git a/MediaBrowser.Model/Configuration/AutoOrganize.cs b/MediaBrowser.Model/Configuration/AutoOrganize.cs index a30aa36d8..fe32d4a80 100644 --- a/MediaBrowser.Model/Configuration/AutoOrganize.cs +++ b/MediaBrowser.Model/Configuration/AutoOrganize.cs @@ -19,6 +19,8 @@ namespace MediaBrowser.Model.Configuration public bool DeleteEmptyFolders { get; set; } + public bool CopyOriginalFile { get; set; } + public TvFileOrganizationOptions() { MinFileSizeMb = 50; @@ -31,6 +33,8 @@ namespace MediaBrowser.Model.Configuration MultiEpisodeNamePattern = "%sn - %sx%0e-x%0ed - %en.%ext"; SeasonFolderPattern = "Season %s"; SeasonZeroFolderName = "Season 0"; + + CopyOriginalFile = false; } } } diff --git a/MediaBrowser.Model/Configuration/DlnaOptions.cs b/MediaBrowser.Model/Configuration/DlnaOptions.cs new file mode 100644 index 000000000..e6c24fdfb --- /dev/null +++ b/MediaBrowser.Model/Configuration/DlnaOptions.cs @@ -0,0 +1,8 @@ + +namespace MediaBrowser.Model.Configuration +{ + public class DlnaOptions + { + public bool EnablePlayTo { get; set; } + } +} diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 0bff8a1bd..c2765754e 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -212,6 +212,8 @@ namespace MediaBrowser.Model.Configuration public string ServerName { get; set; } public string WanDdns { get; set; } + public DlnaOptions DlnaOptions { get; set; } + /// <summary> /// Initializes a new instance of the <see cref="ServerConfiguration" /> class. /// </summary> @@ -271,6 +273,8 @@ namespace MediaBrowser.Model.Configuration }; MetadataOptions = options.ToArray(); + + DlnaOptions = new DlnaOptions(); } } diff --git a/MediaBrowser.Model/Dto/RecommendationDto.cs b/MediaBrowser.Model/Dto/RecommendationDto.cs new file mode 100644 index 000000000..68b71e466 --- /dev/null +++ b/MediaBrowser.Model/Dto/RecommendationDto.cs @@ -0,0 +1,29 @@ + +namespace MediaBrowser.Model.Dto +{ + public class RecommendationDto + { + public BaseItemDto[] Items { get; set; } + + public RecommendationType RecommendationType { get; set; } + + public string BaselineItemName { get; set; } + + public string CategoryId { get; set; } + } + + public enum RecommendationType + { + SimilarToRecentlyPlayed = 0, + + SimilarToLikedItem = 1, + + HasDirectorFromRecentlyPlayed = 2, + + HasActorFromRecentlyPlayed = 3, + + HasLikedDirector = 4, + + HasLikedActor = 5 + } +} diff --git a/MediaBrowser.Model/Entities/BaseItemInfo.cs b/MediaBrowser.Model/Entities/BaseItemInfo.cs index 49f3e2d8f..b704bdb57 100644 --- a/MediaBrowser.Model/Entities/BaseItemInfo.cs +++ b/MediaBrowser.Model/Entities/BaseItemInfo.cs @@ -47,6 +47,30 @@ namespace MediaBrowser.Model.Entities public Guid? PrimaryImageTag { get; set; } /// <summary> + /// Gets or sets the thumb image tag. + /// </summary> + /// <value>The thumb image tag.</value> + public Guid? ThumbImageTag { get; set; } + + /// <summary> + /// Gets or sets the thumb item identifier. + /// </summary> + /// <value>The thumb item identifier.</value> + public string ThumbItemId { get; set; } + + /// <summary> + /// Gets or sets the thumb image tag. + /// </summary> + /// <value>The thumb image tag.</value> + public Guid? BackdropImageTag { get; set; } + + /// <summary> + /// Gets or sets the thumb item identifier. + /// </summary> + /// <value>The thumb item identifier.</value> + public string BackdropItemId { get; set; } + + /// <summary> /// Gets a value indicating whether this instance has primary image. /// </summary> /// <value><c>true</c> if this instance has primary image; otherwise, <c>false</c>.</value> diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 10aedb3ba..45172da43 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -60,6 +60,7 @@ <Compile Include="ApiClient\ServerEventArgs.cs" /> <Compile Include="Configuration\AutoOrganize.cs" /> <Compile Include="Configuration\BaseApplicationConfiguration.cs" /> + <Compile Include="Configuration\DlnaOptions.cs" /> <Compile Include="Configuration\ManualLoginCategory.cs" /> <Compile Include="Configuration\MetadataPlugin.cs" /> <Compile Include="Configuration\MetadataOptions.cs" /> @@ -73,6 +74,7 @@ <Compile Include="Dto\ItemByNameCounts.cs" /> <Compile Include="Dto\ItemCounts.cs" /> <Compile Include="Dto\ItemIndex.cs" /> + <Compile Include="Dto\RecommendationDto.cs" /> <Compile Include="Entities\PackageReviewInfo.cs" /> <Compile Include="FileOrganization\FileOrganizationResult.cs" /> <Compile Include="FileOrganization\FileOrganizationQuery.cs" /> diff --git a/MediaBrowser.Model/Querying/ItemQuery.cs b/MediaBrowser.Model/Querying/ItemQuery.cs index bc769b786..aee1ca947 100644 --- a/MediaBrowser.Model/Querying/ItemQuery.cs +++ b/MediaBrowser.Model/Querying/ItemQuery.cs @@ -219,6 +219,18 @@ namespace MediaBrowser.Model.Querying public string NameStartsWithOrGreater { get; set; } /// <summary> + /// Gets or sets the name starts with. + /// </summary> + /// <value>The name starts with or greater.</value> + public string NameStartsWith { get; set; } + + /// <summary> + /// Gets or sets the name starts with. + /// </summary> + /// <value>The name lessthan.</value> + public string NameLessThan { get; set; } + + /// <summary> /// Gets or sets the album artist starts with or greater. /// </summary> /// <value>The album artist starts with or greater.</value> diff --git a/MediaBrowser.Model/Querying/ItemSortBy.cs b/MediaBrowser.Model/Querying/ItemSortBy.cs index 09b8f0e18..c91e0bafa 100644 --- a/MediaBrowser.Model/Querying/ItemSortBy.cs +++ b/MediaBrowser.Model/Querying/ItemSortBy.cs @@ -75,16 +75,12 @@ namespace MediaBrowser.Model.Querying public const string IsFolder = "IsFolder"; public const string IsUnplayed = "IsUnplayed"; public const string IsPlayed = "IsPlayed"; - public const string TrailerCount = "TrailerCount"; - public const string MovieCount = "MovieCount"; - public const string SeriesCount = "SeriesCount"; - public const string EpisodeCount = "EpisodeCount"; - public const string SongCount = "SongCount"; - public const string AlbumCount = "AlbumCount"; - public const string MusicVideoCount = "MusicVideoCount"; public const string SeriesSortName = "SeriesSortName"; public const string VideoBitRate = "VideoBitRate"; public const string AirTime = "AirTime"; public const string Metascore = "Metascore"; + public const string Studio = "Studio"; + public const string Players = "Players"; + public const string GameSystem = "GameSystem"; } } diff --git a/MediaBrowser.Model/Querying/ItemsByNameQuery.cs b/MediaBrowser.Model/Querying/ItemsByNameQuery.cs index eafc322ab..4227dc0c5 100644 --- a/MediaBrowser.Model/Querying/ItemsByNameQuery.cs +++ b/MediaBrowser.Model/Querying/ItemsByNameQuery.cs @@ -86,6 +86,10 @@ namespace MediaBrowser.Model.Querying public string NameStartsWithOrGreater { get; set; } /// <summary> + /// Gets or sets the name starts with + /// </summary> + /// <value>The name starts with or greater.</value> + public string NameStartsWith { get; set; } /// Gets or sets the name less than. /// </summary> /// <value>The name less than.</value> diff --git a/MediaBrowser.Model/Querying/NextUpQuery.cs b/MediaBrowser.Model/Querying/NextUpQuery.cs index 4f5d47a04..cdce2e307 100644 --- a/MediaBrowser.Model/Querying/NextUpQuery.cs +++ b/MediaBrowser.Model/Querying/NextUpQuery.cs @@ -33,4 +33,32 @@ namespace MediaBrowser.Model.Querying /// <value>The fields.</value> public ItemFields[] Fields { get; set; } } + + public class UpcomingEpisodesQuery + { + /// <summary> + /// Gets or sets the user id. + /// </summary> + /// <value>The user id.</value> + public string UserId { get; set; } + + /// <summary> + /// Skips over a given number of items within the results. Use for paging. + /// </summary> + /// <value>The start index.</value> + public int? StartIndex { get; set; } + + /// <summary> + /// The maximum number of items to return + /// </summary> + /// <value>The limit.</value> + public int? Limit { get; set; } + + /// <summary> + /// Fields to return within the items, in addition to basic information + /// </summary> + /// <value>The fields.</value> + public ItemFields[] Fields { get; set; } + } + } diff --git a/MediaBrowser.Model/Search/SearchQuery.cs b/MediaBrowser.Model/Search/SearchQuery.cs index 87ff7af66..678dfd39d 100644 --- a/MediaBrowser.Model/Search/SearchQuery.cs +++ b/MediaBrowser.Model/Search/SearchQuery.cs @@ -33,6 +33,8 @@ namespace MediaBrowser.Model.Search public bool IncludeStudios { get; set; } public bool IncludeArtists { get; set; } + public string[] IncludeItemTypes { get; set; } + public SearchQuery() { IncludeArtists = true; @@ -40,6 +42,8 @@ namespace MediaBrowser.Model.Search IncludeMedia = true; IncludePeople = true; IncludeStudios = true; + + IncludeItemTypes = new string[] { }; } } } diff --git a/MediaBrowser.Model/Session/BrowseRequest.cs b/MediaBrowser.Model/Session/BrowseRequest.cs index abb3a30ab..e2cdcc1b2 100644 --- a/MediaBrowser.Model/Session/BrowseRequest.cs +++ b/MediaBrowser.Model/Session/BrowseRequest.cs @@ -23,7 +23,7 @@ namespace MediaBrowser.Model.Session /// </summary> /// <value>The name of the item.</value> public string ItemName { get; set; } - + /// <summary> /// Gets or sets the context (Movies, Music, Tv, etc) /// Applicable to genres, studios and persons only because the context of items and artists can be inferred. @@ -40,4 +40,4 @@ namespace MediaBrowser.Model.Session public const string TvShows = "TvShows"; public const string Games = "Games"; } -} +}
\ No newline at end of file diff --git a/MediaBrowser.Model/Session/MessageCommand.cs b/MediaBrowser.Model/Session/MessageCommand.cs index 5ab580815..b028765ed 100644 --- a/MediaBrowser.Model/Session/MessageCommand.cs +++ b/MediaBrowser.Model/Session/MessageCommand.cs @@ -4,9 +4,9 @@ namespace MediaBrowser.Model.Session public class MessageCommand { public string Header { get; set; } - + public string Text { get; set; } public long? TimeoutMs { get; set; } } -} +}
\ No newline at end of file diff --git a/MediaBrowser.Model/Session/PlayRequest.cs b/MediaBrowser.Model/Session/PlayRequest.cs index c9bb58693..57f6c37f5 100644 --- a/MediaBrowser.Model/Session/PlayRequest.cs +++ b/MediaBrowser.Model/Session/PlayRequest.cs @@ -43,4 +43,4 @@ namespace MediaBrowser.Model.Session /// </summary> PlayLast } -} +}
\ No newline at end of file diff --git a/MediaBrowser.Model/Session/PlaystateCommand.cs b/MediaBrowser.Model/Session/PlaystateCommand.cs index 7e85d9d27..918f4f70f 100644 --- a/MediaBrowser.Model/Session/PlaystateCommand.cs +++ b/MediaBrowser.Model/Session/PlaystateCommand.cs @@ -38,4 +38,4 @@ namespace MediaBrowser.Model.Session public long? SeekPositionTicks { get; set; } } -} +}
\ No newline at end of file diff --git a/MediaBrowser.Providers/AdultVideos/AdultVideoXmlProvider.cs b/MediaBrowser.Providers/AdultVideos/AdultVideoXmlProvider.cs index fcdaf29fd..efc8db9e7 100644 --- a/MediaBrowser.Providers/AdultVideos/AdultVideoXmlProvider.cs +++ b/MediaBrowser.Providers/AdultVideos/AdultVideoXmlProvider.cs @@ -23,7 +23,7 @@ namespace MediaBrowser.Providers.AdultVideos new MovieXmlParser(_logger).Fetch(result.Item, path, cancellationToken); } - protected override FileInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) + protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) { return MovieXmlProvider.GetXmlFileInfo(info, FileSystem); } diff --git a/MediaBrowser.Providers/All/LocalImageProvider.cs b/MediaBrowser.Providers/All/LocalImageProvider.cs index f626c9534..6ef5f6bf3 100644 --- a/MediaBrowser.Providers/All/LocalImageProvider.cs +++ b/MediaBrowser.Providers/All/LocalImageProvider.cs @@ -201,7 +201,7 @@ namespace MediaBrowser.Providers.All PopulateBackdrops(images, files, imagePrefix, "background", "background-", ImageType.Backdrop); PopulateBackdrops(images, files, imagePrefix, "art", "art-", ImageType.Backdrop); - var extraFanartFolder = files.OfType<DirectoryInfo>() + var extraFanartFolder = files .FirstOrDefault(i => string.Equals(i.Name, "extrafanart", StringComparison.OrdinalIgnoreCase)); if (extraFanartFolder != null) diff --git a/MediaBrowser.Providers/BaseXmlProvider.cs b/MediaBrowser.Providers/BaseXmlProvider.cs index 908688086..a5a3ba146 100644 --- a/MediaBrowser.Providers/BaseXmlProvider.cs +++ b/MediaBrowser.Providers/BaseXmlProvider.cs @@ -59,7 +59,7 @@ namespace MediaBrowser.Providers FileSystem = fileSystem; } - protected abstract FileInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService); + protected abstract FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService); public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date) { diff --git a/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs b/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs index 9547eedd9..49e616d1a 100644 --- a/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs +++ b/MediaBrowser.Providers/BoxSets/BoxSetMetadataService.cs @@ -1,5 +1,6 @@ using MediaBrowser.Common.IO; using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; @@ -37,6 +38,15 @@ namespace MediaBrowser.Providers.BoxSets protected override void MergeData(BoxSet source, BoxSet target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings) { ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings); + + if (mergeMetadataSettings) + { + var list = source.LinkedChildren.ToList(); + + list.AddRange(target.LinkedChildren.Where(i => i.Type == LinkedChildType.Shortcut)); + + target.LinkedChildren = list; + } } protected override ItemUpdateType BeforeSave(BoxSet item) diff --git a/MediaBrowser.Providers/BoxSets/BoxSetXmlParser.cs b/MediaBrowser.Providers/BoxSets/BoxSetXmlParser.cs new file mode 100644 index 000000000..eb3c99cef --- /dev/null +++ b/MediaBrowser.Providers/BoxSets/BoxSetXmlParser.cs @@ -0,0 +1,129 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Providers; +using MediaBrowser.Model.Logging; +using System.Collections.Generic; +using System.Globalization; +using System.Xml; + +namespace MediaBrowser.Providers.BoxSets +{ + public class BoxSetXmlParser : BaseItemXmlParser<BoxSet> + { + private readonly CultureInfo UsCulture = new CultureInfo("en-US"); + + public BoxSetXmlParser(ILogger logger) + : base(logger) + { + } + + protected override void FetchDataFromXmlNode(XmlReader reader, BoxSet item) + { + switch (reader.Name) + { + case "CollectionItems": + + using (var subReader = reader.ReadSubtree()) + { + FetchFromCollectionItemsNode(subReader, item); + } + break; + + default: + base.FetchDataFromXmlNode(reader, item); + break; + } + } + + private void FetchFromCollectionItemsNode(XmlReader reader, BoxSet item) + { + reader.MoveToContent(); + + var list = new List<LinkedChild>(); + + while (reader.Read()) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "CollectionItem": + { + using (var subReader = reader.ReadSubtree()) + { + var child = GetLinkedChild(subReader); + + if (child != null) + { + list.Add(child); + } + } + + break; + } + + default: + reader.Skip(); + break; + } + } + } + + item.LinkedChildren = list; + } + + private LinkedChild GetLinkedChild(XmlReader reader) + { + reader.MoveToContent(); + + var linkedItem = new LinkedChild + { + Type = LinkedChildType.Manual + }; + + while (reader.Read()) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "Name": + { + linkedItem.ItemName = reader.ReadElementContentAsString(); + break; + } + + case "Type": + { + linkedItem.ItemType = reader.ReadElementContentAsString(); + break; + } + + case "Year": + { + var val = reader.ReadElementContentAsString(); + + if (!string.IsNullOrWhiteSpace(val)) + { + int rval; + + if (int.TryParse(val, NumberStyles.Integer, UsCulture, out rval)) + { + linkedItem.ItemYear = rval; + } + } + + break; + } + + default: + reader.Skip(); + break; + } + } + } + + return string.IsNullOrWhiteSpace(linkedItem.ItemName) || string.IsNullOrWhiteSpace(linkedItem.ItemType) ? null : linkedItem; + } + } +} diff --git a/MediaBrowser.Providers/BoxSets/BoxSetXmlProvider.cs b/MediaBrowser.Providers/BoxSets/BoxSetXmlProvider.cs index e9896c28e..1d4d893ed 100644 --- a/MediaBrowser.Providers/BoxSets/BoxSetXmlProvider.cs +++ b/MediaBrowser.Providers/BoxSets/BoxSetXmlProvider.cs @@ -22,10 +22,10 @@ namespace MediaBrowser.Providers.BoxSets protected override void Fetch(LocalMetadataResult<BoxSet> result, string path, CancellationToken cancellationToken) { - new BaseItemXmlParser<BoxSet>(_logger).Fetch(result.Item, path, cancellationToken); + new BoxSetXmlParser(_logger).Fetch(result.Item, path, cancellationToken); } - protected override FileInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) + protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) { return directoryService.GetFile(Path.Combine(info.Path, "collection.xml")); } diff --git a/MediaBrowser.Providers/Folders/FolderXmlProvider.cs b/MediaBrowser.Providers/Folders/FolderXmlProvider.cs index 978fb0f0c..144d1b752 100644 --- a/MediaBrowser.Providers/Folders/FolderXmlProvider.cs +++ b/MediaBrowser.Providers/Folders/FolderXmlProvider.cs @@ -25,7 +25,7 @@ namespace MediaBrowser.Providers.Folders new BaseItemXmlParser<Folder>(_logger).Fetch(result.Item, path, cancellationToken); } - protected override FileInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) + protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) { return new FileInfo(Path.Combine(info.Path, "folder.xml")); } diff --git a/MediaBrowser.Providers/Games/GameSystemXmlProvider.cs b/MediaBrowser.Providers/Games/GameSystemXmlProvider.cs index 9efa93dfa..db9c8f063 100644 --- a/MediaBrowser.Providers/Games/GameSystemXmlProvider.cs +++ b/MediaBrowser.Providers/Games/GameSystemXmlProvider.cs @@ -22,7 +22,7 @@ namespace MediaBrowser.Providers.Games new GameSystemXmlParser(_logger).Fetch(result.Item, path, cancellationToken); } - protected override FileInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) + protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) { return directoryService.GetFile(Path.Combine(info.Path, "gamesystem.xml")); } diff --git a/MediaBrowser.Providers/Games/GameXmlProvider.cs b/MediaBrowser.Providers/Games/GameXmlProvider.cs index c12feb85c..6609f9d5e 100644 --- a/MediaBrowser.Providers/Games/GameXmlProvider.cs +++ b/MediaBrowser.Providers/Games/GameXmlProvider.cs @@ -22,7 +22,7 @@ namespace MediaBrowser.Providers.Games new GameXmlParser(_logger).Fetch(result.Item, path, cancellationToken); } - protected override FileInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) + protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) { var fileInfo = FileSystem.GetFileSystemInfo(info.Path); diff --git a/MediaBrowser.Providers/LiveTv/ChannelXmlProvider.cs b/MediaBrowser.Providers/LiveTv/ChannelXmlProvider.cs index 400ac825f..44a312e24 100644 --- a/MediaBrowser.Providers/LiveTv/ChannelXmlProvider.cs +++ b/MediaBrowser.Providers/LiveTv/ChannelXmlProvider.cs @@ -22,7 +22,7 @@ namespace MediaBrowser.Providers.LiveTv new BaseItemXmlParser<LiveTvChannel>(_logger).Fetch(result.Item, path, cancellationToken); } - protected override FileInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) + protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) { return directoryService.GetFile(Path.Combine(info.Path, "channel.xml")); } diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs index 2ecc6c9dd..74b54fe2c 100644 --- a/MediaBrowser.Providers/Manager/MetadataService.cs +++ b/MediaBrowser.Providers/Manager/MetadataService.cs @@ -305,22 +305,20 @@ namespace MediaBrowser.Providers.Manager refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.ImageUpdate; } - if (!string.IsNullOrEmpty(localItem.Item.Name)) + if (string.IsNullOrWhiteSpace(localItem.Item.Name)) { - MergeData(localItem.Item, temp, new List<MetadataFields>(), !options.ReplaceAllMetadata, true); - refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.MetadataImport; - - // Only one local provider allowed per item - hasLocalMetadata = true; - break; + localItem.Item.Name = item.Name ?? Path.GetFileNameWithoutExtension(item.Path); } - Logger.Error("Invalid local metadata found for: " + item.Path); - } - else - { - Logger.Debug("{0} returned no metadata for {1}", providerName, item.Path ?? item.Name); + MergeData(localItem.Item, temp, new List<MetadataFields>(), !options.ReplaceAllMetadata, true); + refreshResult.UpdateType = refreshResult.UpdateType | ItemUpdateType.MetadataImport; + + // Only one local provider allowed per item + hasLocalMetadata = true; + break; } + + Logger.Debug("{0} returned no metadata for {1}", providerName, item.Path ?? item.Name); } catch (OperationCanceledException) { diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index a5ea1b64b..79cbdfa68 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -75,6 +75,7 @@ <Compile Include="All\LocalImageProvider.cs" /> <Compile Include="Books\BookMetadataService.cs" /> <Compile Include="BoxSets\BoxSetMetadataService.cs" /> + <Compile Include="BoxSets\BoxSetXmlParser.cs" /> <Compile Include="BoxSets\MovieDbBoxSetImageProvider.cs" /> <Compile Include="BoxSets\MovieDbBoxSetProvider.cs" /> <Compile Include="Folders\CollectionFolderImageProvider.cs" /> diff --git a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs index c2279fb50..d516a8221 100644 --- a/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs +++ b/MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs @@ -357,7 +357,7 @@ namespace MediaBrowser.Providers.MediaInfo } } - public IEnumerable<FileInfo> GetSubtitleFiles(Video video, IDirectoryService directoryService) + public IEnumerable<FileSystemInfo> GetSubtitleFiles(Video video, IDirectoryService directoryService) { var containingPath = video.ContainingFolderPath; diff --git a/MediaBrowser.Providers/Movies/MovieXmlProvider.cs b/MediaBrowser.Providers/Movies/MovieXmlProvider.cs index bb7650255..63921fd6a 100644 --- a/MediaBrowser.Providers/Movies/MovieXmlProvider.cs +++ b/MediaBrowser.Providers/Movies/MovieXmlProvider.cs @@ -22,7 +22,7 @@ namespace MediaBrowser.Providers.Movies new MovieXmlParser(_logger).Fetch(result.Item, path, cancellationToken); } - protected override FileInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) + protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) { return GetXmlFileInfo(info, FileSystem); } diff --git a/MediaBrowser.Providers/Movies/TrailerXmlProvider.cs b/MediaBrowser.Providers/Movies/TrailerXmlProvider.cs index df90d32ca..7e53c9e7f 100644 --- a/MediaBrowser.Providers/Movies/TrailerXmlProvider.cs +++ b/MediaBrowser.Providers/Movies/TrailerXmlProvider.cs @@ -22,7 +22,7 @@ namespace MediaBrowser.Providers.Movies new MovieXmlParser(_logger).Fetch(result.Item, path, cancellationToken); } - protected override FileInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) + protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) { return MovieXmlProvider.GetXmlFileInfo(info, FileSystem); } diff --git a/MediaBrowser.Providers/Music/AlbumXmlProvider.cs b/MediaBrowser.Providers/Music/AlbumXmlProvider.cs index 60bc18b2f..73b914090 100644 --- a/MediaBrowser.Providers/Music/AlbumXmlProvider.cs +++ b/MediaBrowser.Providers/Music/AlbumXmlProvider.cs @@ -22,7 +22,7 @@ namespace MediaBrowser.Providers.Music new BaseItemXmlParser<MusicAlbum>(_logger).Fetch(result.Item, path, cancellationToken); } - protected override FileInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) + protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) { return directoryService.GetFile(Path.Combine(info.Path, "album.xml")); } diff --git a/MediaBrowser.Providers/Music/ArtistXmlProvider.cs b/MediaBrowser.Providers/Music/ArtistXmlProvider.cs index 89ba1c794..b221fde1e 100644 --- a/MediaBrowser.Providers/Music/ArtistXmlProvider.cs +++ b/MediaBrowser.Providers/Music/ArtistXmlProvider.cs @@ -22,7 +22,7 @@ namespace MediaBrowser.Providers.Music new BaseItemXmlParser<MusicArtist>(_logger).Fetch(result.Item, path, cancellationToken); } - protected override FileInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) + protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) { return directoryService.GetFile(Path.Combine(info.Path, "artist.xml")); } diff --git a/MediaBrowser.Providers/Music/MusicExternalIds.cs b/MediaBrowser.Providers/Music/MusicExternalIds.cs index a25ab9deb..a4eab1eee 100644 --- a/MediaBrowser.Providers/Music/MusicExternalIds.cs +++ b/MediaBrowser.Providers/Music/MusicExternalIds.cs @@ -23,7 +23,7 @@ namespace MediaBrowser.Providers.Music public bool Supports(IHasProviderIds item) { - return item is Audio || item is MusicAlbum || item is MusicArtist; + return item is Audio || item is MusicAlbum; } } diff --git a/MediaBrowser.Providers/Music/MusicVideoXmlProvider.cs b/MediaBrowser.Providers/Music/MusicVideoXmlProvider.cs index 4291f2369..93d9031c3 100644 --- a/MediaBrowser.Providers/Music/MusicVideoXmlProvider.cs +++ b/MediaBrowser.Providers/Music/MusicVideoXmlProvider.cs @@ -24,7 +24,7 @@ namespace MediaBrowser.Providers.Music new MusicVideoXmlParser(_logger).Fetch(result.Item, path, cancellationToken); } - protected override FileInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) + protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) { return MovieXmlProvider.GetXmlFileInfo(info, FileSystem); } diff --git a/MediaBrowser.Providers/People/PersonXmlProvider.cs b/MediaBrowser.Providers/People/PersonXmlProvider.cs index 4a145a1c0..b120c4830 100644 --- a/MediaBrowser.Providers/People/PersonXmlProvider.cs +++ b/MediaBrowser.Providers/People/PersonXmlProvider.cs @@ -22,7 +22,7 @@ namespace MediaBrowser.Providers.People new BaseItemXmlParser<Person>(_logger).Fetch(result.Item, path, cancellationToken); } - protected override FileInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) + protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) { return directoryService.GetFile(Path.Combine(info.Path, "person.xml")); } diff --git a/MediaBrowser.Providers/Photos/PhotoProvider.cs b/MediaBrowser.Providers/Photos/PhotoProvider.cs index 66344d1d1..a89919d3f 100644 --- a/MediaBrowser.Providers/Photos/PhotoProvider.cs +++ b/MediaBrowser.Providers/Photos/PhotoProvider.cs @@ -25,6 +25,7 @@ namespace MediaBrowser.Providers.Photos public Task<ItemUpdateType> FetchAsync(Photo item, IDirectoryService directoryService, CancellationToken cancellationToken) { item.SetImagePath(ImageType.Primary, item.Path); + item.SetImagePath(ImageType.Backdrop, item.Path); if (item.Path.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) || item.Path.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase)) { diff --git a/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs b/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs index b8ab55db0..03fe5c802 100644 --- a/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs +++ b/MediaBrowser.Providers/Savers/XmlSaverHelpers.cs @@ -1,4 +1,5 @@ using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Persistence; using MediaBrowser.Model.Entities; @@ -82,7 +83,8 @@ namespace MediaBrowser.Providers.Savers "TVRageId", "VoteCount", "Website", - "Zap2ItId" + "Zap2ItId", + "CollectionItems" }.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase); @@ -580,6 +582,12 @@ namespace MediaBrowser.Providers.Savers builder.Append("</Persons>"); } + + var folder = item as BoxSet; + if (folder != null) + { + AddCollectionItems(folder, builder); + } } public static void AddChapters(Video item, StringBuilder builder, IItemRepository repository) @@ -631,5 +639,34 @@ namespace MediaBrowser.Providers.Savers } } } + + public static void AddCollectionItems(Folder item, StringBuilder builder) + { + var items = item.LinkedChildren + .Where(i => i.Type == LinkedChildType.Manual && !string.IsNullOrWhiteSpace(i.ItemName)) + .ToList(); + + if (items.Count == 0) + { + return; + } + + builder.Append("<CollectionItems>"); + foreach (var link in items) + { + builder.Append("<CollectionItem>"); + + builder.Append("<Name>" + SecurityElement.Escape(link.ItemName) + "</Name>"); + builder.Append("<Type>" + SecurityElement.Escape(link.ItemType) + "</Type>"); + + if (link.ItemYear.HasValue) + { + builder.Append("<Year>" + SecurityElement.Escape(link.ItemYear.Value.ToString(UsCulture)) + "</Year>"); + } + + builder.Append("</CollectionItem>"); + } + builder.Append("</CollectionItems>"); + } } } diff --git a/MediaBrowser.Providers/TV/EpisodeXmlProvider.cs b/MediaBrowser.Providers/TV/EpisodeXmlProvider.cs index 2590fbce8..44755b18a 100644 --- a/MediaBrowser.Providers/TV/EpisodeXmlProvider.cs +++ b/MediaBrowser.Providers/TV/EpisodeXmlProvider.cs @@ -27,7 +27,7 @@ namespace MediaBrowser.Providers.TV result.Images = images; } - protected override FileInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) + protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) { var metadataPath = Path.GetDirectoryName(info.Path); metadataPath = Path.Combine(metadataPath, "metadata"); diff --git a/MediaBrowser.Providers/TV/SeasonXmlProvider.cs b/MediaBrowser.Providers/TV/SeasonXmlProvider.cs index e53a2deff..c2d3fe2d1 100644 --- a/MediaBrowser.Providers/TV/SeasonXmlProvider.cs +++ b/MediaBrowser.Providers/TV/SeasonXmlProvider.cs @@ -25,7 +25,7 @@ namespace MediaBrowser.Providers.TV new BaseItemXmlParser<Season>(_logger).Fetch(result.Item, path, cancellationToken); } - protected override FileInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) + protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) { return directoryService.GetFile(Path.Combine(info.Path, "season.xml")); } diff --git a/MediaBrowser.Providers/TV/SeriesXmlProvider.cs b/MediaBrowser.Providers/TV/SeriesXmlProvider.cs index 53599ef55..f32afbd96 100644 --- a/MediaBrowser.Providers/TV/SeriesXmlProvider.cs +++ b/MediaBrowser.Providers/TV/SeriesXmlProvider.cs @@ -25,7 +25,7 @@ namespace MediaBrowser.Providers.TV new SeriesXmlParser(_logger).Fetch(result.Item, path, cancellationToken); } - protected override FileInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) + protected override FileSystemInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) { return directoryService.GetFile(Path.Combine(info.Path, "series.xml")); } diff --git a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs new file mode 100644 index 000000000..9a196cc47 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs @@ -0,0 +1,201 @@ +using MediaBrowser.Common.IO; +using MediaBrowser.Controller.Collections; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Server.Implementations.Collections +{ + public class CollectionManager : ICollectionManager + { + private readonly ILibraryManager _libraryManager; + private readonly IFileSystem _fileSystem; + private readonly ILibraryMonitor _iLibraryMonitor; + + public CollectionManager(ILibraryManager libraryManager, IFileSystem fileSystem, ILibraryMonitor iLibraryMonitor) + { + _libraryManager = libraryManager; + _fileSystem = fileSystem; + _iLibraryMonitor = iLibraryMonitor; + } + + public async Task CreateCollection(CollectionCreationOptions options) + { + var name = options.Name; + + // Need to use the [boxset] suffix + // If internet metadata is not found, or if xml saving is off there will be no collection.xml + // This could cause it to get re-resolved as a plain folder + var folderName = _fileSystem.GetValidFilename(name) + " [boxset]"; + + var parentFolder = GetParentFolder(options.ParentId); + + if (parentFolder == null) + { + throw new ArgumentException(); + } + + var path = Path.Combine(parentFolder.Path, folderName); + + _iLibraryMonitor.ReportFileSystemChangeBeginning(path); + + try + { + Directory.CreateDirectory(path); + + var collection = new BoxSet + { + Name = name, + Parent = parentFolder, + DisplayMediaType = "Collection", + Path = path, + DontFetchMeta = options.IsLocked, + ProviderIds = options.ProviderIds + }; + + await parentFolder.AddChild(collection, CancellationToken.None).ConfigureAwait(false); + + await collection.RefreshMetadata(new MetadataRefreshOptions(), CancellationToken.None) + .ConfigureAwait(false); + } + finally + { + // Refresh handled internally + _iLibraryMonitor.ReportFileSystemChangeComplete(path, false); + } + } + + private Folder GetParentFolder(Guid? parentId) + { + if (parentId.HasValue) + { + if (parentId.Value == Guid.Empty) + { + throw new ArgumentNullException("parentId"); + } + + var folder = _libraryManager.GetItemById(parentId.Value) as Folder; + + // Find an actual physical folder + if (folder is CollectionFolder) + { + return _libraryManager.RootFolder.Children.OfType<Folder>().First(i => folder.PhysicalLocations.Contains(i.Path, StringComparer.OrdinalIgnoreCase)); + } + } + + return _libraryManager.RootFolder.Children.OfType<ManualCollectionsFolder>().FirstOrDefault() ?? + _libraryManager.RootFolder.GetHiddenChildren().OfType<ManualCollectionsFolder>().FirstOrDefault(); + } + + public async Task AddToCollection(Guid collectionId, IEnumerable<Guid> ids) + { + var collection = _libraryManager.GetItemById(collectionId) as BoxSet; + + if (collection == null) + { + throw new ArgumentException("No collection exists with the supplied Id"); + } + + var list = new List<LinkedChild>(); + + foreach (var itemId in ids) + { + var item = _libraryManager.GetItemById(itemId); + + if (item == null) + { + throw new ArgumentException("No item exists with the supplied Id"); + } + + if (collection.LinkedChildren.Any(i => i.ItemId.HasValue && i.ItemId == itemId)) + { + throw new ArgumentException("Item already exists in collection"); + } + + list.Add(new LinkedChild + { + ItemName = item.Name, + ItemYear = item.ProductionYear, + ItemType = item.GetType().Name, + Type = LinkedChildType.Manual + }); + } + + collection.LinkedChildren.AddRange(list); + + await collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + + await collection.RefreshMetadata(CancellationToken.None).ConfigureAwait(false); + } + + public async Task RemoveFromCollection(Guid collectionId, IEnumerable<Guid> itemIds) + { + var collection = _libraryManager.GetItemById(collectionId) as BoxSet; + + if (collection == null) + { + throw new ArgumentException("No collection exists with the supplied Id"); + } + + var list = new List<LinkedChild>(); + + foreach (var itemId in itemIds) + { + var child = collection.LinkedChildren.FirstOrDefault(i => i.ItemId.HasValue && i.ItemId.Value == itemId); + + if (child == null) + { + throw new ArgumentException("No collection title exists with the supplied Id"); + } + + list.Add(child); + } + + var shortcutFiles = Directory + .EnumerateFiles(collection.Path, "*", SearchOption.TopDirectoryOnly) + .Where(i => _fileSystem.IsShortcut(i)) + .ToList(); + + var shortcutFilesToDelete = list.Where(child => !string.IsNullOrWhiteSpace(child.Path) && child.Type == LinkedChildType.Shortcut) + .Select(child => shortcutFiles.FirstOrDefault(i => string.Equals(child.Path, _fileSystem.ResolveShortcut(i), StringComparison.OrdinalIgnoreCase))) + .Where(i => !string.IsNullOrWhiteSpace(i)) + .ToList(); + + foreach (var file in shortcutFilesToDelete) + { + _iLibraryMonitor.ReportFileSystemChangeBeginning(file); + } + + try + { + foreach (var file in shortcutFilesToDelete) + { + File.Delete(file); + } + + foreach (var child in list) + { + collection.LinkedChildren.Remove(child); + } + } + finally + { + foreach (var file in shortcutFilesToDelete) + { + _iLibraryMonitor.ReportFileSystemChangeComplete(file, false); + } + } + + await collection.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); + + await collection.RefreshMetadata(CancellationToken.None).ConfigureAwait(false); + } + } +} diff --git a/MediaBrowser.Server.Implementations/Collections/CollectionsDynamicFolder.cs b/MediaBrowser.Server.Implementations/Collections/CollectionsDynamicFolder.cs new file mode 100644 index 000000000..834fbcd31 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Collections/CollectionsDynamicFolder.cs @@ -0,0 +1,55 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Controller.Entities; +using System.IO; +using System.Linq; + +namespace MediaBrowser.Server.Implementations.Collections +{ + public class CollectionsDynamicFolder : IVirtualFolderCreator + { + private readonly IApplicationPaths _appPaths; + + public CollectionsDynamicFolder(IApplicationPaths appPaths) + { + _appPaths = appPaths; + } + + public BasePluginFolder GetFolder() + { + var path = Path.Combine(_appPaths.DataPath, "collections"); + + Directory.CreateDirectory(path); + + return new ManualCollectionsFolder + { + Path = path + }; + } + } + + public class ManualCollectionsFolder : BasePluginFolder + { + public ManualCollectionsFolder() + { + Name = "Collections"; + } + + public override bool IsVisible(User user) + { + if (!GetChildren(user, true).Any()) + { + return false; + } + + return base.IsVisible(user); + } + + public override bool IsHidden + { + get + { + return !ActualChildren.Any() || base.IsHidden; + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Dto/DtoService.cs b/MediaBrowser.Server.Implementations/Dto/DtoService.cs index 7bf87875e..fadf4c900 100644 --- a/MediaBrowser.Server.Implementations/Dto/DtoService.cs +++ b/MediaBrowser.Server.Implementations/Dto/DtoService.cs @@ -121,41 +121,51 @@ namespace MediaBrowser.Server.Implementations.Dto } } - var itemByName = item as IItemByName; - if (itemByName != null) - { - AttachItemByNameCounts(dto, itemByName, user); - } - return dto; } - /// <summary> - /// Attaches the item by name counts. - /// </summary> - /// <param name="dto">The dto.</param> - /// <param name="item">The item.</param> - /// <param name="user">The user.</param> - private void AttachItemByNameCounts(BaseItemDto dto, IItemByName item, User user) + public BaseItemDto GetItemByNameDto<T>(T item, List<ItemFields> fields, User user = null) + where T : BaseItem, IItemByName { - if (user == null) + var libraryItems = user != null ? user.RootFolder.GetRecursiveChildren(user) : + _libraryManager.RootFolder.RecursiveChildren; + + return GetItemByNameDto(item, fields, item.GetTaggedItems(libraryItems).ToList(), user); + } + + public BaseItemDto GetItemByNameDto<T>(T item, List<ItemFields> fields, List<BaseItem> taggedItems, User user = null) + where T : BaseItem, IItemByName + { + var dto = GetBaseItemDto(item, fields, user); + + if (item is MusicArtist || item is MusicGenre) { - return; + dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum); + dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo); + dto.SongCount = taggedItems.Count(i => i is Audio); + } + else if (item is GameGenre) + { + dto.GameCount = taggedItems.Count(i => i is Game); } + else + { + // This populates them all and covers Genre, Person, Studio, Year - var counts = item.GetItemByNameCounts(user.Id) ?? new ItemByNameCounts(); + dto.AdultVideoCount = taggedItems.Count(i => i is AdultVideo); + dto.AlbumCount = taggedItems.Count(i => i is MusicAlbum); + dto.EpisodeCount = taggedItems.Count(i => i is Episode); + dto.GameCount = taggedItems.Count(i => i is Game); + dto.MovieCount = taggedItems.Count(i => i is Movie); + dto.MusicVideoCount = taggedItems.Count(i => i is MusicVideo); + dto.SeriesCount = taggedItems.Count(i => i is Series); + dto.SongCount = taggedItems.Count(i => i is Audio); + dto.TrailerCount = taggedItems.Count(i => i is Trailer); + } - dto.ChildCount = counts.TotalCount; + dto.ChildCount = taggedItems.Count; - dto.AdultVideoCount = counts.AdultVideoCount; - dto.AlbumCount = counts.AlbumCount; - dto.EpisodeCount = counts.EpisodeCount; - dto.GameCount = counts.GameCount; - dto.MovieCount = counts.MovieCount; - dto.MusicVideoCount = counts.MusicVideoCount; - dto.SeriesCount = counts.SeriesCount; - dto.SongCount = counts.SongCount; - dto.TrailerCount = counts.TrailerCount; + return dto; } /// <summary> @@ -297,6 +307,57 @@ namespace MediaBrowser.Server.Implementations.Dto info.PrimaryImageTag = GetImageCacheTag(item, ImageType.Primary); + var backropItem = item.HasImage(ImageType.Backdrop) ? item : null; + + var thumbItem = item.HasImage(ImageType.Thumb) ? item : null; + + if (thumbItem == null) + { + var episode = item as Episode; + + if (episode != null) + { + var series = episode.Series; + + if (series != null && series.HasImage(ImageType.Thumb)) + { + thumbItem = series; + } + } + } + + if (backropItem == null) + { + var episode = item as Episode; + + if (episode != null) + { + var series = episode.Series; + + if (series != null && series.HasImage(ImageType.Backdrop)) + { + backropItem = series; + } + } + } + + if (thumbItem == null) + { + thumbItem = item.Parents.FirstOrDefault(i => i.HasImage(ImageType.Thumb)); + } + + if (thumbItem != null) + { + info.ThumbImageTag = GetImageCacheTag(thumbItem, ImageType.Thumb); + info.ThumbItemId = GetDtoId(thumbItem); + } + + if (thumbItem != null) + { + info.BackdropImageTag = GetImageCacheTag(backropItem, ImageType.Backdrop); + info.BackdropItemId = GetDtoId(backropItem); + } + return info; } diff --git a/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs b/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs index c0d784fcc..ad2852a91 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs +++ b/MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs @@ -3,6 +3,8 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.Logging; using Mono.Nat; +using Mono.Nat.Enums; +using Mono.Nat.EventArgs; using System; using System.IO; using System.Text; diff --git a/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs index b91067dd7..5d326f1ca 100644 --- a/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs +++ b/MediaBrowser.Server.Implementations/FileOrganization/EpisodeFileOrganizer.cs @@ -171,12 +171,23 @@ namespace MediaBrowser.Server.Implementations.FileOrganization var fileExists = File.Exists(result.TargetPath); var otherDuplicatePaths = GetOtherDuplicatePaths(result.TargetPath, series, seasonNumber, episodeNumber, endingEpiosdeNumber); - if (!overwriteExisting && (fileExists || otherDuplicatePaths.Count > 0)) + if (!overwriteExisting) { - result.Status = FileSortingStatus.SkippedExisting; - result.StatusMessage = string.Empty; - result.DuplicatePaths = otherDuplicatePaths; - return; + if (options.CopyOriginalFile && fileExists && IsSameEpisode(sourcePath, newPath)) + { + _logger.Info("File {0} already copied to new path {1}, stopping organization", sourcePath, newPath); + result.Status = FileSortingStatus.SkippedExisting; + result.StatusMessage = string.Empty; + return; + } + + if (fileExists || otherDuplicatePaths.Count > 0) + { + result.Status = FileSortingStatus.SkippedExisting; + result.StatusMessage = string.Empty; + result.DuplicatePaths = otherDuplicatePaths; + return; + } } PerformFileSorting(options, result); @@ -266,7 +277,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization try { - if (copy) + if (copy || options.CopyOriginalFile) { File.Copy(result.OriginalPath, result.TargetPath, true); } @@ -293,7 +304,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization _libraryMonitor.ReportFileSystemChangeComplete(result.TargetPath, true); } - if (copy) + if (copy && !options.CopyOriginalFile) { try { @@ -439,5 +450,27 @@ namespace MediaBrowser.Server.Implementations.FileOrganization .Replace("%0e", episodeNumber.ToString("00", _usCulture)) .Replace("%00e", episodeNumber.ToString("000", _usCulture)); } + + private bool IsSameEpisode(string sourcePath, string newPath) + { + + FileInfo sourceFileInfo = new FileInfo(sourcePath); + FileInfo destinationFileInfo = new FileInfo(newPath); + + try + { + if (sourceFileInfo.Length == destinationFileInfo.Length) + { + return true; + } + } + catch (FileNotFoundException) + { + return false; + } + + return false; + + } } } diff --git a/MediaBrowser.Server.Implementations/FileOrganization/TvFolderOrganizer.cs b/MediaBrowser.Server.Implementations/FileOrganization/TvFolderOrganizer.cs index 7edcf9739..82bb9862c 100644 --- a/MediaBrowser.Server.Implementations/FileOrganization/TvFolderOrganizer.cs +++ b/MediaBrowser.Server.Implementations/FileOrganization/TvFolderOrganizer.cs @@ -61,7 +61,7 @@ namespace MediaBrowser.Server.Implementations.FileOrganization var organizer = new EpisodeFileOrganizer(_organizationService, _config, _fileSystem, _logger, _libraryManager, _libraryMonitor, _providerManager); - var result = await organizer.OrganizeEpisodeFile(file.FullName, options, false, cancellationToken).ConfigureAwait(false); + var result = await organizer.OrganizeEpisodeFile(file.FullName, options, options.OverwriteExistingEpisodes, cancellationToken).ConfigureAwait(false); if (result.Status == FileSortingStatus.Success) { diff --git a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs index 8455d9f58..925ef8050 100644 --- a/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs +++ b/MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs @@ -306,6 +306,18 @@ namespace MediaBrowser.Server.Implementations.HttpServer throw new ArgumentNullException("path"); } + return GetStaticFileResult(requestContext, path, MimeTypes.GetMimeType(path), fileShare, responseHeaders, isHeadRequest); + } + + public object GetStaticFileResult(IRequest requestContext, string path, string contentType, + FileShare fileShare = FileShare.Read, IDictionary<string, string> responseHeaders = null, + bool isHeadRequest = false) + { + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentNullException("path"); + } + if (fileShare != FileShare.Read && fileShare != FileShare.ReadWrite) { throw new ArgumentException("FileShare must be either Read or ReadWrite"); @@ -315,7 +327,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer var cacheKey = path + dateModified.Ticks; - return GetStaticResult(requestContext, cacheKey.GetMD5(), dateModified, null, MimeTypes.GetMimeType(path), () => Task.FromResult(GetFileStream(path, fileShare)), responseHeaders, isHeadRequest); + return GetStaticResult(requestContext, cacheKey.GetMD5(), dateModified, null, contentType, () => Task.FromResult(GetFileStream(path, fileShare)), responseHeaders, isHeadRequest); } /// <summary> diff --git a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs index cbc4a8c24..b2f0a2769 100644 --- a/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs +++ b/MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs @@ -449,7 +449,14 @@ namespace MediaBrowser.Server.Implementations.IO var paths = _affectedPaths.Keys.ToList(); _affectedPaths.Clear(); - await ProcessPathChanges(paths).ConfigureAwait(false); + try + { + await ProcessPathChanges(paths).ConfigureAwait(false); + } + catch (Exception ex) + { + Logger.ErrorException("Error processing directory changes", ex); + } } private void DisposeTimer() diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index 6e9d803bf..0b1947d4c 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -5,6 +5,7 @@ using MediaBrowser.Common.ScheduledTasks; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Entities.Movies; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; @@ -484,6 +485,9 @@ namespace MediaBrowser.Server.Implementations.Library await ItemRepository.DeleteItem(child.Id, CancellationToken.None).ConfigureAwait(false); } + BaseItem removed; + _libraryItemsCache.TryRemove(item.Id, out removed); + ReportItemRemoved(item); } @@ -922,10 +926,10 @@ namespace MediaBrowser.Server.Implementations.Library /// <returns>Task.</returns> public Task ValidatePeople(CancellationToken cancellationToken, IProgress<double> progress) { - // Ensure the location is unavailable. + // Ensure the location is available. Directory.CreateDirectory(ConfigurationManager.ApplicationPaths.PeoplePath); - return new PeopleValidator(this, _logger).ValidatePeople(cancellationToken, progress); + return new PeopleValidator(this, _logger).ValidatePeople(cancellationToken, new MetadataRefreshOptions(), progress); } /// <summary> @@ -953,7 +957,7 @@ namespace MediaBrowser.Server.Implementations.Library // Ensure the location is unavailable. Directory.CreateDirectory(ConfigurationManager.ApplicationPaths.MusicGenrePath); - return new MusicGenresValidator(this, _userManager, _logger).Run(progress, cancellationToken); + return new MusicGenresValidator(this, _logger).Run(progress, cancellationToken); } /// <summary> diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs index 871171541..ac1927931 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs @@ -48,7 +48,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio var collectionType = args.GetCollectionType(); - // If there's a collection type and it's not music, it can't be a series + // If there's a collection type and it's not music, don't allow it. if (!string.IsNullOrEmpty(collectionType) && !string.Equals(collectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase)) { diff --git a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs index ac9b42efb..16c0d1a27 100644 --- a/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs +++ b/MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs @@ -56,21 +56,6 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies { return null; } - - // If the parent is not a boxset, the only other allowed parent type is Folder - if (!(args.Parent is BoxSet)) - { - if (args.Parent.GetType() != typeof(Folder)) - { - return null; - } - } - } - - // Since the looping is expensive, this is an optimization to help us avoid it - if (args.Path.IndexOf("[tvdbid", StringComparison.OrdinalIgnoreCase) != -1) - { - return null; } var isDirectory = args.IsDirectory; @@ -89,34 +74,31 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies // Find movies with their own folders if (isDirectory) { - if (args.Path.IndexOf("[trailers]", StringComparison.OrdinalIgnoreCase) != -1 || - string.Equals(collectionType, CollectionType.Trailers, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(collectionType, CollectionType.Trailers, StringComparison.OrdinalIgnoreCase)) { - return FindMovie<Trailer>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService); + return FindMovie<Trailer>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, false); } - if (args.Path.IndexOf("[musicvideos]", StringComparison.OrdinalIgnoreCase) != -1 || - string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase)) { - return FindMovie<MusicVideo>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService); + return FindMovie<MusicVideo>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, false); } - if (args.Path.IndexOf("[adultvideos]", StringComparison.OrdinalIgnoreCase) != -1 || - string.Equals(collectionType, CollectionType.AdultVideos, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(collectionType, CollectionType.AdultVideos, StringComparison.OrdinalIgnoreCase)) { - return FindMovie<AdultVideo>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService); + return FindMovie<AdultVideo>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, true); } if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase)) { - return FindMovie<Video>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService); + return FindMovie<Video>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, true); } if (string.IsNullOrEmpty(collectionType) || string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase) || string.Equals(collectionType, CollectionType.BoxSets, StringComparison.OrdinalIgnoreCase)) { - return FindMovie<Movie>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService); + return FindMovie<Movie>(args.Path, args.Parent, args.FileSystemChildren, args.DirectoryService, true); } return null; @@ -202,8 +184,10 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies /// <param name="path">The path.</param> /// <param name="parent">The parent.</param> /// <param name="fileSystemEntries">The file system entries.</param> + /// <param name="directoryService">The directory service.</param> + /// <param name="supportMultiFileItems">if set to <c>true</c> [support multi file items].</param> /// <returns>Movie.</returns> - private T FindMovie<T>(string path, Folder parent, IEnumerable<FileSystemInfo> fileSystemEntries, IDirectoryService directoryService) + private T FindMovie<T>(string path, Folder parent, IEnumerable<FileSystemInfo> fileSystemEntries, IDirectoryService directoryService, bool supportMultiFileItems) where T : Video, new() { var movies = new List<T>(); @@ -264,7 +248,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies } } - if (movies.Count > 1) + if (movies.Count > 1 && supportMultiFileItems) { return GetMultiFileMovie(movies); } diff --git a/MediaBrowser.Server.Implementations/Library/SearchEngine.cs b/MediaBrowser.Server.Implementations/Library/SearchEngine.cs index 12686f542..aaafd35a9 100644 --- a/MediaBrowser.Server.Implementations/Library/SearchEngine.cs +++ b/MediaBrowser.Server.Implementations/Library/SearchEngine.cs @@ -37,6 +37,12 @@ namespace MediaBrowser.Server.Implementations.Library var results = await GetSearchHints(inputItems, query).ConfigureAwait(false); + // Include item types + if (query.IncludeItemTypes.Length > 0) + { + results = results.Where(f => query.IncludeItemTypes.Contains(f.Item.GetType().Name, StringComparer.OrdinalIgnoreCase)); + } + var searchResultArray = results.ToArray(); results = searchResultArray; @@ -96,7 +102,9 @@ namespace MediaBrowser.Server.Implementations.Library if (query.IncludeArtists) { // Find artists - var artists = _libraryManager.GetAllArtists(items) + var artists = items.OfType<Audio>() + .SelectMany(i => i.AllArtists) + .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); foreach (var item in artists) diff --git a/MediaBrowser.Server.Implementations/Library/UserManager.cs b/MediaBrowser.Server.Implementations/Library/UserManager.cs index 8654a26ce..06028d37e 100644 --- a/MediaBrowser.Server.Implementations/Library/UserManager.cs +++ b/MediaBrowser.Server.Implementations/Library/UserManager.cs @@ -189,15 +189,10 @@ namespace MediaBrowser.Server.Implementations.Library /// Refreshes metadata for each user /// </summary> /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="force">if set to <c>true</c> [force].</param> /// <returns>Task.</returns> - public Task RefreshUsersMetadata(CancellationToken cancellationToken, bool force = false) + public Task RefreshUsersMetadata(CancellationToken cancellationToken) { - var tasks = Users.Select(user => user.RefreshMetadata(new MetadataRefreshOptions - { - ReplaceAllMetadata = force - - }, cancellationToken)).ToList(); + var tasks = Users.Select(user => user.RefreshMetadata(new MetadataRefreshOptions(), cancellationToken)).ToList(); return Task.WhenAll(tasks); } diff --git a/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs index 1d9eea866..5968d847e 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/ArtistsValidator.cs @@ -1,8 +1,6 @@ using MediaBrowser.Common.Progress; -using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; -using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; using MediaBrowser.Model.Logging; using System; @@ -69,10 +67,6 @@ namespace MediaBrowser.Server.Implementations.Library.Validators var numComplete = 0; - var userLibraries = _userManager.Users - .Select(i => new Tuple<Guid, List<IHasArtist>>(i.Id, i.RootFolder.GetRecursiveChildren(i).OfType<IHasArtist>().ToList())) - .ToList(); - var numArtists = allArtists.Count; foreach (var artist in allArtists) @@ -91,11 +85,6 @@ namespace MediaBrowser.Server.Implementations.Library.Validators .ToList(); } - foreach (var lib in userLibraries) - { - SetItemCounts(artist, lib.Item1, lib.Item2); - } - numComplete++; double percent = numComplete; percent /= numArtists; @@ -108,37 +97,6 @@ namespace MediaBrowser.Server.Implementations.Library.Validators } /// <summary> - /// Sets the item counts. - /// </summary> - /// <param name="artist">The artist.</param> - /// <param name="userId">The user id.</param> - /// <param name="allItems">All items.</param> - private void SetItemCounts(MusicArtist artist, Guid? userId, IEnumerable<IHasArtist> allItems) - { - var name = artist.Name; - - var items = allItems - .Where(i => i.HasArtist(name)) - .ToList(); - - var counts = new ItemByNameCounts - { - TotalCount = items.Count, - - SongCount = items.OfType<Audio>().Count(), - - AlbumCount = items.OfType<MusicAlbum>().Count(), - - MusicVideoCount = items.OfType<MusicVideo>().Count() - }; - - if (userId.HasValue) - { - artist.SetItemByNameCounts(userId.Value, counts); - } - } - - /// <summary> /// Gets all artists. /// </summary> /// <param name="allSongs">All songs.</param> @@ -147,7 +105,8 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// <returns>Task{Artist[]}.</returns> private async Task<List<MusicArtist>> GetAllArtists(IEnumerable<Audio> allSongs, CancellationToken cancellationToken, IProgress<double> progress) { - var allArtists = _libraryManager.GetAllArtists(allSongs) + var allArtists = allSongs.SelectMany(i => i.AllArtists) + .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); var returnArtists = new List<MusicArtist>(allArtists.Count); diff --git a/MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs index 9e64c7810..6b658e175 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/GameGenresValidator.cs @@ -2,7 +2,6 @@ using MediaBrowser.Controller.Library; using MediaBrowser.Model.Logging; using System; -using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -41,38 +40,24 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// <returns>Task.</returns> public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) { - var userLibraries = _userManager.Users - .Select(i => new Tuple<Guid, List<Game>>(i.Id, i.RootFolder.GetRecursiveChildren(i).OfType<Game>().ToList())) + var items = _libraryManager.RootFolder.RecursiveChildren.Where(i => (i is Game)) + .SelectMany(i => i.Genres) + .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); - var masterDictionary = new Dictionary<string, Dictionary<Guid, Dictionary<CountType, int>>>(StringComparer.OrdinalIgnoreCase); - progress.Report(2); - var numComplete = 0; + var count = items.Count; - foreach (var lib in userLibraries) + foreach (var name in items) { - SetItemCounts(lib.Item1, lib.Item2, masterDictionary); - - numComplete++; - double percent = numComplete; - percent /= userLibraries.Count; - percent *= 8; - - progress.Report(percent); - } + cancellationToken.ThrowIfCancellationRequested(); - progress.Report(10); - - var count = masterDictionary.Count; - numComplete = 0; - - foreach (var name in masterDictionary.Keys) - { try { - await UpdateItemByNameCounts(name, cancellationToken, masterDictionary[name]).ConfigureAwait(false); + var itemByName = _libraryManager.GetGameGenre(name); + + await itemByName.RefreshMetadata(cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) { @@ -81,7 +66,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators } catch (Exception ex) { - _logger.ErrorException("Error updating counts for {0}", ex, name); + _logger.ErrorException("Error refreshing {0}", ex, name); } numComplete++; @@ -94,32 +79,5 @@ namespace MediaBrowser.Server.Implementations.Library.Validators progress.Report(100); } - - private Task UpdateItemByNameCounts(string name, CancellationToken cancellationToken, Dictionary<Guid, Dictionary<CountType, int>> counts) - { - var itemByName = _libraryManager.GetGameGenre(name); - - foreach (var libraryId in counts.Keys) - { - var itemCounts = CountHelpers.GetCounts(counts[libraryId]); - - itemByName.SetItemByNameCounts(libraryId, itemCounts); - } - - return itemByName.RefreshMetadata(cancellationToken); - } - - private void SetItemCounts(Guid userId, IEnumerable<BaseItem> allItems, Dictionary<string, Dictionary<Guid, Dictionary<CountType, int>>> masterDictionary) - { - foreach (var media in allItems) - { - var names = media - .Genres - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); - - CountHelpers.SetItemCounts(userId, media, names, masterDictionary); - } - } } } diff --git a/MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs index e0a9e2ce8..b0dee9aaf 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/GenresValidator.cs @@ -3,7 +3,6 @@ using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Logging; using System; -using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -42,38 +41,24 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// <returns>Task.</returns> public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) { - var userLibraries = _userManager.Users - .Select(i => new Tuple<Guid, IList<BaseItem>>(i.Id, i.RootFolder.GetRecursiveChildren(i, m => !(m is IHasMusicGenres) && !(m is Game)))) + var items = _libraryManager.RootFolder.RecursiveChildren.Where(i => !(i is IHasMusicGenres) && !(i is Game)) + .SelectMany(i => i.Genres) + .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); - var masterDictionary = new Dictionary<string, Dictionary<Guid, Dictionary<CountType, int>>>(StringComparer.OrdinalIgnoreCase); - progress.Report(2); - var numComplete = 0; + var count = items.Count; - foreach (var lib in userLibraries) + foreach (var name in items) { - SetItemCounts(lib.Item1, lib.Item2, masterDictionary); - - numComplete++; - double percent = numComplete; - percent /= userLibraries.Count; - percent *= 8; - - progress.Report(percent); - } + cancellationToken.ThrowIfCancellationRequested(); - progress.Report(10); - - var count = masterDictionary.Count; - numComplete = 0; - - foreach (var name in masterDictionary.Keys) - { try { - await UpdateItemByNameCounts(name, cancellationToken, masterDictionary[name]).ConfigureAwait(false); + var itemByName = _libraryManager.GetGenre(name); + + await itemByName.RefreshMetadata(cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) { @@ -82,7 +67,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators } catch (Exception ex) { - _logger.ErrorException("Error updating counts for {0}", ex, name); + _logger.ErrorException("Error refreshing {0}", ex, name); } numComplete++; @@ -95,32 +80,5 @@ namespace MediaBrowser.Server.Implementations.Library.Validators progress.Report(100); } - - private Task UpdateItemByNameCounts(string name, CancellationToken cancellationToken, Dictionary<Guid, Dictionary<CountType, int>> counts) - { - var itemByName = _libraryManager.GetGenre(name); - - foreach (var libraryId in counts.Keys) - { - var itemCounts = CountHelpers.GetCounts(counts[libraryId]); - - itemByName.SetItemByNameCounts(libraryId, itemCounts); - } - - return itemByName.RefreshMetadata(cancellationToken); - } - - private void SetItemCounts(Guid userId, IEnumerable<BaseItem> allItems, Dictionary<string, Dictionary<Guid, Dictionary<CountType, int>>> masterDictionary) - { - foreach (var media in allItems) - { - var names = media - .Genres - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); - - CountHelpers.SetItemCounts(userId, media, names, masterDictionary); - } - } } } diff --git a/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs index b55ab1cbe..aa6c6281e 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/MusicGenresValidator.cs @@ -1,9 +1,7 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Library; using MediaBrowser.Model.Logging; using System; -using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -18,19 +16,13 @@ namespace MediaBrowser.Server.Implementations.Library.Validators private readonly ILibraryManager _libraryManager; /// <summary> - /// The _user manager - /// </summary> - private readonly IUserManager _userManager; - - /// <summary> /// The _logger /// </summary> private readonly ILogger _logger; - public MusicGenresValidator(ILibraryManager libraryManager, IUserManager userManager, ILogger logger) + public MusicGenresValidator(ILibraryManager libraryManager, ILogger logger) { _libraryManager = libraryManager; - _userManager = userManager; _logger = logger; } @@ -42,38 +34,24 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// <returns>Task.</returns> public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) { - var userLibraries = _userManager.Users - .Select(i => new Tuple<Guid, IList<BaseItem>>(i.Id, i.RootFolder.GetRecursiveChildren(i, m => m is IHasMusicGenres))) + var items = _libraryManager.RootFolder.RecursiveChildren.Where(i => (i is IHasMusicGenres)) + .SelectMany(i => i.Genres) + .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); - var masterDictionary = new Dictionary<string, Dictionary<Guid, Dictionary<CountType, int>>>(StringComparer.OrdinalIgnoreCase); - progress.Report(2); - var numComplete = 0; + var count = items.Count; - foreach (var lib in userLibraries) + foreach (var name in items) { - SetItemCounts(lib.Item1, lib.Item2, masterDictionary); - - numComplete++; - double percent = numComplete; - percent /= userLibraries.Count; - percent *= 8; - - progress.Report(percent); - } - - progress.Report(10); + cancellationToken.ThrowIfCancellationRequested(); - var count = masterDictionary.Count; - numComplete = 0; - - foreach (var name in masterDictionary.Keys) - { try { - await UpdateItemByNameCounts(name, cancellationToken, masterDictionary[name]).ConfigureAwait(false); + var itemByName = _libraryManager.GetMusicGenre(name); + + await itemByName.RefreshMetadata(cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) { @@ -82,7 +60,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators } catch (Exception ex) { - _logger.ErrorException("Error updating counts for {0}", ex, name); + _logger.ErrorException("Error refreshing {0}", ex, name); } numComplete++; @@ -95,32 +73,5 @@ namespace MediaBrowser.Server.Implementations.Library.Validators progress.Report(100); } - - private Task UpdateItemByNameCounts(string name, CancellationToken cancellationToken, Dictionary<Guid, Dictionary<CountType, int>> counts) - { - var itemByName = _libraryManager.GetMusicGenre(name); - - foreach (var libraryId in counts.Keys) - { - var itemCounts = CountHelpers.GetCounts(counts[libraryId]); - - itemByName.SetItemByNameCounts(libraryId, itemCounts); - } - - return itemByName.RefreshMetadata(cancellationToken); - } - - private void SetItemCounts(Guid userId, IEnumerable<BaseItem> allItems, Dictionary<string, Dictionary<Guid, Dictionary<CountType, int>>> masterDictionary) - { - foreach (var media in allItems) - { - var names = media - .Genres - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); - - CountHelpers.SetItemCounts(userId, media, names, masterDictionary); - } - } } } diff --git a/MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs b/MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs index 86c5dbc4c..d11e62a1a 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/PeoplePostScanTask.cs @@ -1,10 +1,7 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Logging; using System; -using System.Collections.Generic; -using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -18,19 +15,13 @@ namespace MediaBrowser.Server.Implementations.Library.Validators private readonly ILibraryManager _libraryManager; /// <summary> - /// The _user manager - /// </summary> - private readonly IUserManager _userManager; - - /// <summary> /// The _logger /// </summary> private readonly ILogger _logger; - public PeoplePostScanTask(ILibraryManager libraryManager, IUserManager userManager, ILogger logger) + public PeoplePostScanTask(ILibraryManager libraryManager, ILogger logger) { _libraryManager = libraryManager; - _userManager = userManager; _logger = logger; } @@ -42,94 +33,12 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// <returns>Task.</returns> public Task Run(IProgress<double> progress, CancellationToken cancellationToken) { - return RunInternal(progress, cancellationToken); - } - - private async Task RunInternal(IProgress<double> progress, CancellationToken cancellationToken) - { - var userLibraries = _userManager.Users - .Select(i => new Tuple<Guid, IList<BaseItem>>(i.Id, i.RootFolder.GetRecursiveChildren(i, null))) - .ToList(); - - var masterDictionary = new Dictionary<string, Dictionary<Guid, Dictionary<CountType, int>>>(StringComparer.OrdinalIgnoreCase); - - progress.Report(2); - - var numComplete = 0; - - foreach (var lib in userLibraries) + return new PeopleValidator(_libraryManager, _logger).ValidatePeople(cancellationToken, new MetadataRefreshOptions { - cancellationToken.ThrowIfCancellationRequested(); - - SetItemCounts(lib.Item1, lib.Item2, masterDictionary); - - numComplete++; - double percent = numComplete; - percent /= userLibraries.Count; - percent *= 8; + ImageRefreshMode = ImageRefreshMode.ValidationOnly, + MetadataRefreshMode = MetadataRefreshMode.None - progress.Report(percent); - } - - progress.Report(10); - - var count = masterDictionary.Count; - numComplete = 0; - - foreach (var name in masterDictionary.Keys) - { - cancellationToken.ThrowIfCancellationRequested(); - - try - { - var counts = masterDictionary[name]; - - var itemByName = _libraryManager.GetPerson(name); - - // The only purpose here is to be able to react to image changes without running the people task. - // All other metadata can wait for that. - await itemByName.RefreshMetadata(new MetadataRefreshOptions - { - ImageRefreshMode = ImageRefreshMode.ValidationOnly, - MetadataRefreshMode = MetadataRefreshMode.None - - }, cancellationToken).ConfigureAwait(false); - - foreach (var libraryId in counts.Keys) - { - var itemCounts = CountHelpers.GetCounts(counts[libraryId]); - - itemByName.SetItemByNameCounts(libraryId, itemCounts); - } - } - catch (Exception ex) - { - _logger.ErrorException("Error updating counts for {0}", ex, name); - } - - numComplete++; - double percent = numComplete; - percent /= count; - percent *= 90; - - progress.Report(percent + 10); - } - - progress.Report(100); + }, progress); } - - private void SetItemCounts(Guid userId, IEnumerable<BaseItem> allItems, Dictionary<string, Dictionary<Guid, Dictionary<CountType, int>>> masterDictionary) - { - foreach (var media in allItems) - { - var names = media - .People.Select(i => i.Name) - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); - - CountHelpers.SetItemCounts(userId, media, names, masterDictionary); - } - } - } } diff --git a/MediaBrowser.Server.Implementations/Library/Validators/PeopleValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/PeopleValidator.cs index 268bccd7a..722c24a10 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/PeopleValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/PeopleValidator.cs @@ -1,5 +1,6 @@ using MediaBrowser.Common.Progress; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Providers; using MediaBrowser.Model.Logging; using MoreLinq; using System; @@ -38,9 +39,10 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// Validates the people. /// </summary> /// <param name="cancellationToken">The cancellation token.</param> + /// <param name="options">The options.</param> /// <param name="progress">The progress.</param> /// <returns>Task.</returns> - public async Task ValidatePeople(CancellationToken cancellationToken, IProgress<double> progress) + public async Task ValidatePeople(CancellationToken cancellationToken, MetadataRefreshOptions options, IProgress<double> progress) { var innerProgress = new ActionableProgress<double>(); @@ -61,7 +63,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators { var item = _libraryManager.GetPerson(person.Name); - await item.RefreshMetadata(cancellationToken).ConfigureAwait(false); + await item.RefreshMetadata(options, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { diff --git a/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs b/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs index 54fadfb77..a2ec9788c 100644 --- a/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs +++ b/MediaBrowser.Server.Implementations/Library/Validators/StudiosValidator.cs @@ -1,8 +1,6 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Library; using MediaBrowser.Model.Logging; using System; -using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -41,38 +39,24 @@ namespace MediaBrowser.Server.Implementations.Library.Validators /// <returns>Task.</returns> public async Task Run(IProgress<double> progress, CancellationToken cancellationToken) { - var userLibraries = _userManager.Users - .Select(i => new Tuple<Guid, IList<BaseItem>>(i.Id, i.RootFolder.GetRecursiveChildren(i, null))) + var items = _libraryManager.RootFolder.RecursiveChildren + .SelectMany(i => i.Studios) + .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); - var masterDictionary = new Dictionary<string, Dictionary<Guid, Dictionary<CountType, int>>>(StringComparer.OrdinalIgnoreCase); - progress.Report(2); - var numComplete = 0; + var count = items.Count; - foreach (var lib in userLibraries) + foreach (var name in items) { - SetItemCounts(lib.Item1, lib.Item2, masterDictionary); - - numComplete++; - double percent = numComplete; - percent /= userLibraries.Count; - percent *= 8; - - progress.Report(percent); - } + cancellationToken.ThrowIfCancellationRequested(); - progress.Report(10); - - var count = masterDictionary.Count; - numComplete = 0; - - foreach (var name in masterDictionary.Keys) - { try { - await UpdateItemByNameCounts(name, cancellationToken, masterDictionary[name]).ConfigureAwait(false); + var itemByName = _libraryManager.GetStudio(name); + + await itemByName.RefreshMetadata(cancellationToken).ConfigureAwait(false); } catch (OperationCanceledException) { @@ -81,7 +65,7 @@ namespace MediaBrowser.Server.Implementations.Library.Validators } catch (Exception ex) { - _logger.ErrorException("Error updating counts for {0}", ex, name); + _logger.ErrorException("Error refreshing {0}", ex, name); } numComplete++; @@ -94,32 +78,5 @@ namespace MediaBrowser.Server.Implementations.Library.Validators progress.Report(100); } - - private Task UpdateItemByNameCounts(string name, CancellationToken cancellationToken, Dictionary<Guid, Dictionary<CountType, int>> counts) - { - var itemByName = _libraryManager.GetStudio(name); - - foreach (var libraryId in counts.Keys) - { - var itemCounts = CountHelpers.GetCounts(counts[libraryId]); - - itemByName.SetItemByNameCounts(libraryId, itemCounts); - } - - return itemByName.RefreshMetadata(cancellationToken); - } - - private void SetItemCounts(Guid userId, IEnumerable<BaseItem> allItems, Dictionary<string, Dictionary<Guid, Dictionary<CountType, int>>> masterDictionary) - { - foreach (var media in allItems) - { - var names = media - .Studios - .Distinct(StringComparer.OrdinalIgnoreCase) - .ToList(); - - CountHelpers.SetItemCounts(userId, media, names, masterDictionary); - } - } } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs index 9a1373460..83d8e4b73 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/ChannelImageProvider.cs @@ -105,7 +105,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date) { - return !item.HasImage(ImageType.Primary) && (DateTime.UtcNow - date).TotalDays >= 1; + var liveTvItem = item as LiveTvChannel; + + if (liveTvItem != null) + { + return !liveTvItem.HasImage(ImageType.Primary) && (liveTvItem.HasProviderImage ?? true) && (DateTime.UtcNow - date).TotalHours >= 6; + } + return false; } } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index bd315530e..e4446895f 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -47,6 +47,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv private List<Guid> _channelIdList = new List<Guid>(); private Dictionary<Guid, LiveTvProgram> _programs = new Dictionary<Guid, LiveTvProgram>(); + private readonly ConcurrentDictionary<Guid, bool> _refreshedPrograms = new ConcurrentDictionary<Guid, bool>(); private readonly SemaphoreSlim _refreshSemaphore = new SemaphoreSlim(1, 1); @@ -106,7 +107,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv _taskManager.CancelIfRunningAndQueue<RefreshChannelsScheduledTask>(); } - public Task<QueryResult<ChannelInfoDto>> GetChannels(ChannelQuery query, CancellationToken cancellationToken) + public async Task<QueryResult<ChannelInfoDto>> GetChannels(ChannelQuery query, CancellationToken cancellationToken) { var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(new Guid(query.UserId)); @@ -161,17 +162,22 @@ namespace MediaBrowser.Server.Implementations.LiveTv allEnumerable = allEnumerable.Take(query.Limit.Value); } - var returnChannels = allEnumerable - .Select(i => _tvDtoService.GetChannelInfoDto(i, GetCurrentProgram(i.ExternalId), user)) - .ToArray(); + var returnList = new List<ChannelInfoDto>(); + + foreach (var channel in allEnumerable) + { + var currentProgram = await GetCurrentProgram(channel.ExternalId, cancellationToken).ConfigureAwait(false); + + returnList.Add(_tvDtoService.GetChannelInfoDto(channel, currentProgram, user)); + } var result = new QueryResult<ChannelInfoDto> { - Items = returnChannels, + Items = returnList.ToArray(), TotalRecordCount = allChannels.Count }; - return Task.FromResult(result); + return result; } public LiveTvChannel GetInternalChannel(string id) @@ -184,16 +190,41 @@ namespace MediaBrowser.Server.Implementations.LiveTv return _libraryManager.GetItemById(id) as LiveTvChannel; } - public LiveTvProgram GetInternalProgram(string id) + public async Task<LiveTvProgram> GetInternalProgram(string id, CancellationToken cancellationToken) { var guid = new Guid(id); LiveTvProgram obj = null; _programs.TryGetValue(guid, out obj); + + if (obj != null) + { + await RefreshIfNeeded(obj, cancellationToken).ConfigureAwait(false); + } return obj; } + private async Task RefreshIfNeeded(IEnumerable<LiveTvProgram> programs, CancellationToken cancellationToken) + { + foreach (var program in programs) + { + await RefreshIfNeeded(program, cancellationToken).ConfigureAwait(false); + } + } + + private async Task RefreshIfNeeded(LiveTvProgram program, CancellationToken cancellationToken) + { + if (_refreshedPrograms.ContainsKey(program.Id)) + { + return; + } + + await program.RefreshMetadata(CancellationToken.None).ConfigureAwait(false); + + _refreshedPrograms.TryAdd(program.Id, true); + } + public async Task<ILiveTvRecording> GetInternalRecording(string id, CancellationToken cancellationToken) { var service = ActiveService; @@ -336,10 +367,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv return item; } - private async Task<LiveTvProgram> GetProgram(ProgramInfo info, ChannelType channelType, string serviceName, CancellationToken cancellationToken) + private LiveTvProgram GetProgram(ProgramInfo info, ChannelType channelType, string serviceName, CancellationToken cancellationToken) { - var isNew = false; - var id = _tvDtoService.GetInternalProgramId(serviceName, info.Id); var item = _itemRepo.RetrieveItem(id) as LiveTvProgram; @@ -353,8 +382,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv DateCreated = DateTime.UtcNow, DateModified = DateTime.UtcNow }; - - isNew = true; } item.ChannelType = channelType; @@ -386,12 +413,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv item.RunTimeTicks = (info.EndDate - info.StartDate).Ticks; item.StartDate = info.StartDate; - await item.RefreshMetadata(new MetadataRefreshOptions - { - ForceSave = isNew - - }, cancellationToken); - return item; } @@ -464,7 +485,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv public async Task<ProgramInfoDto> GetProgram(string id, CancellationToken cancellationToken, User user = null) { - var program = GetInternalProgram(id); + var program = await GetInternalProgram(id, cancellationToken).ConfigureAwait(false); var channel = GetChannel(program); @@ -531,7 +552,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv programs = programs.Where(i => i.IsParentalAllowed(currentUser)); } - var returnArray = programs + var programList = programs.ToList(); + + var returnArray = programList .Select(i => { var channel = GetChannel(i); @@ -540,6 +563,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv }) .ToArray(); + await RefreshIfNeeded(programList, cancellationToken).ConfigureAwait(false); + await AddRecordingInfo(returnArray, cancellationToken).ConfigureAwait(false); var result = new QueryResult<ProgramInfoDto> @@ -582,7 +607,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv .Select(i => _libraryManager.GetGenre(i)) .ToDictionary(i => i.Name, StringComparer.OrdinalIgnoreCase); - programs = programList.OrderByDescending(i => GetRecommendationScore(i, user.Id, serviceName, genres)) + programs = programList.OrderBy(i => i.HasImage(ImageType.Primary) ? 0 : 1) + .ThenByDescending(i => GetRecommendationScore(i, user.Id, serviceName, genres)) .ThenBy(i => i.StartDate); if (query.Limit.HasValue) @@ -591,7 +617,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv .OrderBy(i => i.StartDate); } - var returnArray = programs + programList = programs.ToList(); + + await RefreshIfNeeded(programList, cancellationToken).ConfigureAwait(false); + + var returnArray = programList .Select(i => { var channel = GetChannel(i); @@ -790,8 +820,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv var channelPrograms = await service.GetProgramsAsync(currentChannel.ExternalId, start, end, cancellationToken).ConfigureAwait(false); - var programTasks = channelPrograms.Select(program => GetProgram(program, currentChannel.ChannelType, service.Name, cancellationToken)); - var programEntities = await Task.WhenAll(programTasks).ConfigureAwait(false); + var programEntities = channelPrograms.Select(program => GetProgram(program, currentChannel.ChannelType, service.Name, cancellationToken)); programs.AddRange(programEntities); } @@ -812,6 +841,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv } _programs = programs.ToDictionary(i => i.Id); + _refreshedPrograms.Clear(); progress.Report(90); // Load these now which will prefetch metadata @@ -955,6 +985,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv entities = entities.Where(i => i.IsParentalAllowed(currentUser)); } + var entityList = entities.ToList(); + entities = entityList; + if (query.StartIndex.HasValue) { entities = entities.Skip(query.StartIndex.Value); @@ -976,7 +1009,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv return new QueryResult<RecordingInfoDto> { Items = returnArray, - TotalRecordCount = returnArray.Length + TotalRecordCount = entityList.Count }; } @@ -1030,14 +1063,20 @@ namespace MediaBrowser.Server.Implementations.LiveTv .Where(i => _tvDtoService.GetInternalSeriesTimerId(currentServiceName, i.SeriesTimerId) == guid); } - var returnArray = timers - .Select(i => - { - var program = string.IsNullOrEmpty(i.ProgramId) ? null : GetInternalProgram(_tvDtoService.GetInternalProgramId(service.Name, i.ProgramId).ToString("N")); - var channel = string.IsNullOrEmpty(i.ChannelId) ? null : GetInternalChannel(_tvDtoService.GetInternalChannelId(service.Name, i.ChannelId)); + var returnList = new List<TimerInfoDto>(); - return _tvDtoService.GetTimerInfoDto(i, service, program, channel); - }) + foreach (var i in timers) + { + var program = string.IsNullOrEmpty(i.ProgramId) ? + null : + await GetInternalProgram(_tvDtoService.GetInternalProgramId(service.Name, i.ProgramId).ToString("N"), cancellationToken).ConfigureAwait(false); + + var channel = string.IsNullOrEmpty(i.ChannelId) ? null : GetInternalChannel(_tvDtoService.GetInternalChannelId(service.Name, i.ChannelId)); + + returnList.Add(_tvDtoService.GetTimerInfoDto(i, service, program, channel)); + } + + var returnArray = returnList .OrderBy(i => i.StartDate) .ToArray(); @@ -1162,24 +1201,33 @@ namespace MediaBrowser.Server.Implementations.LiveTv }; } - public Task<ChannelInfoDto> GetChannel(string id, CancellationToken cancellationToken, User user = null) + public async Task<ChannelInfoDto> GetChannel(string id, CancellationToken cancellationToken, User user = null) { var channel = GetInternalChannel(id); - var dto = _tvDtoService.GetChannelInfoDto(channel, GetCurrentProgram(channel.ExternalId), user); + var currentProgram = await GetCurrentProgram(channel.ExternalId, cancellationToken).ConfigureAwait(false); - return Task.FromResult(dto); + var dto = _tvDtoService.GetChannelInfoDto(channel, currentProgram, user); + + return dto; } - private LiveTvProgram GetCurrentProgram(string externalChannelId) + private async Task<LiveTvProgram> GetCurrentProgram(string externalChannelId, CancellationToken cancellationToken) { var now = DateTime.UtcNow; - return _programs.Values + var program = _programs.Values .Where(i => string.Equals(externalChannelId, i.ExternalChannelId, StringComparison.OrdinalIgnoreCase)) .OrderBy(i => i.StartDate) .SkipWhile(i => now >= (i.EndDate ?? DateTime.MinValue)) .FirstOrDefault(); + + if (program != null) + { + await RefreshIfNeeded(program, cancellationToken).ConfigureAwait(false); + } + + return program; } private async Task<SeriesTimerInfo> GetNewTimerDefaultsInternal(CancellationToken cancellationToken, LiveTvProgram program = null) @@ -1235,7 +1283,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv public async Task<SeriesTimerInfoDto> GetNewTimerDefaults(string programId, CancellationToken cancellationToken) { - var program = GetInternalProgram(programId); + var program = await GetInternalProgram(programId, cancellationToken).ConfigureAwait(false); var programDto = await GetProgram(programId, cancellationToken).ConfigureAwait(false); var defaults = await GetNewTimerDefaultsInternal(cancellationToken, program).ConfigureAwait(false); diff --git a/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs index cb7635b45..081722bb2 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/ProgramImageProvider.cs @@ -105,7 +105,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date) { - return !item.HasImage(ImageType.Primary) && (DateTime.UtcNow - date).TotalHours >= 6; + var liveTvItem = item as LiveTvProgram; + + if (liveTvItem != null) + { + return !liveTvItem.HasImage(ImageType.Primary) && (liveTvItem.HasProviderImage ?? true) && (DateTime.UtcNow - date).TotalHours >= 6; + } + return false; } } } diff --git a/MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs b/MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs index be8955d16..7aa5dcebd 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/RecordingImageProvider.cs @@ -105,7 +105,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv public bool HasChanged(IHasMetadata item, IDirectoryService directoryService, DateTime date) { - return !item.HasImage(ImageType.Primary) && (DateTime.UtcNow - date).TotalHours >= 3; + var liveTvItem = item as ILiveTvRecording; + + if (liveTvItem != null) + { + return !liveTvItem.HasImage(ImageType.Primary) && (liveTvItem.RecordingInfo.HasImage ?? true) && (DateTime.UtcNow - date).TotalHours >= 6; + } + return false; } } } diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index f0b08b923..c44b60845 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -56,20 +56,18 @@ <SpecificVersion>False</SpecificVersion> <HintPath>..\packages\MediaBrowser.BdInfo.1.0.0.10\lib\net35\DvdLib.dll</HintPath> </Reference> - <Reference Include="Mono.Nat"> - <HintPath>..\packages\Mono.Nat.1.1.13\lib\Net40\Mono.Nat.dll</HintPath> + <Reference Include="Mono.Nat, Version=1.2.3.0, Culture=neutral, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\packages\Mono.Nat.1.2.3\lib\Net40\Mono.Nat.dll</HintPath> </Reference> <Reference Include="ServiceStack.Api.Swagger"> <HintPath>..\ThirdParty\ServiceStack\ServiceStack.Api.Swagger.dll</HintPath> </Reference> <Reference Include="System" /> <Reference Include="System.Core" /> - <Reference Include="System.Data.SQLite, Version=1.0.90.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=x86" Condition=" '$(ConfigurationName)' != 'Release Mono' "> + <Reference Include="System.Data.SQLite, Version=1.0.91.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> - <HintPath>..\packages\System.Data.SQLite.x86.1.0.90.0\lib\net45\System.Data.SQLite.dll</HintPath> - </Reference> - <Reference Include="System.Data.SQLite.Linq" Condition=" '$(ConfigurationName)' != 'Release Mono' "> - <HintPath>..\packages\System.Data.SQLite.x86.1.0.90.0\lib\net45\System.Data.SQLite.Linq.dll</HintPath> + <HintPath>..\packages\System.Data.SQLite.Core.1.0.91.3\lib\net45\System.Data.SQLite.dll</HintPath> </Reference> <Reference Include="System.Drawing" /> <Reference Include="Microsoft.CSharp" /> @@ -108,6 +106,8 @@ <Link>Properties\SharedVersion.cs</Link> </Compile> <Compile Include="BdInfo\BdInfoExaminer.cs" /> + <Compile Include="Collections\CollectionManager.cs" /> + <Compile Include="Collections\CollectionsDynamicFolder.cs" /> <Compile Include="Configuration\ServerConfigurationManager.cs" /> <Compile Include="Drawing\ImageHeader.cs" /> <Compile Include="Drawing\PercentPlayedDrawer.cs" /> @@ -216,7 +216,6 @@ <Compile Include="Sorting\AirTimeComparer.cs" /> <Compile Include="Sorting\AlbumArtistComparer.cs" /> <Compile Include="Sorting\AlbumComparer.cs" /> - <Compile Include="Sorting\AlbumCountComparer.cs" /> <Compile Include="Sorting\AlphanumComparator.cs" /> <Compile Include="Sorting\ArtistComparer.cs" /> <Compile Include="Sorting\BudgetComparer.cs" /> @@ -224,29 +223,26 @@ <Compile Include="Sorting\CriticRatingComparer.cs" /> <Compile Include="Sorting\DateCreatedComparer.cs" /> <Compile Include="Sorting\DatePlayedComparer.cs" /> - <Compile Include="Sorting\EpisodeCountComparer.cs" /> + <Compile Include="Sorting\GameSystemComparer.cs" /> <Compile Include="Sorting\IsFolderComparer.cs" /> <Compile Include="Sorting\IsUnplayedComparer.cs" /> <Compile Include="Sorting\MetascoreComparer.cs" /> - <Compile Include="Sorting\MovieCountComparer.cs" /> - <Compile Include="Sorting\MusicVideoCountComparer.cs" /> <Compile Include="Sorting\NameComparer.cs" /> <Compile Include="Sorting\OfficialRatingComparer.cs" /> <Compile Include="Sorting\PlayCountComparer.cs" /> + <Compile Include="Sorting\PlayersComparer.cs" /> <Compile Include="Sorting\PremiereDateComparer.cs" /> <Compile Include="Sorting\ProductionYearComparer.cs" /> <Compile Include="Sorting\RandomComparer.cs" /> <Compile Include="Sorting\RevenueComparer.cs" /> <Compile Include="Sorting\RuntimeComparer.cs" /> - <Compile Include="Sorting\SeriesCountComparer.cs" /> <Compile Include="Sorting\SeriesSortNameComparer.cs" /> - <Compile Include="Sorting\SongCountComparer.cs" /> <Compile Include="Sorting\SortNameComparer.cs" /> <Compile Include="Persistence\SqliteDisplayPreferencesRepository.cs" /> <Compile Include="Persistence\SqliteItemRepository.cs" /> <Compile Include="Persistence\SqliteUserDataRepository.cs" /> <Compile Include="Persistence\SqliteUserRepository.cs" /> - <Compile Include="Sorting\TrailerCountComparer.cs" /> + <Compile Include="Sorting\StudioComparer.cs" /> <Compile Include="Sorting\VideoBitRateComparer.cs" /> <Compile Include="Themes\AppThemeManager.cs" /> <Compile Include="Udp\UdpMessageReceivedEventArgs.cs" /> @@ -376,6 +372,12 @@ <Link>swagger-ui\swagger-ui.min.js</Link> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> + <Content Include="x64\SQLite.Interop.dll" Condition=" '$(ConfigurationName)' != 'Release Mono' "> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </Content> + <Content Include="x86\SQLite.Interop.dll" Condition=" '$(ConfigurationName)' != 'Release Mono' "> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </Content> <EmbeddedResource Include="Localization\Ratings\be.txt" /> </ItemGroup> <ItemGroup> diff --git a/MediaBrowser.Server.Implementations/Session/SessionManager.cs b/MediaBrowser.Server.Implementations/Session/SessionManager.cs index bfa23b997..71d95b97b 100644 --- a/MediaBrowser.Server.Implementations/Session/SessionManager.cs +++ b/MediaBrowser.Server.Implementations/Session/SessionManager.cs @@ -69,6 +69,8 @@ namespace MediaBrowser.Server.Implementations.Session private IEnumerable<ISessionControllerFactory> _sessionFactories = new List<ISessionControllerFactory>(); + private readonly SemaphoreSlim _sessionLock = new SemaphoreSlim(1, 1); + /// <summary> /// Initializes a new instance of the <see cref="SessionManager" /> class. /// </summary> @@ -146,7 +148,7 @@ namespace MediaBrowser.Server.Implementations.Session var userId = user == null ? (Guid?)null : user.Id; var username = user == null ? null : user.Name; - var session = GetSessionInfo(clientType, appVersion, deviceId, deviceName, remoteEndPoint, userId, username); + var session = await GetSessionInfo(clientType, appVersion, deviceId, deviceName, remoteEndPoint, userId, username).ConfigureAwait(false); session.LastActivityDate = activityDate; @@ -171,6 +173,46 @@ namespace MediaBrowser.Server.Implementations.Session return session; } + public async Task ReportSessionEnded(Guid sessionId) + { + await _sessionLock.WaitAsync(CancellationToken.None).ConfigureAwait(false); + + try + { + var session = GetSession(sessionId); + + if (session == null) + { + throw new ArgumentException("Session not found"); + } + + var key = GetSessionKey(session.Client, session.ApplicationVersion, session.DeviceId); + + SessionInfo removed; + + if (_activeConnections.TryRemove(key, out removed)) + { + var disposable = removed.SessionController as IDisposable; + + if (disposable != null) + { + try + { + disposable.Dispose(); + } + catch (Exception ex) + { + _logger.ErrorException("Error disposing session controller", ex); + } + } + } + } + finally + { + _sessionLock.Release(); + } + } + /// <summary> /// Updates the now playing item id. /// </summary> @@ -207,6 +249,11 @@ namespace MediaBrowser.Server.Implementations.Session } } + private string GetSessionKey(string clientType, string appVersion, string deviceId) + { + return clientType + deviceId + appVersion; + } + /// <summary> /// Gets the connection. /// </summary> @@ -218,36 +265,45 @@ namespace MediaBrowser.Server.Implementations.Session /// <param name="userId">The user identifier.</param> /// <param name="username">The username.</param> /// <returns>SessionInfo.</returns> - private SessionInfo GetSessionInfo(string clientType, string appVersion, string deviceId, string deviceName, string remoteEndPoint, Guid? userId, string username) + private async Task<SessionInfo> GetSessionInfo(string clientType, string appVersion, string deviceId, string deviceName, string remoteEndPoint, Guid? userId, string username) { - var key = clientType + deviceId + appVersion; + var key = GetSessionKey(clientType, appVersion, deviceId); - var connection = _activeConnections.GetOrAdd(key, keyName => new SessionInfo + await _sessionLock.WaitAsync(CancellationToken.None).ConfigureAwait(false); + + try { - Client = clientType, - DeviceId = deviceId, - ApplicationVersion = appVersion, - Id = Guid.NewGuid() - }); + var connection = _activeConnections.GetOrAdd(key, keyName => new SessionInfo + { + Client = clientType, + DeviceId = deviceId, + ApplicationVersion = appVersion, + Id = Guid.NewGuid() + }); - connection.DeviceName = deviceName; - connection.UserId = userId; - connection.UserName = username; - connection.RemoteEndPoint = remoteEndPoint; + connection.DeviceName = deviceName; + connection.UserId = userId; + connection.UserName = username; + connection.RemoteEndPoint = remoteEndPoint; - if (!userId.HasValue) - { - connection.AdditionalUsers.Clear(); - } + if (!userId.HasValue) + { + connection.AdditionalUsers.Clear(); + } + + if (connection.SessionController == null) + { + connection.SessionController = _sessionFactories + .Select(i => i.GetSessionController(connection)) + .FirstOrDefault(i => i != null); + } - if (connection.SessionController == null) + return connection; + } + finally { - connection.SessionController = _sessionFactories - .Select(i => i.GetSessionController(connection)) - .FirstOrDefault(i => i != null); + _sessionLock.Release(); } - - return connection; } private List<User> GetUsers(SessionInfo session) diff --git a/MediaBrowser.Server.Implementations/Sorting/AlbumCountComparer.cs b/MediaBrowser.Server.Implementations/Sorting/AlbumCountComparer.cs deleted file mode 100644 index e35ba00f2..000000000 --- a/MediaBrowser.Server.Implementations/Sorting/AlbumCountComparer.cs +++ /dev/null @@ -1,71 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Controller.Sorting; -using MediaBrowser.Model.Querying; - -namespace MediaBrowser.Server.Implementations.Sorting -{ - class AlbumCountComparer : IUserBaseItemComparer - { - /// <summary> - /// Gets or sets the user. - /// </summary> - /// <value>The user.</value> - public User User { get; set; } - - /// <summary> - /// Gets or sets the user manager. - /// </summary> - /// <value>The user manager.</value> - public IUserManager UserManager { get; set; } - - /// <summary> - /// Gets or sets the user data repository. - /// </summary> - /// <value>The user data repository.</value> - public IUserDataManager UserDataRepository { get; set; } - - /// <summary> - /// Compares the specified x. - /// </summary> - /// <param name="x">The x.</param> - /// <param name="y">The y.</param> - /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) - { - return GetValue(x).CompareTo(GetValue(y)); - } - - /// <summary> - /// Gets the date. - /// </summary> - /// <param name="x">The x.</param> - /// <returns>DateTime.</returns> - private int GetValue(BaseItem x) - { - var itemByName = x as IItemByName; - - if (itemByName != null) - { - var counts = itemByName.GetItemByNameCounts(User.Id); - - if (counts != null) - { - return counts.AlbumCount; - } - } - - return 0; - } - - /// <summary> - /// Gets the name. - /// </summary> - /// <value>The name.</value> - public string Name - { - get { return ItemSortBy.AlbumCount; } - } - } -} diff --git a/MediaBrowser.Server.Implementations/Sorting/EpisodeCountComparer.cs b/MediaBrowser.Server.Implementations/Sorting/EpisodeCountComparer.cs deleted file mode 100644 index b3fd8a023..000000000 --- a/MediaBrowser.Server.Implementations/Sorting/EpisodeCountComparer.cs +++ /dev/null @@ -1,71 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Controller.Sorting; -using MediaBrowser.Model.Querying; - -namespace MediaBrowser.Server.Implementations.Sorting -{ - class EpisodeCountComparer : IUserBaseItemComparer - { - /// <summary> - /// Gets or sets the user. - /// </summary> - /// <value>The user.</value> - public User User { get; set; } - - /// <summary> - /// Gets or sets the user manager. - /// </summary> - /// <value>The user manager.</value> - public IUserManager UserManager { get; set; } - - /// <summary> - /// Gets or sets the user data repository. - /// </summary> - /// <value>The user data repository.</value> - public IUserDataManager UserDataRepository { get; set; } - - /// <summary> - /// Compares the specified x. - /// </summary> - /// <param name="x">The x.</param> - /// <param name="y">The y.</param> - /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) - { - return GetValue(x).CompareTo(GetValue(y)); - } - - /// <summary> - /// Gets the date. - /// </summary> - /// <param name="x">The x.</param> - /// <returns>DateTime.</returns> - private int GetValue(BaseItem x) - { - var itemByName = x as IItemByName; - - if (itemByName != null) - { - var counts = itemByName.GetItemByNameCounts(User.Id); - - if (counts != null) - { - return counts.EpisodeCount; - } - } - - return 0; - } - - /// <summary> - /// Gets the name. - /// </summary> - /// <value>The name.</value> - public string Name - { - get { return ItemSortBy.EpisodeCount; } - } - } -} diff --git a/MediaBrowser.Server.Implementations/Sorting/GameSystemComparer.cs b/MediaBrowser.Server.Implementations/Sorting/GameSystemComparer.cs new file mode 100644 index 000000000..eb83b98e9 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sorting/GameSystemComparer.cs @@ -0,0 +1,54 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; +using System; + +namespace MediaBrowser.Server.Implementations.Sorting +{ + public class GameSystemComparer : IBaseItemComparer + { + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem x, BaseItem y) + { + return string.Compare(GetValue(x), GetValue(y), StringComparison.CurrentCultureIgnoreCase); + } + + /// <summary> + /// Gets the value. + /// </summary> + /// <param name="x">The x.</param> + /// <returns>System.String.</returns> + private string GetValue(BaseItem x) + { + var game = x as Game; + + if (game != null) + { + return game.GameSystem; + } + + var system = x as GameSystem; + + if (system != null) + { + return system.GameSystemName; + } + + return string.Empty; + } + + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public string Name + { + get { return ItemSortBy.GameSystem; } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Sorting/MovieCountComparer.cs b/MediaBrowser.Server.Implementations/Sorting/MovieCountComparer.cs deleted file mode 100644 index 605f4d1af..000000000 --- a/MediaBrowser.Server.Implementations/Sorting/MovieCountComparer.cs +++ /dev/null @@ -1,71 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Controller.Sorting; -using MediaBrowser.Model.Querying; - -namespace MediaBrowser.Server.Implementations.Sorting -{ - public class MovieCountComparer : IUserBaseItemComparer - { - /// <summary> - /// Gets or sets the user. - /// </summary> - /// <value>The user.</value> - public User User { get; set; } - - /// <summary> - /// Gets or sets the user manager. - /// </summary> - /// <value>The user manager.</value> - public IUserManager UserManager { get; set; } - - /// <summary> - /// Gets or sets the user data repository. - /// </summary> - /// <value>The user data repository.</value> - public IUserDataManager UserDataRepository { get; set; } - - /// <summary> - /// Compares the specified x. - /// </summary> - /// <param name="x">The x.</param> - /// <param name="y">The y.</param> - /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) - { - return GetValue(x).CompareTo(GetValue(y)); - } - - /// <summary> - /// Gets the date. - /// </summary> - /// <param name="x">The x.</param> - /// <returns>DateTime.</returns> - private int GetValue(BaseItem x) - { - var itemByName = x as IItemByName; - - if (itemByName != null) - { - var counts = itemByName.GetItemByNameCounts(User.Id); - - if (counts != null) - { - return counts.MovieCount; - } - } - - return 0; - } - - /// <summary> - /// Gets the name. - /// </summary> - /// <value>The name.</value> - public string Name - { - get { return ItemSortBy.MovieCount; } - } - } -} diff --git a/MediaBrowser.Server.Implementations/Sorting/MusicVideoCountComparer.cs b/MediaBrowser.Server.Implementations/Sorting/MusicVideoCountComparer.cs deleted file mode 100644 index 6c9c5534d..000000000 --- a/MediaBrowser.Server.Implementations/Sorting/MusicVideoCountComparer.cs +++ /dev/null @@ -1,71 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Controller.Sorting; -using MediaBrowser.Model.Querying; - -namespace MediaBrowser.Server.Implementations.Sorting -{ - class MusicVideoCountComparer : IUserBaseItemComparer - { - /// <summary> - /// Gets or sets the user. - /// </summary> - /// <value>The user.</value> - public User User { get; set; } - - /// <summary> - /// Gets or sets the user manager. - /// </summary> - /// <value>The user manager.</value> - public IUserManager UserManager { get; set; } - - /// <summary> - /// Gets or sets the user data repository. - /// </summary> - /// <value>The user data repository.</value> - public IUserDataManager UserDataRepository { get; set; } - - /// <summary> - /// Compares the specified x. - /// </summary> - /// <param name="x">The x.</param> - /// <param name="y">The y.</param> - /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) - { - return GetValue(x).CompareTo(GetValue(y)); - } - - /// <summary> - /// Gets the date. - /// </summary> - /// <param name="x">The x.</param> - /// <returns>DateTime.</returns> - private int GetValue(BaseItem x) - { - var itemByName = x as IItemByName; - - if (itemByName != null) - { - var counts = itemByName.GetItemByNameCounts(User.Id); - - if (counts != null) - { - return counts.MusicVideoCount; - } - } - - return 0; - } - - /// <summary> - /// Gets the name. - /// </summary> - /// <value>The name.</value> - public string Name - { - get { return ItemSortBy.MusicVideoCount; } - } - } -} diff --git a/MediaBrowser.Server.Implementations/Sorting/PlayersComparer.cs b/MediaBrowser.Server.Implementations/Sorting/PlayersComparer.cs new file mode 100644 index 000000000..5bcd080d7 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sorting/PlayersComparer.cs @@ -0,0 +1,46 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; + +namespace MediaBrowser.Server.Implementations.Sorting +{ + public class PlayersComparer : IBaseItemComparer + { + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem x, BaseItem y) + { + return GetValue(x).CompareTo(GetValue(y)); + } + + /// <summary> + /// Gets the value. + /// </summary> + /// <param name="x">The x.</param> + /// <returns>System.String.</returns> + private int GetValue(BaseItem x) + { + var game = x as Game; + + if (game != null) + { + return game.PlayersSupported ?? 0; + } + + return 0; + } + + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public string Name + { + get { return ItemSortBy.Players; } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Sorting/SeriesCountComparer.cs b/MediaBrowser.Server.Implementations/Sorting/SeriesCountComparer.cs deleted file mode 100644 index 8567e400c..000000000 --- a/MediaBrowser.Server.Implementations/Sorting/SeriesCountComparer.cs +++ /dev/null @@ -1,71 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Controller.Sorting; -using MediaBrowser.Model.Querying; - -namespace MediaBrowser.Server.Implementations.Sorting -{ - class SeriesCountComparer : IUserBaseItemComparer - { - /// <summary> - /// Gets or sets the user. - /// </summary> - /// <value>The user.</value> - public User User { get; set; } - - /// <summary> - /// Gets or sets the user manager. - /// </summary> - /// <value>The user manager.</value> - public IUserManager UserManager { get; set; } - - /// <summary> - /// Gets or sets the user data repository. - /// </summary> - /// <value>The user data repository.</value> - public IUserDataManager UserDataRepository { get; set; } - - /// <summary> - /// Compares the specified x. - /// </summary> - /// <param name="x">The x.</param> - /// <param name="y">The y.</param> - /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) - { - return GetValue(x).CompareTo(GetValue(y)); - } - - /// <summary> - /// Gets the date. - /// </summary> - /// <param name="x">The x.</param> - /// <returns>DateTime.</returns> - private int GetValue(BaseItem x) - { - var itemByName = x as IItemByName; - - if (itemByName != null) - { - var counts = itemByName.GetItemByNameCounts(User.Id); - - if (counts != null) - { - return counts.SeriesCount; - } - } - - return 0; - } - - /// <summary> - /// Gets the name. - /// </summary> - /// <value>The name.</value> - public string Name - { - get { return ItemSortBy.SeriesCount; } - } - } -} diff --git a/MediaBrowser.Server.Implementations/Sorting/SongCountComparer.cs b/MediaBrowser.Server.Implementations/Sorting/SongCountComparer.cs deleted file mode 100644 index 85b849a21..000000000 --- a/MediaBrowser.Server.Implementations/Sorting/SongCountComparer.cs +++ /dev/null @@ -1,71 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Controller.Sorting; -using MediaBrowser.Model.Querying; - -namespace MediaBrowser.Server.Implementations.Sorting -{ - class SongCountComparer : IUserBaseItemComparer - { - /// <summary> - /// Gets or sets the user. - /// </summary> - /// <value>The user.</value> - public User User { get; set; } - - /// <summary> - /// Gets or sets the user manager. - /// </summary> - /// <value>The user manager.</value> - public IUserManager UserManager { get; set; } - - /// <summary> - /// Gets or sets the user data repository. - /// </summary> - /// <value>The user data repository.</value> - public IUserDataManager UserDataRepository { get; set; } - - /// <summary> - /// Compares the specified x. - /// </summary> - /// <param name="x">The x.</param> - /// <param name="y">The y.</param> - /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) - { - return GetValue(x).CompareTo(GetValue(y)); - } - - /// <summary> - /// Gets the date. - /// </summary> - /// <param name="x">The x.</param> - /// <returns>DateTime.</returns> - private int GetValue(BaseItem x) - { - var itemByName = x as IItemByName; - - if (itemByName != null) - { - var counts = itemByName.GetItemByNameCounts(User.Id); - - if (counts != null) - { - return counts.SongCount; - } - } - - return 0; - } - - /// <summary> - /// Gets the name. - /// </summary> - /// <value>The name.</value> - public string Name - { - get { return ItemSortBy.SongCount; } - } - } -} diff --git a/MediaBrowser.Server.Implementations/Sorting/StudioComparer.cs b/MediaBrowser.Server.Implementations/Sorting/StudioComparer.cs new file mode 100644 index 000000000..83ab4dfc2 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Sorting/StudioComparer.cs @@ -0,0 +1,30 @@ +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Sorting; +using MediaBrowser.Model.Querying; +using System.Linq; + +namespace MediaBrowser.Server.Implementations.Sorting +{ + public class StudioComparer : IBaseItemComparer + { + /// <summary> + /// Compares the specified x. + /// </summary> + /// <param name="x">The x.</param> + /// <param name="y">The y.</param> + /// <returns>System.Int32.</returns> + public int Compare(BaseItem x, BaseItem y) + { + return AlphanumComparator.CompareValues(x.Studios.FirstOrDefault() ?? string.Empty, y.Studios.FirstOrDefault() ?? string.Empty); + } + + /// <summary> + /// Gets the name. + /// </summary> + /// <value>The name.</value> + public string Name + { + get { return ItemSortBy.Studio; } + } + } +} diff --git a/MediaBrowser.Server.Implementations/Sorting/TrailerCountComparer.cs b/MediaBrowser.Server.Implementations/Sorting/TrailerCountComparer.cs deleted file mode 100644 index a13875674..000000000 --- a/MediaBrowser.Server.Implementations/Sorting/TrailerCountComparer.cs +++ /dev/null @@ -1,71 +0,0 @@ -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Controller.Sorting; -using MediaBrowser.Model.Querying; - -namespace MediaBrowser.Server.Implementations.Sorting -{ - public class TrailerCountComparer : IUserBaseItemComparer - { - /// <summary> - /// Gets or sets the user. - /// </summary> - /// <value>The user.</value> - public User User { get; set; } - - /// <summary> - /// Gets or sets the user manager. - /// </summary> - /// <value>The user manager.</value> - public IUserManager UserManager { get; set; } - - /// <summary> - /// Gets or sets the user data repository. - /// </summary> - /// <value>The user data repository.</value> - public IUserDataManager UserDataRepository { get; set; } - - /// <summary> - /// Compares the specified x. - /// </summary> - /// <param name="x">The x.</param> - /// <param name="y">The y.</param> - /// <returns>System.Int32.</returns> - public int Compare(BaseItem x, BaseItem y) - { - return GetValue(x).CompareTo(GetValue(y)); - } - - /// <summary> - /// Gets the date. - /// </summary> - /// <param name="x">The x.</param> - /// <returns>DateTime.</returns> - private int GetValue(BaseItem x) - { - var itemByName = x as IItemByName; - - if (itemByName != null) - { - var counts = itemByName.GetItemByNameCounts(User.Id); - - if (counts != null) - { - return counts.TrailerCount; - } - } - - return 0; - } - - /// <summary> - /// Gets the name. - /// </summary> - /// <value>The name.</value> - public string Name - { - get { return ItemSortBy.TrailerCount; } - } - } -} diff --git a/MediaBrowser.Server.Implementations/packages.config b/MediaBrowser.Server.Implementations/packages.config index 258e0639d..f04536190 100644 --- a/MediaBrowser.Server.Implementations/packages.config +++ b/MediaBrowser.Server.Implementations/packages.config @@ -2,7 +2,7 @@ <packages>
<package id="Alchemy" version="2.2.1" targetFramework="net45" />
<package id="MediaBrowser.BdInfo" version="1.0.0.10" targetFramework="net45" />
- <package id="Mono.Nat" version="1.1.13" targetFramework="net45" />
+ <package id="Mono.Nat" version="1.2.3" targetFramework="net45" />
<package id="morelinq" version="1.0.16006" targetFramework="net45" />
- <package id="System.Data.SQLite.x86" version="1.0.90.0" targetFramework="net45" />
+ <package id="System.Data.SQLite.Core" version="1.0.91.3" targetFramework="net45" />
</packages>
\ No newline at end of file diff --git a/MediaBrowser.ServerApplication/App.config b/MediaBrowser.ServerApplication/App.config index 53788e09a..978f31851 100644 --- a/MediaBrowser.ServerApplication/App.config +++ b/MediaBrowser.ServerApplication/App.config @@ -2,6 +2,8 @@ <configuration> <configSections> <section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog" /> + + <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" /> </configSections> <system.diagnostics> <assert assertuienabled="false" /> @@ -43,7 +45,7 @@ </dependentAssembly> <dependentAssembly> <assemblyIdentity name="System.Data.SQLite" publicKeyToken="db937bc2d44ff139" culture="neutral" /> - <bindingRedirect oldVersion="0.0.0.0-1.0.89.0" newVersion="1.0.89.0" /> + <bindingRedirect oldVersion="0.0.0.0-1.0.91.0" newVersion="1.0.91.0" /> </dependentAssembly> <dependentAssembly> <assemblyIdentity name="SimpleInjector" publicKeyToken="984cb50dea722e99" culture="neutral" /> @@ -63,4 +65,16 @@ </providers> </roleManager> </system.web> + <entityFramework> + <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework"> + <parameters> + <parameter value="v11.0" /> + </parameters> + </defaultConnectionFactory> + <providers> + <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" /> + <provider invariantName="System.Data.SQLite.EF6" type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6" /> + </providers> + </entityFramework> + </configuration>
\ No newline at end of file diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 690d615ff..e9aebb6d2 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -9,6 +9,7 @@ using MediaBrowser.Common.IO; using MediaBrowser.Common.Net; using MediaBrowser.Common.Progress; using MediaBrowser.Controller; +using MediaBrowser.Controller.Collections; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Controller.Dto; @@ -36,6 +37,7 @@ using MediaBrowser.Model.Updates; using MediaBrowser.Providers.Manager; using MediaBrowser.Server.Implementations; using MediaBrowser.Server.Implementations.BdInfo; +using MediaBrowser.Server.Implementations.Collections; using MediaBrowser.Server.Implementations.Configuration; using MediaBrowser.Server.Implementations.Drawing; using MediaBrowser.Server.Implementations.Dto; @@ -488,6 +490,9 @@ namespace MediaBrowser.ServerApplication var appThemeManager = new AppThemeManager(ApplicationPaths, FileSystemManager, JsonSerializer, Logger); RegisterSingleInstance<IAppThemeManager>(appThemeManager); + var collectionManager = new CollectionManager(LibraryManager, FileSystemManager, LibraryMonitor); + RegisterSingleInstance<ICollectionManager>(collectionManager); + LiveTvManager = new LiveTvManager(ServerConfigurationManager, FileSystemManager, Logger, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager); RegisterSingleInstance(LiveTvManager); diff --git a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj index c3eb4fd6a..227d0dd1d 100644 --- a/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj +++ b/MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj @@ -235,6 +235,12 @@ xcopy "$(TargetDir)*.dll" "$(SolutionDir)..\Deploy\Server\System" /y mkdir "$(SolutionDir)..\Deploy\Server\System\dashboard-ui" xcopy "$(TargetDir)dashboard-ui" "$(SolutionDir)..\Deploy\Server\System\dashboard-ui" /y /s +mkdir "$(SolutionDir)..\Deploy\Server\System\x86" +xcopy "$(TargetDir)x86" "$(SolutionDir)..\Deploy\Server\System\x86" /y /s + +mkdir "$(SolutionDir)..\Deploy\Server\System\x64" +xcopy "$(TargetDir)x64" "$(SolutionDir)..\Deploy\Server\System\x64" /y /s + del "$(SolutionDir)..\Deploy\MBServer.zip" "$(SolutionDir)ThirdParty\7zip\7za" a -mx9 "$(SolutionDir)..\Deploy\MBServer.zip" "$(SolutionDir)..\Deploy\Server\*" )</PostBuildEvent> diff --git a/MediaBrowser.Tests/app.config b/MediaBrowser.Tests/app.config index cbc4501c5..3359125c3 100644 --- a/MediaBrowser.Tests/app.config +++ b/MediaBrowser.Tests/app.config @@ -4,7 +4,7 @@ <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <dependentAssembly> <assemblyIdentity name="System.Data.SQLite" publicKeyToken="db937bc2d44ff139" culture="neutral" /> - <bindingRedirect oldVersion="0.0.0.0-1.0.89.0" newVersion="1.0.89.0" /> + <bindingRedirect oldVersion="0.0.0.0-1.0.91.0" newVersion="1.0.91.0" /> </dependentAssembly> </assemblyBinding> </runtime> diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index b163c7748..e5127a07a 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -480,6 +480,8 @@ namespace MediaBrowser.WebDashboard.Api "dashboardinfo.js", "dashboardpage.js", "directorybrowser.js", + "dlnasettings.js", + "editcollectionitems.js", "edititemmetadata.js", "edititempeople.js", "edititemimages.js", @@ -524,6 +526,7 @@ namespace MediaBrowser.WebDashboard.Api "moviegenres.js", "moviecollections.js", "movies.js", + "movieslatest.js", "moviepeople.js", "moviesrecommended.js", "moviestudios.js", diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 2fc2407e0..0bd54ab8c 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -215,6 +215,12 @@ <Content Include="dashboard-ui\dashboardinfopage.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\dlnasettings.html">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\editcollectionitems.html">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\encodingsettings.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -257,13 +263,16 @@ <Content Include="dashboard-ui\livetvtimers.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\movieslatest.html">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\musicalbumartists.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\allusersettings.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
- <Content Include="dashboard-ui\boxsets.html">
+ <Content Include="dashboard-ui\collections.html">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\css\images\clients\mbkinect.png">
@@ -482,6 +491,12 @@ <Content Include="dashboard-ui\scripts\dashboardinfo.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
+ <Content Include="dashboard-ui\scripts\dlnasettings.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
+ <Content Include="dashboard-ui\scripts\editcollectionitems.js">
+ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+ </Content>
<Content Include="dashboard-ui\scripts\encodingsettings.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
@@ -537,6 +552,7 @@ <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\scripts\mediaplayer-video.js">
+ <Content Include="dashboard-ui\scripts\movieslatest.js">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="dashboard-ui\scripts\musicalbumartists.js">
diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index e570f18d8..3e548e625 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"> <metadata> <id>MediaBrowser.Common.Internal</id> - <version>3.0.335</version> + <version>3.0.339</version> <title>MediaBrowser.Common.Internal</title> <authors>Luke</authors> <owners>ebr,Luke,scottisafool</owners> @@ -12,7 +12,7 @@ <description>Contains common components shared by Media Browser Theater and Media Browser Server. Not intended for plugin developer consumption.</description> <copyright>Copyright © Media Browser 2013</copyright> <dependencies> - <dependency id="MediaBrowser.Common" version="3.0.335" /> + <dependency id="MediaBrowser.Common" version="3.0.339" /> <dependency id="NLog" version="2.1.0" /> <dependency id="SimpleInjector" version="2.4.1" /> <dependency id="sharpcompress" version="0.10.2" /> diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index 75b695584..5b81a046e 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"> <metadata> <id>MediaBrowser.Common</id> - <version>3.0.335</version> + <version>3.0.339</version> <title>MediaBrowser.Common</title> <authors>Media Browser Team</authors> <owners>ebr,Luke,scottisafool</owners> diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index 581a82106..bf8bfd157 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <metadata> <id>MediaBrowser.Server.Core</id> - <version>3.0.335</version> + <version>3.0.339</version> <title>Media Browser.Server.Core</title> <authors>Media Browser Team</authors> <owners>ebr,Luke,scottisafool</owners> @@ -12,7 +12,7 @@ <description>Contains core components required to build plugins for Media Browser Server.</description> <copyright>Copyright © Media Browser 2013</copyright> <dependencies> - <dependency id="MediaBrowser.Common" version="3.0.335" /> + <dependency id="MediaBrowser.Common" version="3.0.339" /> </dependencies> </metadata> <files> |
