Browse Source

easier user library setup

Luke Pulverenti 11 years ago
parent
commit
7cd41a6ed6

+ 5 - 8
MediaBrowser.Api/Library/LibraryHelpers.cs

@@ -1,6 +1,5 @@
 using MediaBrowser.Common.IO;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller;
-using MediaBrowser.Controller.Entities;
 using System;
 using System;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
@@ -27,12 +26,11 @@ namespace MediaBrowser.Api.Library
         /// <param name="fileSystem">The file system.</param>
         /// <param name="fileSystem">The file system.</param>
         /// <param name="virtualFolderName">Name of the virtual folder.</param>
         /// <param name="virtualFolderName">Name of the virtual folder.</param>
         /// <param name="mediaPath">The media path.</param>
         /// <param name="mediaPath">The media path.</param>
-        /// <param name="user">The user.</param>
         /// <param name="appPaths">The app paths.</param>
         /// <param name="appPaths">The app paths.</param>
         /// <exception cref="System.IO.DirectoryNotFoundException">The media folder does not exist</exception>
         /// <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);
             var path = Path.Combine(rootFolderPath, virtualFolderName);
 
 
             if (!Directory.Exists(path))
             if (!Directory.Exists(path))
@@ -54,18 +52,17 @@ namespace MediaBrowser.Api.Library
         /// <param name="fileSystem">The file system.</param>
         /// <param name="fileSystem">The file system.</param>
         /// <param name="virtualFolderName">Name of the virtual folder.</param>
         /// <param name="virtualFolderName">Name of the virtual folder.</param>
         /// <param name="path">The path.</param>
         /// <param name="path">The path.</param>
-        /// <param name="user">The user.</param>
         /// <param name="appPaths">The app paths.</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>
         /// <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))
             if (!Directory.Exists(path))
             {
             {
                 throw new DirectoryNotFoundException("The path does not exist.");
                 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 virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
 
 
             var shortcutFilename = Path.GetFileNameWithoutExtension(path);
             var shortcutFilename = Path.GetFileNameWithoutExtension(path);

+ 738 - 11
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.Library;
+using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Querying;
 using ServiceStack;
 using ServiceStack;
 using System;
 using System;
+using System.Collections;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
 using System.Linq;
 using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
 
 
 namespace MediaBrowser.Api.Library
 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>
     /// <summary>
     /// Class GetPhyscialPaths
     /// Class GetPhyscialPaths
     /// </summary>
     /// </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>
     /// <summary>
     /// Class LibraryService
     /// Class LibraryService
     /// </summary>
     /// </summary>
     public class LibraryService : BaseApiService
     public class LibraryService : BaseApiService
     {
     {
         /// <summary>
         /// <summary>
-        /// The _app host
+        /// The _item repo
         /// </summary>
         /// </summary>
-        private readonly IApplicationHost _appHost;
+        private readonly IItemRepository _itemRepo;
+
         private readonly ILibraryManager _libraryManager;
         private readonly ILibraryManager _libraryManager;
+        private readonly IUserManager _userManager;
+        private readonly IUserDataManager _userDataManager;
+
+        private readonly IDtoService _dtoService;
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="LibraryService" /> class.
         /// Initializes a new instance of the <see cref="LibraryService" /> class.
         /// </summary>
         /// </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>
         /// <summary>
@@ -57,5 +323,466 @@ namespace MediaBrowser.Api.Library
 
 
             return ToOptimizedSerializedResultUsingCache(result);
             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>();
+        }
     }
     }
 }
 }

+ 5 - 91
MediaBrowser.Api/Library/LibraryStructureService.cs

@@ -28,15 +28,8 @@ namespace MediaBrowser.Api.Library
     }
     }
 
 
     [Route("/Library/VirtualFolders", "POST")]
     [Route("/Library/VirtualFolders", "POST")]
-    [Route("/Users/{UserId}/VirtualFolders", "POST")]
     public class AddVirtualFolder : IReturnVoid
     public class AddVirtualFolder : IReturnVoid
     {
     {
-        /// <summary>
-        /// Gets or sets the user id.
-        /// </summary>
-        /// <value>The user id.</value>
-        public string UserId { get; set; }
-
         /// <summary>
         /// <summary>
         /// Gets or sets the name.
         /// Gets or sets the name.
         /// </summary>
         /// </summary>
@@ -57,15 +50,8 @@ namespace MediaBrowser.Api.Library
     }
     }
 
 
     [Route("/Library/VirtualFolders", "DELETE")]
     [Route("/Library/VirtualFolders", "DELETE")]
