From 2349c8099d04c6c0631cd33e6c74b404381946ab Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Thu, 6 Mar 2014 00:17:13 -0500 Subject: start on manual collection creation --- .../Collections/CollectionManager.cs | 79 ++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 MediaBrowser.Server.Implementations/Collections/CollectionManager.cs (limited to 'MediaBrowser.Server.Implementations/Collections/CollectionManager.cs') diff --git a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs new file mode 100644 index 000000000..da444d100 --- /dev/null +++ b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs @@ -0,0 +1,79 @@ +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.IO; +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; + + var folderName = _fileSystem.GetValidFilename(name); + + var parentFolder = _libraryManager.GetItemById(options.ParentId) as Folder; + + 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 + }; + + 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); + } + } + + public Task AddToCollection(Guid collectionId, Guid itemId) + { + throw new NotImplementedException(); + } + + public Task RemoveFromCollection(Guid collectionId, Guid itemId) + { + throw new NotImplementedException(); + } + } +} -- cgit v1.2.3 From e00985d07ca3923f7f558b8592c0d092842bff5d Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 7 Mar 2014 10:53:23 -0500 Subject: #715 - Support creating/editing collections (boxsets) in web client --- MediaBrowser.Api/MediaBrowser.Api.csproj | 5 +- MediaBrowser.Api/Movies/CollectionService.cs | 80 +++++++++++++++++++ MediaBrowser.Api/Movies/MoviesService.cs | 82 +++++++++++++++++++ MediaBrowser.Api/Movies/TrailersService.cs | 75 ++++++++++++++++++ MediaBrowser.Api/MoviesService.cs | 82 ------------------- MediaBrowser.Api/SearchService.cs | 6 +- MediaBrowser.Api/TrailersService.cs | 75 ------------------ .../Collections/CollectionCreationOptions.cs | 2 +- .../Collections/ICollectionManager.cs | 9 ++- MediaBrowser.Controller/Entities/BaseItem.cs | 9 +++ .../Entities/BasePluginFolder.cs | 15 +--- MediaBrowser.Controller/Entities/Folder.cs | 66 ++++++++++++---- MediaBrowser.Controller/Entities/LinkedChild.cs | 4 + MediaBrowser.Model/Search/SearchQuery.cs | 4 + MediaBrowser.Providers/Savers/XmlSaverHelpers.cs | 39 ++++++++- .../Collections/CollectionManager.cs | 92 ++++++++++++++++++++-- .../Collections/CollectionsDynamicFolder.cs | 55 +++++++++++++ .../Library/SearchEngine.cs | 6 ++ .../LiveTv/LiveTvManager.cs | 1 + .../MediaBrowser.Server.Implementations.csproj | 14 ++-- .../packages.config | 2 +- MediaBrowser.ServerApplication/App.config | 16 +++- MediaBrowser.ServerApplication/ApplicationHost.cs | 5 ++ MediaBrowser.Tests/app.config | 2 +- MediaBrowser.WebDashboard/Api/DashboardService.cs | 1 + .../MediaBrowser.WebDashboard.csproj | 8 +- 26 files changed, 545 insertions(+), 210 deletions(-) create mode 100644 MediaBrowser.Api/Movies/CollectionService.cs create mode 100644 MediaBrowser.Api/Movies/MoviesService.cs create mode 100644 MediaBrowser.Api/Movies/TrailersService.cs delete mode 100644 MediaBrowser.Api/MoviesService.cs delete mode 100644 MediaBrowser.Api/TrailersService.cs create mode 100644 MediaBrowser.Server.Implementations/Collections/CollectionsDynamicFolder.cs (limited to 'MediaBrowser.Server.Implementations/Collections/CollectionManager.cs') diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index 85e40eda1..e19fbb967 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -66,6 +66,7 @@ Properties\SharedVersion.cs + @@ -91,7 +92,7 @@ - + @@ -118,7 +119,7 @@ - + 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..5d97d13e1 --- /dev/null +++ b/MediaBrowser.Api/Movies/MoviesService.cs @@ -0,0 +1,82 @@ +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.Movies +{ + /// + /// Class GetSimilarMovies + /// + [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; + } + } + + /// + /// Class MoviesService + /// + public class MoviesService : BaseApiService + { + /// + /// The _user manager + /// + private readonly IUserManager _userManager; + + /// + /// The _user data repository + /// + private readonly IUserDataManager _userDataRepository; + /// + /// The _library manager + /// + private readonly ILibraryManager _libraryManager; + + private readonly IItemRepository _itemRepo; + private readonly IDtoService _dtoService; + + /// + /// Initializes a new instance of the class. + /// + /// The user manager. + /// The user data repository. + /// The library manager. + public MoviesService(IUserManager userManager, IUserDataManager userDataRepository, ILibraryManager libraryManager, IItemRepository itemRepo, IDtoService dtoService) + { + _userManager = userManager; + _userDataRepository = userDataRepository; + _libraryManager = libraryManager; + _itemRepo = itemRepo; + _dtoService = dtoService; + } + + /// + /// Gets the specified request. + /// + /// The request. + /// System.Object. + 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/Movies/TrailersService.cs b/MediaBrowser.Api/Movies/TrailersService.cs new file mode 100644 index 000000000..4e17bc7b5 --- /dev/null +++ b/MediaBrowser.Api/Movies/TrailersService.cs @@ -0,0 +1,75 @@ +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.Movies +{ + /// + /// Class GetSimilarTrailers + /// + [Route("/Trailers/{Id}/Similar", "GET")] + [Api(Description = "Finds movies and trailers similar to a given trailer.")] + public class GetSimilarTrailers : BaseGetSimilarItemsFromItem + { + } + + /// + /// Class TrailersService + /// + public class TrailersService : BaseApiService + { + /// + /// The _user manager + /// + private readonly IUserManager _userManager; + + /// + /// The _user data repository + /// + private readonly IUserDataManager _userDataRepository; + /// + /// The _library manager + /// + private readonly ILibraryManager _libraryManager; + + private readonly IItemRepository _itemRepo; + private readonly IDtoService _dtoService; + + /// + /// Initializes a new instance of the class. + /// + /// The user manager. + /// The user data repository. + /// The library manager. + public TrailersService(IUserManager userManager, IUserDataManager userDataRepository, ILibraryManager libraryManager, IItemRepository itemRepo, IDtoService dtoService) + { + _userManager = userManager; + _userDataRepository = userDataRepository; + _libraryManager = libraryManager; + _itemRepo = itemRepo; + _dtoService = dtoService; + } + + /// + /// Gets the specified request. + /// + /// The request. + /// System.Object. + public object Get(GetSimilarTrailers request) + { + var result = SimilarItemsHelper.GetSimilarItemsResult(_userManager, + _itemRepo, + _libraryManager, + _userDataRepository, + _dtoService, + Logger, + request, item => item is Movie || item is Trailer, + SimilarItemsHelper.GetSimiliarityScore); + + return ToOptimizedSerializedResultUsingCache(result); + } + } +} 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 -{ - /// - /// Class GetSimilarMovies - /// - [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; - } - } - - /// - /// Class MoviesService - /// - public class MoviesService : BaseApiService - { - /// - /// The _user manager - /// - private readonly IUserManager _userManager; - - /// - /// The _user data repository - /// - private readonly IUserDataManager _userDataRepository; - /// - /// The _library manager - /// - private readonly ILibraryManager _libraryManager; - - private readonly IItemRepository _itemRepo; - private readonly IDtoService _dtoService; - - /// - /// Initializes a new instance of the class. - /// - /// The user manager. - /// The user data repository. - /// The library manager. - public MoviesService(IUserManager userManager, IUserDataManager userDataRepository, ILibraryManager libraryManager, IItemRepository itemRepo, IDtoService dtoService) - { - _userManager = userManager; - _userDataRepository = userDataRepository; - _libraryManager = libraryManager; - _itemRepo = itemRepo; - _dtoService = dtoService; - } - - /// - /// Gets the specified request. - /// - /// The request. - /// System.Object. - 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/SearchService.cs b/MediaBrowser.Api/SearchService.cs index 18bd8c695..c3da87d40 100644 --- a/MediaBrowser.Api/SearchService.cs +++ b/MediaBrowser.Api/SearchService.cs @@ -63,6 +63,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 +133,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(',') }).ConfigureAwait(false); diff --git a/MediaBrowser.Api/TrailersService.cs b/MediaBrowser.Api/TrailersService.cs deleted file mode 100644 index ca465b5e3..000000000 --- a/MediaBrowser.Api/TrailersService.cs +++ /dev/null @@ -1,75 +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 -{ - /// - /// Class GetSimilarTrailers - /// - [Route("/Trailers/{Id}/Similar", "GET")] - [Api(Description = "Finds movies and trailers similar to a given trailer.")] - public class GetSimilarTrailers : BaseGetSimilarItemsFromItem - { - } - - /// - /// Class TrailersService - /// - public class TrailersService : BaseApiService - { - /// - /// The _user manager - /// - private readonly IUserManager _userManager; - - /// - /// The _user data repository - /// - private readonly IUserDataManager _userDataRepository; - /// - /// The _library manager - /// - private readonly ILibraryManager _libraryManager; - - private readonly IItemRepository _itemRepo; - private readonly IDtoService _dtoService; - - /// - /// Initializes a new instance of the class. - /// - /// The user manager. - /// The user data repository. - /// The library manager. - public TrailersService(IUserManager userManager, IUserDataManager userDataRepository, ILibraryManager libraryManager, IItemRepository itemRepo, IDtoService dtoService) - { - _userManager = userManager; - _userDataRepository = userDataRepository; - _libraryManager = libraryManager; - _itemRepo = itemRepo; - _dtoService = dtoService; - } - - /// - /// Gets the specified request. - /// - /// The request. - /// System.Object. - public object Get(GetSimilarTrailers request) - { - var result = SimilarItemsHelper.GetSimilarItemsResult(_userManager, - _itemRepo, - _libraryManager, - _userDataRepository, - _dtoService, - Logger, - request, item => item is Movie || item is Trailer, - SimilarItemsHelper.GetSimiliarityScore); - - return ToOptimizedSerializedResultUsingCache(result); - } - } -} diff --git a/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs b/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs index d26bf5b35..089f9b6ad 100644 --- a/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs +++ b/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs @@ -6,7 +6,7 @@ namespace MediaBrowser.Controller.Collections { public string Name { get; set; } - public Guid ParentId { get; set; } + public Guid? ParentId { get; set; } public bool IsLocked { get; set; } } diff --git a/MediaBrowser.Controller/Collections/ICollectionManager.cs b/MediaBrowser.Controller/Collections/ICollectionManager.cs index a1e6b2c12..d7bc178ad 100644 --- a/MediaBrowser.Controller/Collections/ICollectionManager.cs +++ b/MediaBrowser.Controller/Collections/ICollectionManager.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Threading.Tasks; namespace MediaBrowser.Controller.Collections @@ -16,16 +17,16 @@ namespace MediaBrowser.Controller.Collections /// Adds to collection. /// /// The collection identifier. - /// The item identifier. + /// The item ids. /// Task. - Task AddToCollection(Guid collectionId, Guid itemId); + Task AddToCollection(Guid collectionId, IEnumerable itemIds); /// /// Removes from collection. /// /// The collection identifier. - /// The item identifier. + /// The item ids. /// Task. - Task RemoveFromCollection(Guid collectionId, Guid itemId); + Task RemoveFromCollection(Guid collectionId, IEnumerable itemIds); } } diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 923673bd8..0deebeb32 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -124,6 +124,15 @@ namespace MediaBrowser.Controller.Entities } } + [IgnoreDataMember] + public virtual bool IsHidden + { + get + { + return false; + } + } + [IgnoreDataMember] public virtual bool IsOwnedItem { 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 { /// @@ -8,18 +7,6 @@ namespace MediaBrowser.Controller.Entities /// public abstract class BasePluginFolder : Folder, ICollectionFolder, IByReferenceItem { - /// - /// Gets or sets the type of the location. - /// - /// The type of the location. - 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..7dfe7f22e 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -264,7 +264,7 @@ namespace MediaBrowser.Controller.Entities [IgnoreDataMember] public IEnumerable Children { - get { return ActualChildren; } + get { return ActualChildren.Where(i => !i.IsHidden); } } /// @@ -905,13 +905,6 @@ namespace MediaBrowser.Controller.Entities /// BaseItem. 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 RefreshedOwnedItems(MetadataRefreshOptions options, List 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 GetHiddenChildren() + { + return ActualChildren.Where(i => i.IsHidden); + } } } diff --git a/MediaBrowser.Controller/Entities/LinkedChild.cs b/MediaBrowser.Controller/Entities/LinkedChild.cs index cc5f7bf38..84af0500d 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; } + /// /// Serves as a cache /// 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.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(""); } + + 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(""); + foreach (var link in items) + { + builder.Append(""); + + builder.Append("" + SecurityElement.Escape(link.ItemName) + ""); + builder.Append("" + SecurityElement.Escape(link.ItemType) + ""); + + if (link.ItemYear.HasValue) + { + builder.Append("" + SecurityElement.Escape(link.ItemYear.Value.ToString(UsCulture)) + ""); + } + + builder.Append(""); + } + builder.Append(""); + } } } diff --git a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs index da444d100..679b629a8 100644 --- a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs +++ b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs @@ -5,7 +5,9 @@ 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; @@ -30,7 +32,7 @@ namespace MediaBrowser.Server.Implementations.Collections var folderName = _fileSystem.GetValidFilename(name); - var parentFolder = _libraryManager.GetItemById(options.ParentId) as Folder; + var parentFolder = GetParentFolder(options.ParentId); if (parentFolder == null) { @@ -66,14 +68,94 @@ namespace MediaBrowser.Server.Implementations.Collections } } - public Task AddToCollection(Guid collectionId, Guid itemId) + private Folder GetParentFolder(Guid? parentId) { - throw new NotImplementedException(); + if (parentId.HasValue) + { + if (parentId.Value == Guid.Empty) + { + throw new ArgumentNullException("parentId"); + } + + return _libraryManager.GetItemById(parentId.Value) as Folder; + } + + return _libraryManager.RootFolder.Children.OfType().FirstOrDefault() ?? + _libraryManager.RootFolder.GetHiddenChildren().OfType().FirstOrDefault(); } - public Task RemoveFromCollection(Guid collectionId, Guid itemId) + public async Task AddToCollection(Guid collectionId, IEnumerable ids) { - throw new NotImplementedException(); + var collection = _libraryManager.GetItemById(collectionId) as BoxSet; + + if (collection == null) + { + throw new ArgumentException("No collection exists with the supplied Id"); + } + + var list = new List(); + + 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 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(); + + 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); + } + + foreach (var child in list) + { + collection.LinkedChildren.Remove(child); + } + + 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/Library/SearchEngine.cs b/MediaBrowser.Server.Implementations/Library/SearchEngine.cs index 12686f542..b22fd343b 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; diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index bd315530e..104ebfab8 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -583,6 +583,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv .ToDictionary(i => i.Name, StringComparer.OrdinalIgnoreCase); programs = programList.OrderByDescending(i => GetRecommendationScore(i, user.Id, serviceName, genres)) + .ThenBy(i => i.HasImage(ImageType.Primary) ? 0 : 1) .ThenBy(i => i.StartDate); if (query.Limit.HasValue) diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 0165cefad..a0df2c23a 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -65,12 +65,9 @@ - + False - ..\packages\System.Data.SQLite.x86.1.0.90.0\lib\net45\System.Data.SQLite.dll - - - ..\packages\System.Data.SQLite.x86.1.0.90.0\lib\net45\System.Data.SQLite.Linq.dll + ..\packages\System.Data.SQLite.Core.1.0.91.3\lib\net45\System.Data.SQLite.dll @@ -110,6 +107,7 @@ + @@ -378,6 +376,12 @@ swagger-ui\swagger-ui.min.js PreserveNewest + + Always + + + Always + diff --git a/MediaBrowser.Server.Implementations/packages.config b/MediaBrowser.Server.Implementations/packages.config index 3c984e265..f04536190 100644 --- a/MediaBrowser.Server.Implementations/packages.config +++ b/MediaBrowser.Server.Implementations/packages.config @@ -4,5 +4,5 @@ - + \ 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 @@
+ +
@@ -43,7 +45,7 @@ - + @@ -63,4 +65,16 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 479e07ee6..a3a878537 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(appThemeManager); + var collectionManager = new CollectionManager(LibraryManager, FileSystemManager, LibraryMonitor); + RegisterSingleInstance(collectionManager); + LiveTvManager = new LiveTvManager(ServerConfigurationManager, FileSystemManager, Logger, ItemRepository, ImageProcessor, UserDataManager, DtoService, UserManager, LibraryManager, TaskManager); RegisterSingleInstance(LiveTvManager); 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 @@ - + diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index c10b17d67..19f213b2f 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -480,6 +480,7 @@ namespace MediaBrowser.WebDashboard.Api "dashboardinfo.js", "dashboardpage.js", "directorybrowser.js", + "editcollectionitems.js", "edititemmetadata.js", "edititempeople.js", "edititemimages.js", diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 4895d203f..424192e28 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -213,6 +213,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -261,7 +264,7 @@ PreserveNewest - + PreserveNewest @@ -480,6 +483,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest -- cgit v1.2.3 From 546acf0ebb7edce384822770ccc6fca43fb2cc1c Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 7 Mar 2014 13:48:55 -0500 Subject: fixes #715 - Support creating/editing collections (boxsets) in web client #715 --- MediaBrowser.Api/SearchService.cs | 2 +- MediaBrowser.Controller/Entities/Folder.cs | 4 +- MediaBrowser.Controller/Entities/LinkedChild.cs | 6 +- MediaBrowser.Controller/Library/IUserManager.cs | 3 +- .../BoxSets/BoxSetMetadataService.cs | 10 ++ MediaBrowser.Providers/BoxSets/BoxSetXmlParser.cs | 129 +++++++++++++++++++++ .../BoxSets/BoxSetXmlProvider.cs | 2 +- .../MediaBrowser.Providers.csproj | 1 + .../Collections/CollectionManager.cs | 34 +++++- .../Library/UserManager.cs | 9 +- .../LiveTv/LiveTvManager.cs | 4 +- 11 files changed, 184 insertions(+), 20 deletions(-) create mode 100644 MediaBrowser.Providers/BoxSets/BoxSetXmlParser.cs (limited to 'MediaBrowser.Server.Implementations/Collections/CollectionManager.cs') diff --git a/MediaBrowser.Api/SearchService.cs b/MediaBrowser.Api/SearchService.cs index c3da87d40..455cf6a50 100644 --- a/MediaBrowser.Api/SearchService.cs +++ b/MediaBrowser.Api/SearchService.cs @@ -134,7 +134,7 @@ namespace MediaBrowser.Api IncludeStudios = request.IncludeStudios, StartIndex = request.StartIndex, UserId = request.UserId, - IncludeItemTypes = (request.IncludeItemTypes ?? string.Empty).Split(',') + IncludeItemTypes = (request.IncludeItemTypes ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray() }).ConfigureAwait(false); diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 7dfe7f22e..ee371680e 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -745,9 +745,9 @@ namespace MediaBrowser.Controller.Entities var list = new List(); - 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; } /// diff --git a/MediaBrowser.Controller/Entities/LinkedChild.cs b/MediaBrowser.Controller/Entities/LinkedChild.cs index 84af0500d..1ae04e40f 100644 --- a/MediaBrowser.Controller/Entities/LinkedChild.cs +++ b/MediaBrowser.Controller/Entities/LinkedChild.cs @@ -22,8 +22,8 @@ namespace MediaBrowser.Controller.Entities public enum LinkedChildType { - Manual = 1, - Shortcut = 2 + Manual = 0, + Shortcut = 1 } public class LinkedChildComparer : IEqualityComparer @@ -39,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/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 /// /// The cancellation token. - /// if set to true [force]. /// Task. - Task RefreshUsersMetadata(CancellationToken cancellationToken, bool force = false); + Task RefreshUsersMetadata(CancellationToken cancellationToken); /// /// Renames the user. 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 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 + { + 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(); + + 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..77ea52fa9 100644 --- a/MediaBrowser.Providers/BoxSets/BoxSetXmlProvider.cs +++ b/MediaBrowser.Providers/BoxSets/BoxSetXmlProvider.cs @@ -22,7 +22,7 @@ namespace MediaBrowser.Providers.BoxSets protected override void Fetch(LocalMetadataResult result, string path, CancellationToken cancellationToken) { - new BaseItemXmlParser(_logger).Fetch(result.Item, path, cancellationToken); + new BoxSetXmlParser(_logger).Fetch(result.Item, path, cancellationToken); } protected override FileInfo GetXmlFile(ItemInfo info, IDirectoryService directoryService) 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 @@ + diff --git a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs index 679b629a8..5a5dfdd3e 100644 --- a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs +++ b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs @@ -148,9 +148,39 @@ namespace MediaBrowser.Server.Implementations.Collections list.Add(child); } - foreach (var child in list) + 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 { - collection.LinkedChildren.Remove(child); + 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); 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 /// /// The cancellation token. - /// if set to true [force]. /// Task. - 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/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index 104ebfab8..7c0361a1f 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -582,8 +582,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)) - .ThenBy(i => i.HasImage(ImageType.Primary) ? 0 : 1) + 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) -- cgit v1.2.3 From c6bd890cb2976c4cbf5c278a5987320dcb631652 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Fri, 7 Mar 2014 23:20:31 -0500 Subject: refined collection editing --- .../Collections/CollectionCreationOptions.cs | 13 +++++++++++-- .../Collections/CollectionManager.cs | 8 ++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) (limited to 'MediaBrowser.Server.Implementations/Collections/CollectionManager.cs') diff --git a/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs b/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs index 089f9b6ad..e147e0905 100644 --- a/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs +++ b/MediaBrowser.Controller/Collections/CollectionCreationOptions.cs @@ -1,13 +1,22 @@ -using System; +using MediaBrowser.Model.Entities; +using System; +using System.Collections.Generic; namespace MediaBrowser.Controller.Collections { - public class CollectionCreationOptions + public class CollectionCreationOptions : IHasProviderIds { public string Name { get; set; } public Guid? ParentId { get; set; } public bool IsLocked { get; set; } + + public Dictionary ProviderIds { get; set; } + + public CollectionCreationOptions() + { + ProviderIds = new Dictionary(StringComparer.OrdinalIgnoreCase); + } } } diff --git a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs index 5a5dfdd3e..1cfcef514 100644 --- a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs +++ b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs @@ -30,7 +30,10 @@ namespace MediaBrowser.Server.Implementations.Collections { var name = options.Name; - var folderName = _fileSystem.GetValidFilename(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); @@ -53,7 +56,8 @@ namespace MediaBrowser.Server.Implementations.Collections Parent = parentFolder, DisplayMediaType = "Collection", Path = path, - DontFetchMeta = options.IsLocked + DontFetchMeta = options.IsLocked, + ProviderIds = options.ProviderIds }; await parentFolder.AddChild(collection, CancellationToken.None).ConfigureAwait(false); -- cgit v1.2.3 From 38e5e32b42a2eb217457cbd42e8c26b46d401b23 Mon Sep 17 00:00:00 2001 From: Luke Pulverenti Date: Mon, 10 Mar 2014 13:38:53 -0400 Subject: added play to config page --- MediaBrowser.Dlna/PlayTo/PlayToServerEntryPoint.cs | 91 +++++++++++++++++++--- .../MediaBrowser.Model.Portable.csproj | 3 + .../MediaBrowser.Model.net35.csproj | 3 + MediaBrowser.Model/Configuration/DlnaOptions.cs | 8 ++ .../Configuration/ServerConfiguration.cs | 4 + MediaBrowser.Model/MediaBrowser.Model.csproj | 1 + .../Collections/CollectionManager.cs | 8 +- MediaBrowser.WebDashboard/Api/DashboardService.cs | 1 + .../MediaBrowser.WebDashboard.csproj | 6 ++ MediaBrowser.sln | 2 +- 10 files changed, 113 insertions(+), 14 deletions(-) create mode 100644 MediaBrowser.Model/Configuration/DlnaOptions.cs (limited to 'MediaBrowser.Server.Implementations/Collections/CollectionManager.cs') 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.Model.Portable/MediaBrowser.Model.Portable.csproj b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj index 04296de35..8dd8a2a34 100644 --- a/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj +++ b/MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj @@ -80,6 +80,9 @@ Configuration\BaseApplicationConfiguration.cs + + Configuration\DlnaOptions.cs + Configuration\ManualLoginCategory.cs diff --git a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj index 56f7fb99d..4cdcaae64 100644 --- a/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj +++ b/MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj @@ -67,6 +67,9 @@ Configuration\BaseApplicationConfiguration.cs + + Configuration\DlnaOptions.cs + Configuration\ManualLoginCategory.cs 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; } + /// /// Initializes a new instance of the class. /// @@ -271,6 +273,8 @@ namespace MediaBrowser.Model.Configuration }; MetadataOptions = options.ToArray(); + + DlnaOptions = new DlnaOptions(); } } diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj index 10aedb3ba..42ae7c396 100644 --- a/MediaBrowser.Model/MediaBrowser.Model.csproj +++ b/MediaBrowser.Model/MediaBrowser.Model.csproj @@ -60,6 +60,7 @@ + diff --git a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs index 1cfcef514..9a196cc47 100644 --- a/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs +++ b/MediaBrowser.Server.Implementations/Collections/CollectionManager.cs @@ -81,7 +81,13 @@ namespace MediaBrowser.Server.Implementations.Collections throw new ArgumentNullException("parentId"); } - return _libraryManager.GetItemById(parentId.Value) as Folder; + var folder = _libraryManager.GetItemById(parentId.Value) as Folder; + + // Find an actual physical folder + if (folder is CollectionFolder) + { + return _libraryManager.RootFolder.Children.OfType().First(i => folder.PhysicalLocations.Contains(i.Path, StringComparer.OrdinalIgnoreCase)); + } } return _libraryManager.RootFolder.Children.OfType().FirstOrDefault() ?? diff --git a/MediaBrowser.WebDashboard/Api/DashboardService.cs b/MediaBrowser.WebDashboard/Api/DashboardService.cs index 19f213b2f..fa2ae278c 100644 --- a/MediaBrowser.WebDashboard/Api/DashboardService.cs +++ b/MediaBrowser.WebDashboard/Api/DashboardService.cs @@ -480,6 +480,7 @@ namespace MediaBrowser.WebDashboard.Api "dashboardinfo.js", "dashboardpage.js", "directorybrowser.js", + "dlnasettings.js", "editcollectionitems.js", "edititemmetadata.js", "edititempeople.js", diff --git a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj index 424192e28..4b2ce4833 100644 --- a/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj +++ b/MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj @@ -213,6 +213,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest @@ -483,6 +486,9 @@ PreserveNewest + + PreserveNewest + PreserveNewest diff --git a/MediaBrowser.sln b/MediaBrowser.sln index 7ac158065..1d8a3bf26 100644 --- a/MediaBrowser.sln +++ b/MediaBrowser.sln @@ -254,4 +254,4 @@ Global GlobalSection(Performance) = preSolution HasPerformanceSessions = true EndGlobalSection -EndGlobal +EndGlobal \ No newline at end of file -- cgit v1.2.3