diff options
17 files changed, 863 insertions, 1068 deletions
diff --git a/MediaBrowser.Api/Library/LibraryHelpers.cs b/MediaBrowser.Api/Library/LibraryHelpers.cs index 3836a0860..be9f00a61 100644 --- a/MediaBrowser.Api/Library/LibraryHelpers.cs +++ b/MediaBrowser.Api/Library/LibraryHelpers.cs @@ -1,6 +1,5 @@ using MediaBrowser.Common.IO; using MediaBrowser.Controller; -using MediaBrowser.Controller.Entities; using System; using System.IO; using System.Linq; @@ -27,12 +26,11 @@ namespace MediaBrowser.Api.Library /// <param name="fileSystem">The file system.</param> /// <param name="virtualFolderName">Name of the virtual folder.</param> /// <param name="mediaPath">The media path.</param> - /// <param name="user">The user.</param> /// <param name="appPaths">The app paths.</param> /// <exception cref="System.IO.DirectoryNotFoundException">The media folder does not exist</exception> - public static void RemoveMediaPath(IFileSystem fileSystem, string virtualFolderName, string mediaPath, User user, IServerApplicationPaths appPaths) + public static void RemoveMediaPath(IFileSystem fileSystem, string virtualFolderName, string mediaPath, IServerApplicationPaths appPaths) { - var rootFolderPath = user != null ? user.RootFolderPath : appPaths.DefaultUserViewsPath; + var rootFolderPath = appPaths.DefaultUserViewsPath; var path = Path.Combine(rootFolderPath, virtualFolderName); if (!Directory.Exists(path)) @@ -54,18 +52,17 @@ namespace MediaBrowser.Api.Library /// <param name="fileSystem">The file system.</param> /// <param name="virtualFolderName">Name of the virtual folder.</param> /// <param name="path">The path.</param> - /// <param name="user">The user.</param> /// <param name="appPaths">The app paths.</param> - /// <exception cref="System.ArgumentException">The path is not valid.</exception> /// <exception cref="System.IO.DirectoryNotFoundException">The path does not exist.</exception> - public static void AddMediaPath(IFileSystem fileSystem, string virtualFolderName, string path, User user, IServerApplicationPaths appPaths) + /// <exception cref="System.ArgumentException">The path is not valid.</exception> + public static void AddMediaPath(IFileSystem fileSystem, string virtualFolderName, string path, IServerApplicationPaths appPaths) { if (!Directory.Exists(path)) { throw new DirectoryNotFoundException("The path does not exist."); } - var rootFolderPath = user != null ? user.RootFolderPath : appPaths.DefaultUserViewsPath; + var rootFolderPath = appPaths.DefaultUserViewsPath; var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName); var shortcutFilename = Path.GetFileNameWithoutExtension(path); diff --git a/MediaBrowser.Api/Library/LibraryService.cs b/MediaBrowser.Api/Library/LibraryService.cs index 840a5b851..1a14646e1 100644 --- a/MediaBrowser.Api/Library/LibraryService.cs +++ b/MediaBrowser.Api/Library/LibraryService.cs @@ -1,12 +1,216 @@ -using MediaBrowser.Common; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Controller.Dto; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Audio; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Persistence; +using MediaBrowser.Model.Dto; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Querying; using ServiceStack; using System; +using System.Collections; using System.Collections.Generic; +using System.Globalization; +using System.IO; using System.Linq; +using System.Threading; +using System.Threading.Tasks; namespace MediaBrowser.Api.Library { + [Route("/Items/{Id}/File", "GET")] + [Api(Description = "Gets the original file of an item")] + public class GetFile + { + /// <summary> + /// Gets or sets the id. + /// </summary> + /// <value>The id.</value> + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Id { get; set; } + } + + [Route("/Videos/{Id}/Subtitle/{Index}", "GET")] + [Api(Description = "Gets an external subtitle file")] + public class GetSubtitle + { + /// <summary> + /// Gets or sets the id. + /// </summary> + /// <value>The id.</value> + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Id { get; set; } + + [ApiMember(Name = "Index", Description = "The subtitle stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")] + public int Index { get; set; } + } + + /// <summary> + /// Class GetCriticReviews + /// </summary> + [Route("/Items/{Id}/CriticReviews", "GET")] + [Api(Description = "Gets critic reviews for an item")] + public class GetCriticReviews : IReturn<QueryResult<ItemReview>> + { + /// <summary> + /// Gets or sets the id. + /// </summary> + /// <value>The id.</value> + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Id { 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> + /// Class GetThemeSongs + /// </summary> + [Route("/Items/{Id}/ThemeSongs", "GET")] + [Api(Description = "Gets theme songs for an item")] + public class GetThemeSongs : IReturn<ThemeMediaResult> + { + /// <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; } + + /// <summary> + /// Gets or sets the id. + /// </summary> + /// <value>The id.</value> + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Id { get; set; } + + [ApiMember(Name = "InheritFromParent", Description = "Determines whether or not parent items should be searched for theme media.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public bool InheritFromParent { get; set; } + } + + /// <summary> + /// Class GetThemeVideos + /// </summary> + [Route("/Items/{Id}/ThemeVideos", "GET")] + [Api(Description = "Gets theme videos for an item")] + public class GetThemeVideos : IReturn<ThemeMediaResult> + { + /// <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; } + + /// <summary> + /// Gets or sets the id. + /// </summary> + /// <value>The id.</value> + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Id { get; set; } + + [ApiMember(Name = "InheritFromParent", Description = "Determines whether or not parent items should be searched for theme media.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public bool InheritFromParent { get; set; } + } + + /// <summary> + /// Class GetThemeVideos + /// </summary> + [Route("/Items/{Id}/ThemeMedia", "GET")] + [Api(Description = "Gets theme videos and songs for an item")] + public class GetThemeMedia : IReturn<AllThemeMediaResult> + { + /// <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; } + + /// <summary> + /// Gets or sets the id. + /// </summary> + /// <value>The id.</value> + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Id { get; set; } + + [ApiMember(Name = "InheritFromParent", Description = "Determines whether or not parent items should be searched for theme media.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public bool InheritFromParent { get; set; } + } + + [Route("/Library/Refresh", "POST")] + [Api(Description = "Starts a library scan")] + public class RefreshLibrary : IReturnVoid + { + } + + [Route("/Items/{Id}", "DELETE")] + [Api(Description = "Deletes an item from the library and file system")] + public class DeleteItem : IReturnVoid + { + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] + public string Id { get; set; } + } + + [Route("/Items/Counts", "GET")] + [Api(Description = "Gets counts of various item types")] + public class GetItemCounts : IReturn<ItemCounts> + { + [ApiMember(Name = "UserId", Description = "Optional. Get counts from a specific user's library.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] + public Guid? UserId { get; set; } + + [ApiMember(Name = "IsFavorite", Description = "Optional. Get counts of favorite items", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] + public bool? IsFavorite { get; set; } + } + + [Route("/Items/{Id}/Ancestors", "GET")] + [Api(Description = "Gets all parents of an item")] + public class GetAncestors : IReturn<BaseItemDto[]> + { + /// <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; } + + /// <summary> + /// Gets or sets the id. + /// </summary> + /// <value>The id.</value> + [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] + public string Id { get; set; } + } + + [Route("/Items/YearIndex", "GET")] + [Api(Description = "Gets a year index based on an item query.")] + public class GetYearIndex : IReturn<List<ItemIndex>> + { + /// <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; } + + [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; } + } + /// <summary> /// Class GetPhyscialPaths /// </summary> @@ -16,32 +220,94 @@ namespace MediaBrowser.Api.Library { } + [Route("/Library/MediaFolders", "GET")] + [Api(Description = "Gets all user media folders.")] + public class GetMediaFolders : IReturn<ItemsResult> + { + + } + /// <summary> /// Class LibraryService /// </summary> public class LibraryService : BaseApiService { /// <summary> - /// The _app host + /// The _item repo /// </summary> - private readonly IApplicationHost _appHost; + private readonly IItemRepository _itemRepo; + private readonly ILibraryManager _libraryManager; + private readonly IUserManager _userManager; + private readonly IUserDataManager _userDataManager; + + private readonly IDtoService _dtoService; /// <summary> /// Initializes a new instance of the <see cref="LibraryService" /> class. /// </summary> - /// <param name="appHost">The app host.</param> - /// <param name="libraryManager">The library manager.</param> - /// <exception cref="System.ArgumentNullException">appHost</exception> - public LibraryService(IApplicationHost appHost, ILibraryManager libraryManager) + public LibraryService(IItemRepository itemRepo, ILibraryManager libraryManager, IUserManager userManager, + IDtoService dtoService, IUserDataManager userDataManager) { - if (appHost == null) + _itemRepo = itemRepo; + _libraryManager = libraryManager; + _userManager = userManager; + _dtoService = dtoService; + _userDataManager = userDataManager; + } + + public object Get(GetMediaFolders request) + { + var items = _libraryManager.GetUserRootFolder().Children.ToList(); + + // Get everything + var fields = Enum.GetNames(typeof(ItemFields)) + .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)) + .ToList(); + + var result = new ItemsResult { - throw new ArgumentNullException("appHost"); + TotalRecordCount = items.Count, + + Items = items.Select(i => _dtoService.GetBaseItemDto(i, fields)).ToArray() + }; + + return ToOptimizedResult(result); + } + + public object Get(GetFile request) + { + var item = _dtoService.GetItemByDtoId(request.Id); + var locationType = item.LocationType; + if (locationType == LocationType.Remote || locationType == LocationType.Virtual) + { + throw new ArgumentException("This command cannot be used for remote or virtual items."); + } + if (Directory.Exists(item.Path)) + { + throw new ArgumentException("This command cannot be used for directories."); } - _appHost = appHost; - _libraryManager = libraryManager; + return ToStaticFileResult(item.Path); + } + + public object Get(GetSubtitle request) + { + var subtitleStream = _itemRepo.GetMediaStreams(new MediaStreamQuery + { + + Index = request.Index, + ItemId = new Guid(request.Id), + Type = MediaStreamType.Subtitle + + }).FirstOrDefault(); + + if (subtitleStream == null) + { + throw new ResourceNotFoundException(); + } + + return ToStaticFileResult(subtitleStream.Path); } /// <summary> @@ -57,5 +323,466 @@ namespace MediaBrowser.Api.Library return ToOptimizedSerializedResultUsingCache(result); } + + /// <summary> + /// Gets the specified request. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>System.Object.</returns> + public object Get(GetAncestors request) + { + var result = GetAncestors(request); + + return ToOptimizedSerializedResultUsingCache(result); + } + + /// <summary> + /// Gets the ancestors. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>Task{BaseItemDto[]}.</returns> + public List<BaseItemDto> GetAncestors(GetAncestors request) + { + var item = _dtoService.GetItemByDtoId(request.Id); + + var baseItemDtos = new List<BaseItemDto>(); + + var user = request.UserId.HasValue ? _userManager.GetUserById(request.UserId.Value) : null; + + // Get everything + var fields = Enum.GetNames(typeof(ItemFields)) + .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)) + .ToList(); + + BaseItem parent = item.Parent; + + while (parent != null) + { + if (user != null) + { + parent = TranslateParentItem(parent, user); + } + + baseItemDtos.Add(_dtoService.GetBaseItemDto(parent, fields, user)); + + parent = parent.Parent; + } + + return baseItemDtos.ToList(); + } + + private BaseItem TranslateParentItem(BaseItem item, User user) + { + if (item.Parent is AggregateFolder) + { + return user.RootFolder.GetChildren(user, true).FirstOrDefault(i => i.PhysicalLocations.Contains(item.Path)); + } + + return item; + } + + /// <summary> + /// Gets the specified request. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>System.Object.</returns> + public object Get(GetCriticReviews request) + { + var result = GetCriticReviews(request); + + return ToOptimizedSerializedResultUsingCache(result); + } + + /// <summary> + /// Gets the specified request. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>System.Object.</returns> + public object Get(GetItemCounts request) + { + var items = GetAllLibraryItems(request.UserId, _userManager, _libraryManager) + .Where(i => i.LocationType != LocationType.Virtual) + .ToList(); + + var filteredItems = request.UserId.HasValue ? FilterItems(items, request, request.UserId.Value).ToList() : items; + + var albums = filteredItems.OfType<MusicAlbum>().ToList(); + var episodes = filteredItems.OfType<Episode>().ToList(); + var games = filteredItems.OfType<Game>().ToList(); + var movies = filteredItems.OfType<Movie>().ToList(); + var musicVideos = filteredItems.OfType<MusicVideo>().ToList(); + var adultVideos = filteredItems.OfType<AdultVideo>().ToList(); + var boxsets = filteredItems.OfType<BoxSet>().ToList(); + var books = filteredItems.OfType<Book>().ToList(); + var songs = filteredItems.OfType<Audio>().ToList(); + var series = filteredItems.OfType<Series>().ToList(); + + var counts = new ItemCounts + { + AlbumCount = albums.Count, + EpisodeCount = episodes.Count, + GameCount = games.Count, + GameSystemCount = filteredItems.OfType<GameSystem>().Count(), + MovieCount = movies.Count, + SeriesCount = series.Count, + SongCount = songs.Count, + TrailerCount = filteredItems.OfType<Trailer>().Count(), + MusicVideoCount = musicVideos.Count, + AdultVideoCount = adultVideos.Count, + BoxSetCount = boxsets.Count, + BookCount = books.Count, + + UniqueTypes = items.Select(i => i.GetClientTypeName()).Distinct().ToList() + }; + + return ToOptimizedSerializedResultUsingCache(counts); + } + + private IEnumerable<T> FilterItems<T>(IEnumerable<T> items, GetItemCounts request, Guid userId) + where T : BaseItem + { + if (request.IsFavorite.HasValue) + { + var val = request.IsFavorite.Value; + + items = items.Where(i => _userDataManager.GetUserData(userId, i.GetUserDataKey()).IsFavorite == val); + } + + return items; + } + + /// <summary> + /// Posts the specified request. + /// </summary> + /// <param name="request">The request.</param> + public async void Post(RefreshLibrary request) + { + try + { + await _libraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None) + .ConfigureAwait(false); + } + catch (Exception ex) + { + Logger.ErrorException("Error refreshing library", ex); + } + } + + /// <summary> + /// Deletes the specified request. + /// </summary> + /// <param name="request">The request.</param> + public void Delete(DeleteItem request) + { + var task = DeleteItem(request); + + Task.WaitAll(task); + } + + private Task DeleteItem(DeleteItem request) + { + var item = _dtoService.GetItemByDtoId(request.Id); + + return _libraryManager.DeleteItem(item); + } + + /// <summary> + /// Gets the critic reviews async. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>Task{ItemReviewsResult}.</returns> + private QueryResult<ItemReview> GetCriticReviews(GetCriticReviews request) + { + var reviews = _itemRepo.GetCriticReviews(new Guid(request.Id)); + + var reviewsArray = reviews.ToArray(); + + var result = new QueryResult<ItemReview> + { + TotalRecordCount = reviewsArray.Length + }; + + if (request.StartIndex.HasValue) + { + reviewsArray = reviewsArray.Skip(request.StartIndex.Value).ToArray(); + } + if (request.Limit.HasValue) + { + reviewsArray = reviewsArray.Take(request.Limit.Value).ToArray(); + } + + result.Items = reviewsArray; + + return result; + } + + public object Get(GetThemeMedia request) + { + var themeSongs = GetThemeSongs(new GetThemeSongs + { + InheritFromParent = request.InheritFromParent, + Id = request.Id, + UserId = request.UserId + + }); + + var themeVideos = GetThemeVideos(new GetThemeVideos + { + InheritFromParent = request.InheritFromParent, + Id = request.Id, + UserId = request.UserId + + }); + + return ToOptimizedSerializedResultUsingCache(new AllThemeMediaResult + { + ThemeSongsResult = themeSongs, + ThemeVideosResult = themeVideos, + + SoundtrackSongsResult = GetSoundtrackSongs(request.Id, request.UserId, request.InheritFromParent) + }); + } + + /// <summary> + /// Gets the specified request. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>System.Object.</returns> + public object Get(GetThemeSongs request) + { + var result = GetThemeSongs(request); + + return ToOptimizedSerializedResultUsingCache(result); + } + + private ThemeMediaResult GetThemeSongs(GetThemeSongs request) + { + var user = request.UserId.HasValue ? _userManager.GetUserById(request.UserId.Value) : null; + + var item = string.IsNullOrEmpty(request.Id) + ? (request.UserId.HasValue + ? user.RootFolder + : (Folder)_libraryManager.RootFolder) + : _dtoService.GetItemByDtoId(request.Id, request.UserId); + + var originalItem = item; + + while (GetThemeSongIds(item).Count == 0 && request.InheritFromParent && item.Parent != null) + { + item = item.Parent; + } + + // Get everything + var fields = Enum.GetNames(typeof(ItemFields)) + .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)) + .ToList(); + + var themeSongIds = GetThemeSongIds(item); + + if (themeSongIds.Count == 0 && request.InheritFromParent) + { + var album = originalItem as MusicAlbum; + + if (album != null) + { + var linkedItemWithThemes = album.SoundtrackIds + .Select(i => _libraryManager.GetItemById(i)) + .FirstOrDefault(i => GetThemeSongIds(i).Count > 0); + + if (linkedItemWithThemes != null) + { + themeSongIds = GetThemeSongIds(linkedItemWithThemes); + item = linkedItemWithThemes; + } + } + } + + var dtos = themeSongIds.Select(_libraryManager.GetItemById) + .OrderBy(i => i.SortName) + .Select(i => _dtoService.GetBaseItemDto(i, fields, user, item)); + + var items = dtos.ToArray(); + + return new ThemeMediaResult + { + Items = items, + TotalRecordCount = items.Length, + OwnerId = _dtoService.GetDtoId(item) + }; + } + + /// <summary> + /// Gets the specified request. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>System.Object.</returns> + public object Get(GetThemeVideos request) + { + var result = GetThemeVideos(request); + + return ToOptimizedSerializedResultUsingCache(result); + } + + public ThemeMediaResult GetThemeVideos(GetThemeVideos request) + { + var user = request.UserId.HasValue ? _userManager.GetUserById(request.UserId.Value) : null; + + var item = string.IsNullOrEmpty(request.Id) + ? (request.UserId.HasValue + ? user.RootFolder + : (Folder)_libraryManager.RootFolder) + : _dtoService.GetItemByDtoId(request.Id, request.UserId); + + var originalItem = item; + + while (GetThemeVideoIds(item).Count == 0 && request.InheritFromParent && item.Parent != null) + { + item = item.Parent; + } + + // Get everything + var fields = Enum.GetNames(typeof(ItemFields)) + .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)) + .ToList(); + + var themeVideoIds = GetThemeVideoIds(item); + + if (themeVideoIds.Count == 0 && request.InheritFromParent) + { + var album = originalItem as MusicAlbum; + + if (album == null) + { + album = originalItem.Parents.OfType<MusicAlbum>().FirstOrDefault(); + } + + if (album != null) + { + var linkedItemWithThemes = album.SoundtrackIds + .Select(i => _libraryManager.GetItemById(i)) + .FirstOrDefault(i => GetThemeVideoIds(i).Count > 0); + + if (linkedItemWithThemes != null) + { + themeVideoIds = GetThemeVideoIds(linkedItemWithThemes); + item = linkedItemWithThemes; + } + } + } + + var dtos = themeVideoIds.Select(_libraryManager.GetItemById) + .OrderBy(i => i.SortName) + .Select(i => _dtoService.GetBaseItemDto(i, fields, user, item)); + + var items = dtos.ToArray(); + + return new ThemeMediaResult + { + Items = items, + TotalRecordCount = items.Length, + OwnerId = _dtoService.GetDtoId(item) + }; + } + + private List<Guid> GetThemeVideoIds(BaseItem item) + { + var i = item as IHasThemeMedia; + + if (i != null) + { + return i.ThemeVideoIds; + } + + return new List<Guid>(); + } + + private List<Guid> GetThemeSongIds(BaseItem item) + { + var i = item as IHasThemeMedia; + + if (i != null) + { + return i.ThemeSongIds; + } + + return new List<Guid>(); + } + + private readonly CultureInfo _usCulture = new CultureInfo("en-US"); + + public object Get(GetYearIndex request) + { + IEnumerable<BaseItem> items = GetAllLibraryItems(request.UserId, _userManager, _libraryManager); + + if (!string.IsNullOrEmpty(request.IncludeItemTypes)) + { + var vals = request.IncludeItemTypes.Split(','); + items = items.Where(f => vals.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase)); + } + + var lookup = items + .ToLookup(i => i.ProductionYear ?? -1) + .OrderBy(i => i.Key) + .Select(i => new ItemIndex + { + ItemCount = i.Count(), + Name = i.Key == -1 ? string.Empty : i.Key.ToString(_usCulture) + }) + .ToList(); + + return ToOptimizedSerializedResultUsingCache(lookup); + } + + public ThemeMediaResult GetSoundtrackSongs(string id, Guid? userId, bool inheritFromParent) + { + var user = userId.HasValue ? _userManager.GetUserById(userId.Value) : null; + + var item = string.IsNullOrEmpty(id) + ? (userId.HasValue + ? user.RootFolder + : (Folder)_libraryManager.RootFolder) + : _dtoService.GetItemByDtoId(id, userId); + + // Get everything + var fields = Enum.GetNames(typeof(ItemFields)) + .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)) + .ToList(); + + var dtos = GetSoundtrackSongIds(item, inheritFromParent) + .Select(_libraryManager.GetItemById) + .OfType<MusicAlbum>() + .SelectMany(i => i.RecursiveChildren) + .OfType<Audio>() + .OrderBy(i => i.SortName) + .Select(i => _dtoService.GetBaseItemDto(i, fields, user, item)); + + var items = dtos.ToArray(); + + return new ThemeMediaResult + { + Items = items, + TotalRecordCount = items.Length, + OwnerId = _dtoService.GetDtoId(item) + }; + } + + private IEnumerable<Guid> GetSoundtrackSongIds(BaseItem item, bool inherit) + { + var hasSoundtracks = item as IHasSoundtracks; + + if (hasSoundtracks != null) + { + return hasSoundtracks.SoundtrackIds; + } + + if (!inherit) + { + return null; + } + + hasSoundtracks = item.Parents.OfType<IHasSoundtracks>().FirstOrDefault(); + + return hasSoundtracks != null ? hasSoundtracks.SoundtrackIds : new List<Guid>(); + } } } diff --git a/MediaBrowser.Api/Library/LibraryStructureService.cs b/MediaBrowser.Api/Library/LibraryStructureService.cs index ff4f78c96..947da29fe 100644 --- a/MediaBrowser.Api/Library/LibraryStructureService.cs +++ b/MediaBrowser.Api/Library/LibraryStructureService.cs @@ -28,16 +28,9 @@ namespace MediaBrowser.Api.Library } [Route("/Library/VirtualFolders", "POST")] - [Route("/Users/{UserId}/VirtualFolders", "POST")] public class AddVirtualFolder : IReturnVoid { /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - public string UserId { get; set; } - - /// <summary> /// Gets or sets the name. /// </summary> /// <value>The name.</value> @@ -57,16 +50,9 @@ namespace MediaBrowser.Api.Library } [Route("/Library/VirtualFolders", "DELETE")] - [Route("/Users/{UserId}/VirtualFolders", "DELETE")] public class RemoveVirtualFolder : IReturnVoid { /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - public string UserId { get; set; } - - /// <summary> /// Gets or sets the name. /// </summary> /// <value>The name.</value> @@ -80,16 +66,9 @@ namespace MediaBrowser.Api.Library } [Route("/Library/VirtualFolders/Name", "POST")] - [Route("/Users/{UserId}/VirtualFolders/Name", "POST")] public class RenameVirtualFolder : IReturnVoid { /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - public string UserId { get; set; } - - /// <summary> /// Gets or sets the name. /// </summary> /// <value>The name.</value> @@ -109,16 +88,9 @@ namespace MediaBrowser.Api.Library } [Route("/Library/VirtualFolders/Paths", "POST")] - [Route("/Users/{UserId}/VirtualFolders/Paths", "POST")] public class AddMediaPath : IReturnVoid { /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - public string UserId { get; set; } - - /// <summary> /// Gets or sets the name. /// </summary> /// <value>The name.</value> @@ -138,16 +110,9 @@ namespace MediaBrowser.Api.Library } [Route("/Library/VirtualFolders/Paths", "DELETE")] - [Route("/Users/{UserId}/VirtualFolders/Paths", "DELETE")] public class RemoveMediaPath : IReturnVoid { /// <summary> - /// Gets or sets the user id. - /// </summary> - /// <value>The user id.</value> - public string UserId { get; set; } - - /// <summary> /// Gets or sets the name. /// </summary> /// <value>The name.</value> @@ -275,18 +240,7 @@ namespace MediaBrowser.Api.Library var name = _fileSystem.GetValidFilename(request.Name); - string rootFolderPath; - - if (string.IsNullOrEmpty(request.UserId)) - { - rootFolderPath = _appPaths.DefaultUserViewsPath; - } - else - { - var user = _userManager.GetUserById(new Guid(request.UserId)); - - rootFolderPath = user.RootFolderPath; - } + var rootFolderPath = _appPaths.DefaultUserViewsPath; var virtualFolderPath = Path.Combine(rootFolderPath, name); @@ -344,18 +298,7 @@ namespace MediaBrowser.Api.Library throw new ArgumentNullException("request"); } - string rootFolderPath; - - if (string.IsNullOrEmpty(request.UserId)) - { - rootFolderPath = _appPaths.DefaultUserViewsPath; - } - else - { - var user = _userManager.GetUserById(new Guid(request.UserId)); - - rootFolderPath = user.RootFolderPath; - } + var rootFolderPath = _appPaths.DefaultUserViewsPath; var currentPath = Path.Combine(rootFolderPath, request.Name); var newPath = Path.Combine(rootFolderPath, request.NewName); @@ -417,18 +360,7 @@ namespace MediaBrowser.Api.Library throw new ArgumentNullException("request"); } - string rootFolderPath; - - if (string.IsNullOrEmpty(request.UserId)) - { - rootFolderPath = _appPaths.DefaultUserViewsPath; - } - else - { - var user = _userManager.GetUserById(new Guid(request.UserId)); - - rootFolderPath = user.RootFolderPath; - } + var rootFolderPath = _appPaths.DefaultUserViewsPath; var path = Path.Combine(rootFolderPath, request.Name); @@ -478,16 +410,7 @@ namespace MediaBrowser.Api.Library try { - if (string.IsNullOrEmpty(request.UserId)) - { - LibraryHelpers.AddMediaPath(_fileSystem, request.Name, request.Path, null, _appPaths); - } - else - { - var user = _userManager.GetUserById(new Guid(request.UserId)); - - LibraryHelpers.AddMediaPath(_fileSystem, request.Name, request.Path, user, _appPaths); - } + LibraryHelpers.AddMediaPath(_fileSystem, request.Name, request.Path, _appPaths); // Need to add a delay here or directory watchers may still pick up the changes var task = Task.Delay(1000); @@ -524,16 +447,7 @@ namespace MediaBrowser.Api.Library try { - if (string.IsNullOrEmpty(request.UserId)) - { - LibraryHelpers.RemoveMediaPath(_fileSystem, request.Name, request.Path, null, _appPaths); - } - else - { - var user = _userManager.GetUserById(new Guid(request.UserId)); - - LibraryHelpers.RemoveMediaPath(_fileSystem, request.Name, request.Path, user, _appPaths); - } + LibraryHelpers.RemoveMediaPath(_fileSystem, request.Name, request.Path, _appPaths); // Need to add a delay here or directory watchers may still pick up the changes var task = Task.Delay(1000); diff --git a/MediaBrowser.Api/LibraryService.cs b/MediaBrowser.Api/LibraryService.cs deleted file mode 100644 index f8b4caf94..000000000 --- a/MediaBrowser.Api/LibraryService.cs +++ /dev/null @@ -1,744 +0,0 @@ -using MediaBrowser.Common.Extensions; -using MediaBrowser.Controller.Dto; -using MediaBrowser.Controller.Entities; -using MediaBrowser.Controller.Entities.Audio; -using MediaBrowser.Controller.Entities.Movies; -using MediaBrowser.Controller.Entities.TV; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Persistence; -using MediaBrowser.Model.Dto; -using MediaBrowser.Model.Entities; -using MediaBrowser.Model.Querying; -using ServiceStack; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace MediaBrowser.Api -{ - [Route("/Items/{Id}/File", "GET")] - [Api(Description = "Gets the original file of an item")] - public class GetFile - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - [Route("/Videos/{Id}/Subtitle/{Index}", "GET")] - [Api(Description = "Gets an external subtitle file")] - public class GetSubtitle - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - - [ApiMember(Name = "Index", Description = "The subtitle stream index", IsRequired = true, DataType = "int", ParameterType = "path", Verb = "GET")] - public int Index { get; set; } - } - - /// <summary> - /// Class GetCriticReviews - /// </summary> - [Route("/Items/{Id}/CriticReviews", "GET")] - [Api(Description = "Gets critic reviews for an item")] - public class GetCriticReviews : IReturn<QueryResult<ItemReview>> - { - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { 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> - /// Class GetThemeSongs - /// </summary> - [Route("/Items/{Id}/ThemeSongs", "GET")] - [Api(Description = "Gets theme songs for an item")] - public class GetThemeSongs : IReturn<ThemeMediaResult> - { - /// <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; } - - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - - [ApiMember(Name = "InheritFromParent", Description = "Determines whether or not parent items should be searched for theme media.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public bool InheritFromParent { get; set; } - } - - /// <summary> - /// Class GetThemeVideos - /// </summary> - [Route("/Items/{Id}/ThemeVideos", "GET")] - [Api(Description = "Gets theme videos for an item")] - public class GetThemeVideos : IReturn<ThemeMediaResult> - { - /// <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; } - - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - - [ApiMember(Name = "InheritFromParent", Description = "Determines whether or not parent items should be searched for theme media.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public bool InheritFromParent { get; set; } - } - - /// <summary> - /// Class GetThemeVideos - /// </summary> - [Route("/Items/{Id}/ThemeMedia", "GET")] - [Api(Description = "Gets theme videos and songs for an item")] - public class GetThemeMedia : IReturn<AllThemeMediaResult> - { - /// <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; } - - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - - [ApiMember(Name = "InheritFromParent", Description = "Determines whether or not parent items should be searched for theme media.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public bool InheritFromParent { get; set; } - } - - [Route("/Library/Refresh", "POST")] - [Api(Description = "Starts a library scan")] - public class RefreshLibrary : IReturnVoid - { - } - - [Route("/Items/{Id}", "DELETE")] - [Api(Description = "Deletes an item from the library and file system")] - public class DeleteItem : IReturnVoid - { - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")] - public string Id { get; set; } - } - - [Route("/Items/Counts", "GET")] - [Api(Description = "Gets counts of various item types")] - public class GetItemCounts : IReturn<ItemCounts> - { - [ApiMember(Name = "UserId", Description = "Optional. Get counts from a specific user's library.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")] - public Guid? UserId { get; set; } - - [ApiMember(Name = "IsFavorite", Description = "Optional. Get counts of favorite items", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")] - public bool? IsFavorite { get; set; } - } - - [Route("/Items/{Id}/Ancestors", "GET")] - [Api(Description = "Gets all parents of an item")] - public class GetAncestors : IReturn<BaseItemDto[]> - { - /// <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; } - - /// <summary> - /// Gets or sets the id. - /// </summary> - /// <value>The id.</value> - [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")] - public string Id { get; set; } - } - - [Route("/Items/YearIndex", "GET")] - [Api(Description = "Gets a year index based on an item query.")] - public class GetYearIndex : IReturn<List<ItemIndex>> - { - /// <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; } - - [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; } - } - - /// <summary> - /// Class LibraryService - /// </summary> - public class LibraryService : BaseApiService - { - /// <summary> - /// The _item repo - /// </summary> - private readonly IItemRepository _itemRepo; - - private readonly ILibraryManager _libraryManager; - private readonly IUserManager _userManager; - private readonly IUserDataManager _userDataManager; - - private readonly IDtoService _dtoService; - - /// <summary> - /// Initializes a new instance of the <see cref="LibraryService" /> class. - /// </summary> - public LibraryService(IItemRepository itemRepo, ILibraryManager libraryManager, IUserManager userManager, - IDtoService dtoService, IUserDataManager userDataManager) - { - _itemRepo = itemRepo; - _libraryManager = libraryManager; - _userManager = userManager; - _dtoService = dtoService; - _userDataManager = userDataManager; - } - - public object Get(GetFile request) - { - var item = _dtoService.GetItemByDtoId(request.Id); - var locationType = item.LocationType; - if (locationType == LocationType.Remote || locationType == LocationType.Virtual) - { - throw new ArgumentException("This command cannot be used for remote or virtual items."); - } - if (Directory.Exists(item.Path)) - { - throw new ArgumentException("This command cannot be used for directories."); - } - - return ToStaticFileResult(item.Path); - } - - public object Get(GetSubtitle request) - { - var subtitleStream = _itemRepo.GetMediaStreams(new MediaStreamQuery - { - - Index = request.Index, - ItemId = new Guid(request.Id), - Type = MediaStreamType.Subtitle - - }).FirstOrDefault(); - - if (subtitleStream == null) - { - throw new ResourceNotFoundException(); - } - - return ToStaticFileResult(subtitleStream.Path); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetAncestors request) - { - var result = GetAncestors(request); - - return ToOptimizedSerializedResultUsingCache(result); - } - - /// <summary> - /// Gets the ancestors. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>Task{BaseItemDto[]}.</returns> - public List<BaseItemDto> GetAncestors(GetAncestors request) - { - var item = _dtoService.GetItemByDtoId(request.Id); - - var baseItemDtos = new List<BaseItemDto>(); - - var user = request.UserId.HasValue ? _userManager.GetUserById(request.UserId.Value) : null; - - // Get everything - var fields = Enum.GetNames(typeof(ItemFields)) - .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)) - .ToList(); - - BaseItem parent = item.Parent; - - while (parent != null) - { - if (user != null) - { - parent = TranslateParentItem(parent, user); - } - - baseItemDtos.Add(_dtoService.GetBaseItemDto(parent, fields, user)); - - if (parent is UserRootFolder) - { - break; - } - - parent = parent.Parent; - } - - return baseItemDtos.ToList(); - } - - private BaseItem TranslateParentItem(BaseItem item, User user) - { - if (item.Parent is AggregateFolder) - { - return user.RootFolder.GetChildren(user, true).FirstOrDefault(i => i.PhysicalLocations.Contains(item.Path)); - } - - return item; - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetCriticReviews request) - { - var result = GetCriticReviews(request); - - return ToOptimizedSerializedResultUsingCache(result); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetItemCounts request) - { - var items = GetAllLibraryItems(request.UserId, _userManager, _libraryManager) - .Where(i => i.LocationType != LocationType.Virtual) - .ToList(); - - var filteredItems = request.UserId.HasValue ? FilterItems(items, request, request.UserId.Value).ToList() : items; - - var albums = filteredItems.OfType<MusicAlbum>().ToList(); - var episodes = filteredItems.OfType<Episode>().ToList(); - var games = filteredItems.OfType<Game>().ToList(); - var movies = filteredItems.OfType<Movie>().ToList(); - var musicVideos = filteredItems.OfType<MusicVideo>().ToList(); - var adultVideos = filteredItems.OfType<AdultVideo>().ToList(); - var boxsets = filteredItems.OfType<BoxSet>().ToList(); - var books = filteredItems.OfType<Book>().ToList(); - var songs = filteredItems.OfType<Audio>().ToList(); - var series = filteredItems.OfType<Series>().ToList(); - - var counts = new ItemCounts - { - AlbumCount = albums.Count, - EpisodeCount = episodes.Count, - GameCount = games.Count, - GameSystemCount = filteredItems.OfType<GameSystem>().Count(), - MovieCount = movies.Count, - SeriesCount = series.Count, - SongCount = songs.Count, - TrailerCount = filteredItems.OfType<Trailer>().Count(), - MusicVideoCount = musicVideos.Count, - AdultVideoCount = adultVideos.Count, - BoxSetCount = boxsets.Count, - BookCount = books.Count, - - UniqueTypes = items.Select(i => i.GetClientTypeName()).Distinct().ToList() - }; - - return ToOptimizedSerializedResultUsingCache(counts); - } - - private IEnumerable<T> FilterItems<T>(IEnumerable<T> items, GetItemCounts request, Guid userId) - where T : BaseItem - { - if (request.IsFavorite.HasValue) - { - var val = request.IsFavorite.Value; - - items = items.Where(i => _userDataManager.GetUserData(userId, i.GetUserDataKey()).IsFavorite == val); - } - - return items; - } - - /// <summary> - /// Posts the specified request. - /// </summary> - /// <param name="request">The request.</param> - public async void Post(RefreshLibrary request) - { - try - { - await _libraryManager.ValidateMediaLibrary(new Progress<double>(), CancellationToken.None) - .ConfigureAwait(false); - } - catch (Exception ex) - { - Logger.ErrorException("Error refreshing library", ex); - } - } - - /// <summary> - /// Deletes the specified request. - /// </summary> - /// <param name="request">The request.</param> - public void Delete(DeleteItem request) - { - var task = DeleteItem(request); - - Task.WaitAll(task); - } - - private Task DeleteItem(DeleteItem request) - { - var item = _dtoService.GetItemByDtoId(request.Id); - - return _libraryManager.DeleteItem(item); - } - - /// <summary> - /// Gets the critic reviews async. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>Task{ItemReviewsResult}.</returns> - private QueryResult<ItemReview> GetCriticReviews(GetCriticReviews request) - { - var reviews = _itemRepo.GetCriticReviews(new Guid(request.Id)); - - var reviewsArray = reviews.ToArray(); - - var result = new QueryResult<ItemReview> - { - TotalRecordCount = reviewsArray.Length - }; - - if (request.StartIndex.HasValue) - { - reviewsArray = reviewsArray.Skip(request.StartIndex.Value).ToArray(); - } - if (request.Limit.HasValue) - { - reviewsArray = reviewsArray.Take(request.Limit.Value).ToArray(); - } - - result.Items = reviewsArray; - - return result; - } - - public object Get(GetThemeMedia request) - { - var themeSongs = GetThemeSongs(new GetThemeSongs - { - InheritFromParent = request.InheritFromParent, - Id = request.Id, - UserId = request.UserId - - }); - - var themeVideos = GetThemeVideos(new GetThemeVideos - { - InheritFromParent = request.InheritFromParent, - Id = request.Id, - UserId = request.UserId - - }); - - return ToOptimizedSerializedResultUsingCache(new AllThemeMediaResult - { - ThemeSongsResult = themeSongs, - ThemeVideosResult = themeVideos, - - SoundtrackSongsResult = GetSoundtrackSongs(request.Id, request.UserId, request.InheritFromParent) - }); - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetThemeSongs request) - { - var result = GetThemeSongs(request); - - return ToOptimizedSerializedResultUsingCache(result); - } - - private ThemeMediaResult GetThemeSongs(GetThemeSongs request) - { - var user = request.UserId.HasValue ? _userManager.GetUserById(request.UserId.Value) : null; - - var item = string.IsNullOrEmpty(request.Id) - ? (request.UserId.HasValue - ? user.RootFolder - : (Folder)_libraryManager.RootFolder) - : _dtoService.GetItemByDtoId(request.Id, request.UserId); - - var originalItem = item; - - while (GetThemeSongIds(item).Count == 0 && request.InheritFromParent && item.Parent != null) - { - item = item.Parent; - } - - // Get everything - var fields = Enum.GetNames(typeof(ItemFields)) - .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)) - .ToList(); - - var themeSongIds = GetThemeSongIds(item); - - if (themeSongIds.Count == 0 && request.InheritFromParent) - { - var album = originalItem as MusicAlbum; - - if (album != null) - { - var linkedItemWithThemes = album.SoundtrackIds - .Select(i => _libraryManager.GetItemById(i)) - .FirstOrDefault(i => GetThemeSongIds(i).Count > 0); - - if (linkedItemWithThemes != null) - { - themeSongIds = GetThemeSongIds(linkedItemWithThemes); - item = linkedItemWithThemes; - } - } - } - - var dtos = themeSongIds.Select(_libraryManager.GetItemById) - .OrderBy(i => i.SortName) - .Select(i => _dtoService.GetBaseItemDto(i, fields, user, item)); - - var items = dtos.ToArray(); - - return new ThemeMediaResult - { - Items = items, - TotalRecordCount = items.Length, - OwnerId = _dtoService.GetDtoId(item) - }; - } - - /// <summary> - /// Gets the specified request. - /// </summary> - /// <param name="request">The request.</param> - /// <returns>System.Object.</returns> - public object Get(GetThemeVideos request) - { - var result = GetThemeVideos(request); - - return ToOptimizedSerializedResultUsingCache(result); - } - - public ThemeMediaResult GetThemeVideos(GetThemeVideos request) - { - var user = request.UserId.HasValue ? _userManager.GetUserById(request.UserId.Value) : null; - - var item = string.IsNullOrEmpty(request.Id) - ? (request.UserId.HasValue - ? user.RootFolder - : (Folder)_libraryManager.RootFolder) - : _dtoService.GetItemByDtoId(request.Id, request.UserId); - - var originalItem = item; - - while (GetThemeVideoIds(item).Count == 0 && request.InheritFromParent && item.Parent != null) - { - item = item.Parent; - } - - // Get everything - var fields = Enum.GetNames(typeof(ItemFields)) - .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)) - .ToList(); - - var themeVideoIds = GetThemeVideoIds(item); - - if (themeVideoIds.Count == 0 && request.InheritFromParent) - { - var album = originalItem as MusicAlbum; - - if (album == null) - { - album = originalItem.Parents.OfType<MusicAlbum>().FirstOrDefault(); - } - - if (album != null) - { - var linkedItemWithThemes = album.SoundtrackIds - .Select(i => _libraryManager.GetItemById(i)) - .FirstOrDefault(i => GetThemeVideoIds(i).Count > 0); - - if (linkedItemWithThemes != null) - { - themeVideoIds = GetThemeVideoIds(linkedItemWithThemes); - item = linkedItemWithThemes; - } - } - } - - var dtos = themeVideoIds.Select(_libraryManager.GetItemById) - .OrderBy(i => i.SortName) - .Select(i => _dtoService.GetBaseItemDto(i, fields, user, item)); - - var items = dtos.ToArray(); - - return new ThemeMediaResult - { - Items = items, - TotalRecordCount = items.Length, - OwnerId = _dtoService.GetDtoId(item) - }; - } - - private List<Guid> GetThemeVideoIds(BaseItem item) - { - var i = item as IHasThemeMedia; - - if (i != null) - { - return i.ThemeVideoIds; - } - - return new List<Guid>(); - } - - private List<Guid> GetThemeSongIds(BaseItem item) - { - var i = item as IHasThemeMedia; - - if (i != null) - { - return i.ThemeSongIds; - } - - return new List<Guid>(); - } - - private readonly CultureInfo _usCulture = new CultureInfo("en-US"); - - public object Get(GetYearIndex request) - { - IEnumerable<BaseItem> items = GetAllLibraryItems(request.UserId, _userManager, _libraryManager); - - if (!string.IsNullOrEmpty(request.IncludeItemTypes)) - { - var vals = request.IncludeItemTypes.Split(','); - items = items.Where(f => vals.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase)); - } - - var lookup = items - .ToLookup(i => i.ProductionYear ?? -1) - .OrderBy(i => i.Key) - .Select(i => new ItemIndex - { - ItemCount = i.Count(), - Name = i.Key == -1 ? string.Empty : i.Key.ToString(_usCulture) - }) - .ToList(); - - return ToOptimizedSerializedResultUsingCache(lookup); - } - - public ThemeMediaResult GetSoundtrackSongs(string id, Guid? userId, bool inheritFromParent) - { - var user = userId.HasValue ? _userManager.GetUserById(userId.Value) : null; - - var item = string.IsNullOrEmpty(id) - ? (userId.HasValue - ? user.RootFolder - : (Folder)_libraryManager.RootFolder) - : _dtoService.GetItemByDtoId(id, userId); - - // Get everything - var fields = Enum.GetNames(typeof(ItemFields)) - .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)) - .ToList(); - - var dtos = GetSoundtrackSongIds(item, inheritFromParent) - .Select(_libraryManager.GetItemById) - .OfType<MusicAlbum>() - .SelectMany(i => i.RecursiveChildren) - .OfType<Audio>() - .OrderBy(i => i.SortName) - .Select(i => _dtoService.GetBaseItemDto(i, fields, user, item)); - - var items = dtos.ToArray(); - - return new ThemeMediaResult - { - Items = items, - TotalRecordCount = items.Length, - OwnerId = _dtoService.GetDtoId(item) - }; - } - - private IEnumerable<Guid> GetSoundtrackSongIds(BaseItem item, bool inherit) - { - var hasSoundtracks = item as IHasSoundtracks; - - if (hasSoundtracks != null) - { - return hasSoundtracks.SoundtrackIds; - } - - if (!inherit) - { - return null; - } - - hasSoundtracks = item.Parents.OfType<IHasSoundtracks>().FirstOrDefault(); - - return hasSoundtracks != null ? hasSoundtracks.SoundtrackIds : new List<Guid>(); - } - } -} diff --git a/MediaBrowser.Api/MediaBrowser.Api.csproj b/MediaBrowser.Api/MediaBrowser.Api.csproj index f8d0ed125..c95df7db3 100644 --- a/MediaBrowser.Api/MediaBrowser.Api.csproj +++ b/MediaBrowser.Api/MediaBrowser.Api.csproj @@ -83,10 +83,9 @@ <Compile Include="InstantMixService.cs" /> <Compile Include="ItemRefreshService.cs" /> <Compile Include="ItemUpdateService.cs" /> - <Compile Include="LibraryService.cs" /> + <Compile Include="Library\LibraryService.cs" /> <Compile Include="Library\FileOrganizationService.cs" /> <Compile Include="Library\LibraryHelpers.cs" /> - <Compile Include="Library\LibraryService.cs" /> <Compile Include="Library\LibraryStructureService.cs" /> <Compile Include="LiveTv\LiveTvService.cs" /> <Compile Include="LocalizationService.cs" /> diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index b9b6dd517..6472b988a 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -277,6 +277,19 @@ namespace MediaBrowser.Controller.Entities get { return GetRecursiveChildren(); } } + public override bool IsVisible(User user) + { + if (this is ICollectionFolder) + { + if (user.Configuration.BlockedMediaFolders.Contains(Name, StringComparer.OrdinalIgnoreCase)) + { + return false; + } + } + + return base.IsVisible(user); + } + private List<BaseItem> LoadChildrenInternal() { return LoadChildren().ToList(); @@ -762,15 +775,15 @@ namespace MediaBrowser.Controller.Entities { list.Add(child); } - } - - if (recursive && child.IsFolder) - { - var folder = (Folder)child; - if (folder.AddChildrenToList(user, includeLinkedChildren, list, true, filter)) + if (recursive && child.IsFolder) { - hasLinkedChildren = true; + var folder = (Folder)child; + + if (folder.AddChildrenToList(user, includeLinkedChildren, list, true, filter)) + { + hasLinkedChildren = true; + } } } } diff --git a/MediaBrowser.Controller/Entities/User.cs b/MediaBrowser.Controller/Entities/User.cs index 66ef8c7dc..d5fc3172c 100644 --- a/MediaBrowser.Controller/Entities/User.cs +++ b/MediaBrowser.Controller/Entities/User.cs @@ -20,35 +20,6 @@ namespace MediaBrowser.Controller.Entities public static IXmlSerializer XmlSerializer { get; set; } /// <summary> - /// Gets the root folder path. - /// </summary> - /// <value>The root folder path.</value> - [IgnoreDataMember] - public string RootFolderPath - { - get - { - var path = Configuration.UseCustomLibrary ? GetRootFolderPath(Name) : ConfigurationManager.ApplicationPaths.DefaultUserViewsPath; - - Directory.CreateDirectory(path); - - return path; - } - } - - /// <summary> - /// Gets the root folder path based on a given username - /// </summary> - /// <param name="username">The username.</param> - /// <returns>System.String.</returns> - private string GetRootFolderPath(string username) - { - var safeFolderName = FileSystem.GetValidFilename(username); - - return System.IO.Path.Combine(ConfigurationManager.ApplicationPaths.RootFolderPath, safeFolderName); - } - - /// <summary> /// Gets or sets the password. /// </summary> /// <value>The password.</value> @@ -98,23 +69,15 @@ namespace MediaBrowser.Controller.Entities } /// <summary> - /// The _root folder - /// </summary> - private UserRootFolder _rootFolder; - /// <summary> /// Gets the root folder. /// </summary> /// <value>The root folder.</value> [IgnoreDataMember] - public UserRootFolder RootFolder + public Folder RootFolder { get { - return _rootFolder ?? (LibraryManager.GetUserRootFolder(RootFolderPath)); - } - private set - { - _rootFolder = value; + return LibraryManager.GetUserRootFolder(); } } @@ -166,22 +129,6 @@ namespace MediaBrowser.Controller.Entities } /// <summary> - /// Reloads the root media folder - /// </summary> - /// <param name="cancellationToken">The cancellation token.</param> - /// <param name="progress">The progress.</param> - /// <returns>Task.</returns> - public async Task ValidateMediaLibrary(IProgress<double> progress, CancellationToken cancellationToken) - { - Logger.Info("Validating media library for {0}", Name); - await RootFolder.RefreshMetadata(cancellationToken).ConfigureAwait(false); - - cancellationToken.ThrowIfCancellationRequested(); - - await RootFolder.ValidateChildren(progress, cancellationToken).ConfigureAwait(false); - } - - /// <summary> /// Renames the user. /// </summary> /// <param name="newName">The new name.</param> @@ -215,29 +162,10 @@ namespace MediaBrowser.Controller.Entities { Directory.CreateDirectory(newConfigDirectory); } - - var customLibraryPath = GetRootFolderPath(Name); - - // Move the root folder path if using a custom library - if (Directory.Exists(customLibraryPath)) - { - var newRootFolderPath = GetRootFolderPath(newName); - if (Directory.Exists(newRootFolderPath)) - { - Directory.Delete(newRootFolderPath, true); - } - Directory.Move(customLibraryPath, newRootFolderPath); - } } Name = newName; - // Force these to be lazy loaded again - RootFolder = null; - - // Kick off a task to validate the media library - Task.Run(() => ValidateMediaLibrary(new Progress<double>(), CancellationToken.None)); - return RefreshMetadata(new MetadataRefreshOptions { ReplaceAllMetadata = true, @@ -318,16 +246,8 @@ namespace MediaBrowser.Controller.Entities throw new ArgumentNullException("config"); } - var customLibraryChanged = config.UseCustomLibrary != Configuration.UseCustomLibrary; - Configuration = config; SaveConfiguration(serializer); - - // Force these to be lazy loaded again - if (customLibraryChanged) - { - RootFolder = null; - } } } } diff --git a/MediaBrowser.Controller/Entities/UserRootFolder.cs b/MediaBrowser.Controller/Entities/UserRootFolder.cs index 9a8d3e7f4..1829e10c7 100644 --- a/MediaBrowser.Controller/Entities/UserRootFolder.cs +++ b/MediaBrowser.Controller/Entities/UserRootFolder.cs @@ -25,7 +25,7 @@ namespace MediaBrowser.Controller.Entities if (string.Equals("default", Name, System.StringComparison.OrdinalIgnoreCase)) { - Name = "Default Media Library"; + Name = "Media Folders"; hasChanges = true; } diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 0b1ac9137..747ae48ad 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -202,9 +202,8 @@ namespace MediaBrowser.Controller.Library /// <summary> /// Gets the user root folder. /// </summary> - /// <param name="userRootPath">The user root path.</param> /// <returns>UserRootFolder.</returns> - UserRootFolder GetUserRootFolder(string userRootPath); + Folder GetUserRootFolder(); /// <summary> /// Creates the item. diff --git a/MediaBrowser.Model/Configuration/UserConfiguration.cs b/MediaBrowser.Model/Configuration/UserConfiguration.cs index c306c7775..fa2554e05 100644 --- a/MediaBrowser.Model/Configuration/UserConfiguration.cs +++ b/MediaBrowser.Model/Configuration/UserConfiguration.cs @@ -17,12 +17,6 @@ namespace MediaBrowser.Model.Configuration /// </summary> /// <value><c>true</c> if items with no rating info should be blocked; otherwise, <c>false</c>.</value> public bool BlockNotRated { get; set; } - - /// <summary> - /// Gets or sets a value indicating whether [use custom library]. - /// </summary> - /// <value><c>true</c> if [use custom library]; otherwise, <c>false</c>.</value> - public bool UseCustomLibrary { get; set; } /// <summary> /// Gets or sets a value indicating whether this instance is administrator. @@ -71,7 +65,9 @@ namespace MediaBrowser.Model.Configuration public bool EnableLiveTvAccess { get; set; } public bool EnableMediaPlayback { get; set; } - + + public string[] BlockedMediaFolders { get; set; } + /// <summary> /// Initializes a new instance of the <see cref="UserConfiguration" /> class. /// </summary> @@ -84,6 +80,8 @@ namespace MediaBrowser.Model.Configuration EnableLiveTvManagement = true; EnableMediaPlayback = true; EnableLiveTvAccess = true; + + BlockedMediaFolders = new string[] { }; } } } diff --git a/MediaBrowser.Providers/TV/EpisodeXmlParser.cs b/MediaBrowser.Providers/TV/EpisodeXmlParser.cs index ee78c3777..2b2d607ca 100644 --- a/MediaBrowser.Providers/TV/EpisodeXmlParser.cs +++ b/MediaBrowser.Providers/TV/EpisodeXmlParser.cs @@ -23,9 +23,12 @@ namespace MediaBrowser.Providers.TV { } + private string _xmlPath; + public void Fetch(Episode item, List<LocalImageInfo> images, string metadataFile, CancellationToken cancellationToken) { _imagesFound = images; + _xmlPath = metadataFile; Fetch(item, metadataFile, cancellationToken); } @@ -75,8 +78,8 @@ namespace MediaBrowser.Providers.TV // even though it's actually using the metadata folder. filename = Path.GetFileName(filename); - var parentFolder = Path.GetDirectoryName(item.Path); - filename = Path.Combine(parentFolder, "metadata", filename); + var parentFolder = Path.GetDirectoryName(_xmlPath); + filename = Path.Combine(parentFolder, filename); var file = new FileInfo(filename); if (file.Exists) diff --git a/MediaBrowser.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/MediaBrowser.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index 2928363e3..36a8d3526 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/MediaBrowser.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -283,26 +283,6 @@ namespace MediaBrowser.Server.Implementations.EntryPoints return new[] { user.RootFolder as T }; } - // Need to find what user collection folder this belongs to - if (item.Parent is AggregateFolder) - { - if (item.LocationType == LocationType.FileSystem) - { - return collections.Where(i => i.PhysicalLocations.Contains(item.Path)).Cast<T>(); - } - } - - // If it's a user root, return it only if it's the right one - if (item is UserRootFolder) - { - if (item.Id == user.RootFolder.Id) - { - return new[] { item }; - } - - return new T[] { }; - } - // Return it only if it's in the user's library if (includeIfNotFound || allRecursiveChildren.ContainsKey(item.Id)) { diff --git a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs index c2044476a..5bc1ff45a 100644 --- a/MediaBrowser.Server.Implementations/Library/LibraryManager.cs +++ b/MediaBrowser.Server.Implementations/Library/LibraryManager.cs @@ -153,12 +153,6 @@ namespace MediaBrowser.Server.Implementations.Library } } - /// <summary> - /// The _user root folders - /// </summary> - private readonly ConcurrentDictionary<string, UserRootFolder> _userRootFolders = - new ConcurrentDictionary<string, UserRootFolder>(); - private readonly IFileSystem _fileSystem; /// <summary> @@ -586,7 +580,7 @@ namespace MediaBrowser.Server.Implementations.Library var flattenFolderDepth = isPhysicalRoot ? 2 : 0; var fileSystemDictionary = FileData.GetFilteredFileSystemEntries(directoryService, args.Path, _fileSystem, _logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf); - + // Need to remove subpaths that may have been resolved from shortcuts // Example: if \\server\movies exists, then strip out \\server\movies\action if (isPhysicalRoot) @@ -701,20 +695,18 @@ namespace MediaBrowser.Server.Implementations.Library return rootFolder; } - /// <summary> - /// Gets the user root folder. - /// </summary> - /// <param name="userRootPath">The user root path.</param> - /// <returns>UserRootFolder.</returns> - public UserRootFolder GetUserRootFolder(string userRootPath) + private UserRootFolder _userRootFolder; + public Folder GetUserRootFolder() { - return _userRootFolders.GetOrAdd(userRootPath, key => RetrieveItem(userRootPath.GetMBId(typeof(UserRootFolder))) as UserRootFolder ?? - (UserRootFolder)ResolvePath(new DirectoryInfo(userRootPath))); - } + if (_userRootFolder == null) + { + var userRootPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath; - public Person GetPersonSync(string name) - { - return GetItemByName<Person>(ConfigurationManager.ApplicationPaths.PeoplePath, name); + _userRootFolder = RetrieveItem(userRootPath.GetMBId(typeof(UserRootFolder))) as UserRootFolder ?? + (UserRootFolder)ResolvePath(new DirectoryInfo(userRootPath)); + } + + return _userRootFolder; } /// <summary> @@ -1001,7 +993,7 @@ namespace MediaBrowser.Server.Implementations.Library // Just run the scheduled task so that the user can see it _taskManager.QueueScheduledTask<RefreshMediaLibraryTask>(); } - + /// <summary> /// Validates the media library internal. /// </summary> @@ -1035,19 +1027,15 @@ namespace MediaBrowser.Server.Implementations.Library progress.Report(1); - foreach (var folder in _userManager.Users.Select(u => u.RootFolder).Distinct()) - { - await ValidateCollectionFolders(folder, cancellationToken).ConfigureAwait(false); - } + var userRoot = GetUserRootFolder(); + await userRoot.RefreshMetadata(cancellationToken).ConfigureAwait(false); + + await userRoot.ValidateChildren(new Progress<double>(), cancellationToken, new MetadataRefreshOptions(), recursive: false).ConfigureAwait(false); progress.Report(2); var innerProgress = new ActionableProgress<double>(); - innerProgress.RegisterAction(pct => progress.Report(2 + pct * .13)); - - innerProgress = new ActionableProgress<double>(); - innerProgress.RegisterAction(pct => progress.Report(2 + pct * .73)); // Now validate the entire media library @@ -1119,22 +1107,6 @@ namespace MediaBrowser.Server.Implementations.Library } /// <summary> - /// Validates only the collection folders for a User and goes no further - /// </summary> - /// <param name="userRootFolder">The user root folder.</param> - /// <param name="cancellationToken">The cancellation token.</param> - /// <returns>Task.</returns> - private async Task ValidateCollectionFolders(UserRootFolder userRootFolder, CancellationToken cancellationToken) - { - _logger.Info("Validating collection folders within {0}", userRootFolder.Path); - await userRootFolder.RefreshMetadata(cancellationToken).ConfigureAwait(false); - - cancellationToken.ThrowIfCancellationRequested(); - - await userRootFolder.ValidateChildren(new Progress<double>(), cancellationToken, new MetadataRefreshOptions(), recursive: false).ConfigureAwait(false); - } - - /// <summary> /// Gets the default view. /// </summary> /// <returns>IEnumerable{VirtualFolderInfo}.</returns> @@ -1150,7 +1122,7 @@ namespace MediaBrowser.Server.Implementations.Library /// <returns>IEnumerable{VirtualFolderInfo}.</returns> public IEnumerable<VirtualFolderInfo> GetVirtualFolders(User user) { - return GetView(user.RootFolderPath); + return GetDefaultVirtualFolders(); } /// <summary> @@ -1399,7 +1371,7 @@ namespace MediaBrowser.Server.Implementations.Library { await _providerManagerFactory().SaveMetadata(item, updateReason).ConfigureAwait(false); } - + item.DateLastSaved = DateTime.UtcNow; _logger.Debug("Saving {0} to database.", item.Path ?? item.Name); diff --git a/MediaBrowser.Server.Implementations/Library/UserManager.cs b/MediaBrowser.Server.Implementations/Library/UserManager.cs index ce76dd21b..8654a26ce 100644 --- a/MediaBrowser.Server.Implementations/Library/UserManager.cs +++ b/MediaBrowser.Server.Implementations/Library/UserManager.cs @@ -332,29 +332,15 @@ namespace MediaBrowser.Server.Implementations.Library await UserRepository.DeleteUser(user, CancellationToken.None).ConfigureAwait(false); - if (user.Configuration.UseCustomLibrary) + var path = user.ConfigurationFilePath; + + try + { + File.Delete(path); + } + catch (IOException ex) { - var path = user.RootFolderPath; - - try - { - Directory.Delete(path, true); - } - catch (IOException ex) - { - _logger.ErrorException("Error deleting directory {0}", ex, path); - } - - path = user.ConfigurationFilePath; - - try - { - File.Delete(path); - } - catch (IOException ex) - { - _logger.ErrorException("Error deleting file {0}", ex, path); - } + _logger.ErrorException("Error deleting file {0}", ex, path); } // Force this to be lazy loaded again diff --git a/MediaBrowser.ServerApplication/ApplicationHost.cs b/MediaBrowser.ServerApplication/ApplicationHost.cs index 85aeeb129..895a8d9b9 100644 --- a/MediaBrowser.ServerApplication/ApplicationHost.cs +++ b/MediaBrowser.ServerApplication/ApplicationHost.cs @@ -254,6 +254,14 @@ namespace MediaBrowser.ServerApplication { try { + MigrateUserFolders(); + } + catch (IOException ex) + { + } + + try + { File.Delete(Path.Combine(ApplicationPaths.PluginsPath, "MBPhoto.dll")); } catch (IOException) @@ -319,6 +327,35 @@ namespace MediaBrowser.ServerApplication }); } + private void MigrateUserFolders() + { + var rootPath = ApplicationPaths.RootFolderPath; + + var folders = new DirectoryInfo(rootPath).EnumerateDirectories("*", SearchOption.TopDirectoryOnly).Where(i => !string.Equals(i.Name, "default", StringComparison.OrdinalIgnoreCase)) + .ToList(); + + foreach (var folder in folders) + { + MigrateUserFolder(folder); + } + } + + private void MigrateUserFolder(DirectoryInfo folder) + { + var foldersInDefault = new DirectoryInfo(ApplicationPaths.DefaultUserViewsPath).EnumerateDirectories("*", SearchOption.TopDirectoryOnly).ToList(); + + var foldersInUserView = folder.EnumerateDirectories("*", SearchOption.TopDirectoryOnly).ToList(); + + var foldersToMove = foldersInUserView.Where(i => !foldersInDefault.Any(f => string.Equals(f.Name, i.Name, StringComparison.OrdinalIgnoreCase))).ToList(); + + foreach (var folderToMove in foldersToMove) + { + folderToMove.MoveTo(Path.Combine(ApplicationPaths.DefaultUserViewsPath, folderToMove.Name)); + } + + Directory.Delete(folder.FullName, true); + } + /// <summary> /// Registers resources that classes will depend on /// </summary> diff --git a/MediaBrowser.WebDashboard/ApiClient.js b/MediaBrowser.WebDashboard/ApiClient.js index 375aacbce..97c26ec78 100644 --- a/MediaBrowser.WebDashboard/ApiClient.js +++ b/MediaBrowser.WebDashboard/ApiClient.js @@ -1300,7 +1300,7 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi }; /** - * Gets the virtual folder for a view. Specify a userId to get a user view, or omit for the default view. + * Gets the virtual folder list */ self.getVirtualFolders = function (userId) { @@ -1477,16 +1477,16 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi }; /** - * Removes a virtual folder from either the default view or a user view + * Removes a virtual folder * @param {String} name */ - self.removeVirtualFolder = function (name, userId, refreshLibrary) { + self.removeVirtualFolder = function (name, refreshLibrary) { if (!name) { throw new Error("null name"); } - var url = userId ? "Users/" + userId + "/VirtualFolders" : "Library/VirtualFolders"; + var url = "Library/VirtualFolders"; url = self.getUrl(url, { refreshLibrary: refreshLibrary ? true : false, @@ -1500,10 +1500,10 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi }; /** - * Adds a virtual folder to either the default view or a user view + * Adds a virtual folder * @param {String} name */ - self.addVirtualFolder = function (name, type, userId, refreshLibrary) { + self.addVirtualFolder = function (name, type, refreshLibrary) { if (!name) { throw new Error("null name"); @@ -1518,7 +1518,7 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi options.refreshLibrary = refreshLibrary ? true : false; options.name = name; - var url = userId ? "Users/" + userId + "/VirtualFolders" : "Library/VirtualFolders"; + var url = "Library/VirtualFolders"; url = self.getUrl(url, options); @@ -1529,18 +1529,16 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi }; /** - * Renames a virtual folder, within either the default view or a user view + * Renames a virtual folder * @param {String} name */ - self.renameVirtualFolder = function (name, newName, userId, refreshLibrary) { + self.renameVirtualFolder = function (name, newName, refreshLibrary) { if (!name) { throw new Error("null name"); } - var url = userId ? "Users/" + userId + "/VirtualFolders" : "Library/VirtualFolders"; - - url += "/Name"; + var url = "Library/VirtualFolders/Name"; url = self.getUrl(url, { refreshLibrary: refreshLibrary ? true : false, @@ -1555,10 +1553,10 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi }; /** - * Adds an additional mediaPath to an existing virtual folder, within either the default view or a user view + * Adds an additional mediaPath to an existing virtual folder * @param {String} name */ - self.addMediaPath = function (virtualFolderName, mediaPath, userId, refreshLibrary) { + self.addMediaPath = function (virtualFolderName, mediaPath, refreshLibrary) { if (!virtualFolderName) { throw new Error("null virtualFolderName"); @@ -1568,9 +1566,7 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi throw new Error("null mediaPath"); } - var url = userId ? "Users/" + userId + "/VirtualFolders" : "Library/VirtualFolders"; - - url += "/Paths"; + var url = "Library/VirtualFolders/Paths"; url = self.getUrl(url, { refreshLibrary: refreshLibrary ? true : false, @@ -1585,10 +1581,10 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi }; /** - * Removes a media path from a virtual folder, within either the default view or a user view + * Removes a media path from a virtual folder * @param {String} name */ - self.removeMediaPath = function (virtualFolderName, mediaPath, userId, refreshLibrary) { + self.removeMediaPath = function (virtualFolderName, mediaPath, refreshLibrary) { if (!virtualFolderName) { throw new Error("null virtualFolderName"); @@ -1598,9 +1594,7 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi throw new Error("null mediaPath"); } - var url = userId ? "Users/" + userId + "/VirtualFolders" : "Library/VirtualFolders"; - - url += "/Paths"; + var url = "Library/VirtualFolders/Paths"; url = self.getUrl(url, { refreshLibrary: refreshLibrary ? true : false, diff --git a/MediaBrowser.WebDashboard/packages.config b/MediaBrowser.WebDashboard/packages.config index 4957f3563..0bdea523e 100644 --- a/MediaBrowser.WebDashboard/packages.config +++ b/MediaBrowser.WebDashboard/packages.config @@ -1,4 +1,4 @@ <?xml version="1.0" encoding="utf-8"?> <packages> - <package id="MediaBrowser.ApiClient.Javascript" version="3.0.244" targetFramework="net45" /> + <package id="MediaBrowser.ApiClient.Javascript" version="3.0.245" targetFramework="net45" /> </packages>
\ No newline at end of file |