-    [Route("/Users/{UserId}/VirtualFolders", "DELETE")]
     public class RemoveVirtualFolder : IReturnVoid
     public class RemoveVirtualFolder : IReturnVoid
     {
     {
-        /// <summary>
-        /// Gets or sets the user id.
-        /// </summary>
-        /// <value>The user id.</value>
-        public string UserId { get; set; }
-
         /// <summary>
         /// <summary>
         /// Gets or sets the name.
         /// Gets or sets the name.
         /// </summary>
         /// </summary>
@@ -80,15 +66,8 @@ namespace MediaBrowser.Api.Library
     }
     }
 
 
     [Route("/Library/VirtualFolders/Name", "POST")]
     [Route("/Library/VirtualFolders/Name", "POST")]
-    [Route("/Users/{UserId}/VirtualFolders/Name", "POST")]
     public class RenameVirtualFolder : IReturnVoid
     public class RenameVirtualFolder : IReturnVoid
     {
     {
-        /// <summary>
-        /// Gets or sets the user id.
-        /// </summary>
-        /// <value>The user id.</value>
-        public string UserId { get; set; }
-
         /// <summary>
         /// <summary>
         /// Gets or sets the name.
         /// Gets or sets the name.
         /// </summary>
         /// </summary>
@@ -109,15 +88,8 @@ namespace MediaBrowser.Api.Library
     }
     }
 
 
     [Route("/Library/VirtualFolders/Paths", "POST")]
     [Route("/Library/VirtualFolders/Paths", "POST")]
-    [Route("/Users/{UserId}/VirtualFolders/Paths", "POST")]
     public class AddMediaPath : IReturnVoid
     public class AddMediaPath : IReturnVoid
     {
     {
-        /// <summary>
-        /// Gets or sets the user id.
-        /// </summary>
-        /// <value>The user id.</value>
-        public string UserId { get; set; }
-
         /// <summary>
         /// <summary>
         /// Gets or sets the name.
         /// Gets or sets the name.
         /// </summary>
         /// </summary>
@@ -138,15 +110,8 @@ namespace MediaBrowser.Api.Library
     }
     }
 
 
     [Route("/Library/VirtualFolders/Paths", "DELETE")]
     [Route("/Library/VirtualFolders/Paths", "DELETE")]
-    [Route("/Users/{UserId}/VirtualFolders/Paths", "DELETE")]
     public class RemoveMediaPath : IReturnVoid
     public class RemoveMediaPath : IReturnVoid
     {
     {
-        /// <summary>
-        /// Gets or sets the user id.
-        /// </summary>
-        /// <value>The user id.</value>
-        public string UserId { get; set; }
-
         /// <summary>
         /// <summary>
         /// Gets or sets the name.
         /// Gets or sets the name.
         /// </summary>
         /// </summary>
@@ -275,18 +240,7 @@ namespace MediaBrowser.Api.Library
 
 
             var name = _fileSystem.GetValidFilename(request.Name);
             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);
             var virtualFolderPath = Path.Combine(rootFolderPath, name);
 
 
@@ -344,18 +298,7 @@ namespace MediaBrowser.Api.Library
                 throw new ArgumentNullException("request");
                 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 currentPath = Path.Combine(rootFolderPath, request.Name);
             var newPath = Path.Combine(rootFolderPath, request.NewName);
             var newPath = Path.Combine(rootFolderPath, request.NewName);
@@ -417,18 +360,7 @@ namespace MediaBrowser.Api.Library
                 throw new ArgumentNullException("request");
                 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);
             var path = Path.Combine(rootFolderPath, request.Name);
 
 
@@ -478,16 +410,7 @@ namespace MediaBrowser.Api.Library
 
 
             try
             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
                 // Need to add a delay here or directory watchers may still pick up the changes
                 var task = Task.Delay(1000);
                 var task = Task.Delay(1000);
@@ -524,16 +447,7 @@ namespace MediaBrowser.Api.Library
 
 
             try
             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
                 // Need to add a delay here or directory watchers may still pick up the changes
                 var task = Task.Delay(1000);
                 var task = Task.Delay(1000);

+ 0 - 744
MediaBrowser.Api/LibraryService.cs

@@ -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>();
-        }
-    }
-}

+ 1 - 2
MediaBrowser.Api/MediaBrowser.Api.csproj

@@ -83,10 +83,9 @@
     <Compile Include="InstantMixService.cs" />
     <Compile Include="InstantMixService.cs" />
     <Compile Include="ItemRefreshService.cs" />
     <Compile Include="ItemRefreshService.cs" />
     <Compile Include="ItemUpdateService.cs" />
     <Compile Include="ItemUpdateService.cs" />
-    <Compile Include="LibraryService.cs" />
+    <Compile Include="Library\LibraryService.cs" />
     <Compile Include="Library\FileOrganizationService.cs" />
     <Compile Include="Library\FileOrganizationService.cs" />
     <Compile Include="Library\LibraryHelpers.cs" />
     <Compile Include="Library\LibraryHelpers.cs" />
-    <Compile Include="Library\LibraryService.cs" />
     <Compile Include="Library\LibraryStructureService.cs" />
     <Compile Include="Library\LibraryStructureService.cs" />
     <Compile Include="LiveTv\LiveTvService.cs" />
     <Compile Include="LiveTv\LiveTvService.cs" />
     <Compile Include="LocalizationService.cs" />
     <Compile Include="LocalizationService.cs" />

+ 20 - 7
MediaBrowser.Controller/Entities/Folder.cs

@@ -277,6 +277,19 @@ namespace MediaBrowser.Controller.Entities
             get { return GetRecursiveChildren(); }
             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()
         private List<BaseItem> LoadChildrenInternal()
         {
         {
             return LoadChildren().ToList();
             return LoadChildren().ToList();
@@ -762,15 +775,15 @@ namespace MediaBrowser.Controller.Entities
                     {
                     {
                         list.Add(child);
                         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;
+                        }
                     }
                     }
                 }
                 }
             }
             }

+ 2 - 82
MediaBrowser.Controller/Entities/User.cs

@@ -19,35 +19,6 @@ namespace MediaBrowser.Controller.Entities
         public static IUserManager UserManager { get; set; }
         public static IUserManager UserManager { get; set; }
         public static IXmlSerializer XmlSerializer { get; set; }
         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>
         /// <summary>
         /// Gets or sets the password.
         /// Gets or sets the password.
         /// </summary>
         /// </summary>
@@ -97,24 +68,16 @@ namespace MediaBrowser.Controller.Entities
             }
             }
         }
         }
 
 
-        /// <summary>
-        /// The _root folder
-        /// </summary>
-        private UserRootFolder _rootFolder;
         /// <summary>
         /// <summary>
         /// Gets the root folder.
         /// Gets the root folder.
         /// </summary>
         /// </summary>
         /// <value>The root folder.</value>
         /// <value>The root folder.</value>
         [IgnoreDataMember]
         [IgnoreDataMember]
-        public UserRootFolder RootFolder
+        public Folder RootFolder
         {
         {
             get
             get
             {
             {
-                return _rootFolder ?? (LibraryManager.GetUserRootFolder(RootFolderPath));
-            }
-            private set
-            {
-                _rootFolder = value;
+                return LibraryManager.GetUserRootFolder();
             }
             }
         }
         }
 
 
@@ -165,22 +128,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>
         /// <summary>
         /// Renames the user.
         /// Renames the user.
         /// </summary>
         /// </summary>
@@ -215,29 +162,10 @@ namespace MediaBrowser.Controller.Entities
                 {
                 {
                     Directory.CreateDirectory(newConfigDirectory);
                     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;
             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
             return RefreshMetadata(new MetadataRefreshOptions
             {
             {
                 ReplaceAllMetadata = true,
                 ReplaceAllMetadata = true,
@@ -318,16 +246,8 @@ namespace MediaBrowser.Controller.Entities
                 throw new ArgumentNullException("config");
                 throw new ArgumentNullException("config");
             }
             }
 
 
-            var customLibraryChanged = config.UseCustomLibrary != Configuration.UseCustomLibrary;
-
             Configuration = config;
             Configuration = config;
             SaveConfiguration(serializer);
             SaveConfiguration(serializer);
-
-            // Force these to be lazy loaded again
-            if (customLibraryChanged)
-            {
-                RootFolder = null;
-            }
         }
         }
     }
     }
 }
 }

+ 1 - 1
MediaBrowser.Controller/Entities/UserRootFolder.cs

@@ -25,7 +25,7 @@ namespace MediaBrowser.Controller.Entities
 
 
             if (string.Equals("default", Name, System.StringComparison.OrdinalIgnoreCase))
             if (string.Equals("default", Name, System.StringComparison.OrdinalIgnoreCase))
             {
             {
-                Name = "Default Media Library";
+                Name = "Media Folders";
                 hasChanges = true;
                 hasChanges = true;
             }
             }
 
 

+ 1 - 2
MediaBrowser.Controller/Library/ILibraryManager.cs

@@ -202,9 +202,8 @@ namespace MediaBrowser.Controller.Library
         /// <summary>
         /// <summary>
         /// Gets the user root folder.
         /// Gets the user root folder.
         /// </summary>
         /// </summary>
-        /// <param name="userRootPath">The user root path.</param>
         /// <returns>UserRootFolder.</returns>
         /// <returns>UserRootFolder.</returns>
-        UserRootFolder GetUserRootFolder(string userRootPath);
+        Folder GetUserRootFolder();
 
 
         /// <summary>
         /// <summary>
         /// Creates the item.
         /// Creates the item.

+ 5 - 7
MediaBrowser.Model/Configuration/UserConfiguration.cs

@@ -17,12 +17,6 @@ namespace MediaBrowser.Model.Configuration
         /// </summary>
         /// </summary>
         /// <value><c>true</c> if items with no rating info should be blocked; otherwise, <c>false</c>.</value>
         /// <value><c>true</c> if items with no rating info should be blocked; otherwise, <c>false</c>.</value>
         public bool BlockNotRated { get; set; }
         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>
         /// <summary>
         /// Gets or sets a value indicating whether this instance is administrator.
         /// 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 EnableLiveTvAccess { get; set; }
 
 
         public bool EnableMediaPlayback { get; set; }
         public bool EnableMediaPlayback { get; set; }
-        
+
+        public string[] BlockedMediaFolders { get; set; }
+
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="UserConfiguration" /> class.
         /// Initializes a new instance of the <see cref="UserConfiguration" /> class.
         /// </summary>
         /// </summary>
@@ -84,6 +80,8 @@ namespace MediaBrowser.Model.Configuration
             EnableLiveTvManagement = true;
             EnableLiveTvManagement = true;
             EnableMediaPlayback = true;
             EnableMediaPlayback = true;
             EnableLiveTvAccess = true;
             EnableLiveTvAccess = true;
+
+            BlockedMediaFolders = new string[] { };
         }
         }
     }
     }
 }
 }

+ 5 - 2
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)
         public void Fetch(Episode item, List<LocalImageInfo> images, string metadataFile, CancellationToken cancellationToken)
         {
         {
             _imagesFound = images;
             _imagesFound = images;
+            _xmlPath = metadataFile;
 
 
             Fetch(item, metadataFile, cancellationToken);
             Fetch(item, metadataFile, cancellationToken);
         }
         }
@@ -75,8 +78,8 @@ namespace MediaBrowser.Providers.TV
                             // even though it's actually using the metadata folder.
                             // even though it's actually using the metadata folder.
                             filename = Path.GetFileName(filename);
                             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);
                             var file = new FileInfo(filename);
 
 
                             if (file.Exists)
                             if (file.Exists)

+ 0 - 20
MediaBrowser.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs

@@ -283,26 +283,6 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
                 return new[] { user.RootFolder as T };
                 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
             // Return it only if it's in the user's library
             if (includeIfNotFound || allRecursiveChildren.ContainsKey(item.Id))
             if (includeIfNotFound || allRecursiveChildren.ContainsKey(item.Id))
             {
             {

+ 18 - 46
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;
         private readonly IFileSystem _fileSystem;
 
 
         /// <summary>
         /// <summary>
@@ -586,7 +580,7 @@ namespace MediaBrowser.Server.Implementations.Library
                 var flattenFolderDepth = isPhysicalRoot ? 2 : 0;
                 var flattenFolderDepth = isPhysicalRoot ? 2 : 0;
 
 
                 var fileSystemDictionary = FileData.GetFilteredFileSystemEntries(directoryService, args.Path, _fileSystem, _logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf);
                 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
                 // Need to remove subpaths that may have been resolved from shortcuts
                 // Example: if \\server\movies exists, then strip out \\server\movies\action
                 // Example: if \\server\movies exists, then strip out \\server\movies\action
                 if (isPhysicalRoot)
                 if (isPhysicalRoot)
@@ -701,20 +695,18 @@ namespace MediaBrowser.Server.Implementations.Library
             return rootFolder;
             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>
         /// <summary>
@@ -1001,7 +993,7 @@ namespace MediaBrowser.Server.Implementations.Library
             // Just run the scheduled task so that the user can see it
             // Just run the scheduled task so that the user can see it
             _taskManager.QueueScheduledTask<RefreshMediaLibraryTask>();
             _taskManager.QueueScheduledTask<RefreshMediaLibraryTask>();
         }
         }
-        
+
         /// <summary>
         /// <summary>
         /// Validates the media library internal.
         /// Validates the media library internal.
         /// </summary>
         /// </summary>
@@ -1035,19 +1027,15 @@ namespace MediaBrowser.Server.Implementations.Library
 
 
             progress.Report(1);
             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);
             progress.Report(2);
 
 
             var innerProgress = new ActionableProgress<double>();
             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));
             innerProgress.RegisterAction(pct => progress.Report(2 + pct * .73));
 
 
             // Now validate the entire media library
             // Now validate the entire media library
@@ -1118,22 +1106,6 @@ namespace MediaBrowser.Server.Implementations.Library
             progress.Report(100);
             progress.Report(100);
         }
         }
 
 
-        /// <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>
         /// <summary>
         /// Gets the default view.
         /// Gets the default view.
         /// </summary>
         /// </summary>
@@ -1150,7 +1122,7 @@ namespace MediaBrowser.Server.Implementations.Library
         /// <returns>IEnumerable{VirtualFolderInfo}.</returns>
         /// <returns>IEnumerable{VirtualFolderInfo}.</returns>
         public IEnumerable<VirtualFolderInfo> GetVirtualFolders(User user)
         public IEnumerable<VirtualFolderInfo> GetVirtualFolders(User user)
         {
         {
-            return GetView(user.RootFolderPath);
+            return GetDefaultVirtualFolders();
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -1399,7 +1371,7 @@ namespace MediaBrowser.Server.Implementations.Library
             {
             {
                 await _providerManagerFactory().SaveMetadata(item, updateReason).ConfigureAwait(false);
                 await _providerManagerFactory().SaveMetadata(item, updateReason).ConfigureAwait(false);
             }
             }
-            
+
             item.DateLastSaved = DateTime.UtcNow;
             item.DateLastSaved = DateTime.UtcNow;
 
 
             _logger.Debug("Saving {0} to database.", item.Path ?? item.Name);
             _logger.Debug("Saving {0} to database.", item.Path ?? item.Name);

+ 8 - 22
MediaBrowser.Server.Implementations/Library/UserManager.cs

@@ -332,29 +332,15 @@ namespace MediaBrowser.Server.Implementations.Library
 
 
             await UserRepository.DeleteUser(user, CancellationToken.None).ConfigureAwait(false);
             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
             // Force this to be lazy loaded again

+ 37 - 0
MediaBrowser.ServerApplication/ApplicationHost.cs

@@ -252,6 +252,14 @@ namespace MediaBrowser.ServerApplication
 
 
         private void DeleteDeprecatedModules()
         private void DeleteDeprecatedModules()
         {
         {
+            try
+            {
+                MigrateUserFolders();
+            }
+            catch (IOException ex)
+            {
+            }
+
             try
             try
             {
             {
                 File.Delete(Path.Combine(ApplicationPaths.PluginsPath, "MBPhoto.dll"));
                 File.Delete(Path.Combine(ApplicationPaths.PluginsPath, "MBPhoto.dll"));
@@ -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>
         /// <summary>
         /// Registers resources that classes will depend on
         /// Registers resources that classes will depend on
         /// </summary>
         /// </summary>

+ 16 - 22
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) {
         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
         * @param {String} name
         */
         */
-        self.removeVirtualFolder = function (name, userId, refreshLibrary) {
+        self.removeVirtualFolder = function (name, refreshLibrary) {
 
 
             if (!name) {
             if (!name) {
                 throw new Error("null name");
                 throw new Error("null name");
             }
             }
 
 
-            var url = userId ? "Users/" + userId + "/VirtualFolders" : "Library/VirtualFolders";
+            var url = "Library/VirtualFolders";
 
 
             url = self.getUrl(url, {
             url = self.getUrl(url, {
                 refreshLibrary: refreshLibrary ? true : false,
                 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
        * @param {String} name
        */
        */
-        self.addVirtualFolder = function (name, type, userId, refreshLibrary) {
+        self.addVirtualFolder = function (name, type, refreshLibrary) {
 
 
             if (!name) {
             if (!name) {
                 throw new Error("null name");
                 throw new Error("null name");
@@ -1518,7 +1518,7 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi
             options.refreshLibrary = refreshLibrary ? true : false;
             options.refreshLibrary = refreshLibrary ? true : false;
             options.name = name;
             options.name = name;
 
 
-            var url = userId ? "Users/" + userId + "/VirtualFolders" : "Library/VirtualFolders";
+            var url = "Library/VirtualFolders";
 
 
             url = self.getUrl(url, options);
             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
        * @param {String} name
        */
        */
-        self.renameVirtualFolder = function (name, newName, userId, refreshLibrary) {
+        self.renameVirtualFolder = function (name, newName, refreshLibrary) {
 
 
             if (!name) {
             if (!name) {
                 throw new Error("null 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, {
             url = self.getUrl(url, {
                 refreshLibrary: refreshLibrary ? true : false,
                 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
         * @param {String} name
         */
         */
-        self.addMediaPath = function (virtualFolderName, mediaPath, userId, refreshLibrary) {
+        self.addMediaPath = function (virtualFolderName, mediaPath, refreshLibrary) {
 
 
             if (!virtualFolderName) {
             if (!virtualFolderName) {
                 throw new Error("null virtualFolderName");
                 throw new Error("null virtualFolderName");
@@ -1568,9 +1566,7 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi
                 throw new Error("null mediaPath");
                 throw new Error("null mediaPath");
             }
             }
 
 
-            var url = userId ? "Users/" + userId + "/VirtualFolders" : "Library/VirtualFolders";
-
-            url += "/Paths";
+            var url = "Library/VirtualFolders/Paths";
 
 
             url = self.getUrl(url, {
             url = self.getUrl(url, {
                 refreshLibrary: refreshLibrary ? true : false,
                 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
         * @param {String} name
         */
         */
-        self.removeMediaPath = function (virtualFolderName, mediaPath, userId, refreshLibrary) {
+        self.removeMediaPath = function (virtualFolderName, mediaPath, refreshLibrary) {
 
 
             if (!virtualFolderName) {
             if (!virtualFolderName) {
                 throw new Error("null virtualFolderName");
                 throw new Error("null virtualFolderName");
@@ -1598,9 +1594,7 @@ MediaBrowser.ApiClient = function ($, navigator, JSON, WebSocket, setTimeout, wi
                 throw new Error("null mediaPath");
                 throw new Error("null mediaPath");
             }
             }
 
 
-            var url = userId ? "Users/" + userId + "/VirtualFolders" : "Library/VirtualFolders";
-
-            url += "/Paths";
+            var url = "Library/VirtualFolders/Paths";
 
 
             url = self.getUrl(url, {
             url = self.getUrl(url, {
                 refreshLibrary: refreshLibrary ? true : false,
                 refreshLibrary: refreshLibrary ? true : false,

+ 1 - 1
MediaBrowser.WebDashboard/packages.config

@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
 <packages>
-  <package id="MediaBrowser.ApiClient.Javascript" version="3.0.244" targetFramework="net45" />
+  <package id="MediaBrowser.ApiClient.Javascript" version="3.0.245" targetFramework="net45" />
 </packages>
 </packages>