浏览代码

Merge branch 'dev' into beta

Luke Pulverenti 9 年之前
父节点
当前提交
5d4682d418
共有 100 个文件被更改,包括 2001 次插入1791 次删除
  1. 1 1
      Emby.Drawing/Emby.Drawing.csproj
  2. 10 11
      Emby.Drawing/ImageMagick/ImageMagickEncoder.cs
  3. 1 1
      Emby.Drawing/packages.config
  4. 0 44
      MediaBrowser.Api/BaseApiService.cs
  5. 16 5
      MediaBrowser.Api/GamesService.cs
  6. 52 8
      MediaBrowser.Api/Library/LibraryService.cs
  7. 25 20
      MediaBrowser.Api/Movies/MoviesService.cs
  8. 1 1
      MediaBrowser.Api/SearchService.cs
  9. 0 4
      MediaBrowser.Api/StartupWizardService.cs
  10. 16 17
      MediaBrowser.Api/TvShowsService.cs
  11. 7 0
      MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs
  12. 6 9
      MediaBrowser.Api/UserLibrary/ItemsService.cs
  13. 19 4
      MediaBrowser.Api/UserLibrary/UserViewsService.cs
  14. 2 2
      MediaBrowser.Controller/Channels/ChannelAudioItem.cs
  15. 6 0
      MediaBrowser.Controller/Channels/ChannelFolderItem.cs
  16. 2 2
      MediaBrowser.Controller/Channels/ChannelVideoItem.cs
  17. 16 18
      MediaBrowser.Controller/Entities/Audio/Audio.cs
  18. 7 2
      MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
  19. 5 0
      MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
  20. 180 75
      MediaBrowser.Controller/Entities/BaseItem.cs
  21. 3 14
      MediaBrowser.Controller/Entities/Book.cs
  22. 8 7
      MediaBrowser.Controller/Entities/CollectionFolder.cs
  23. 55 77
      MediaBrowser.Controller/Entities/Folder.cs
  24. 2 9
      MediaBrowser.Controller/Entities/Game.cs
  25. 5 0
      MediaBrowser.Controller/Entities/GameSystem.cs
  26. 5 0
      MediaBrowser.Controller/Entities/ICollectionFolder.cs
  27. 18 0
      MediaBrowser.Controller/Entities/IHiddenFromDisplay.cs
  28. 35 0
      MediaBrowser.Controller/Entities/InternalItemsQuery.cs
  29. 6 31
      MediaBrowser.Controller/Entities/Movies/BoxSet.cs
  30. 3 3
      MediaBrowser.Controller/Entities/Movies/Movie.cs
  31. 2 2
      MediaBrowser.Controller/Entities/MusicVideo.cs
  32. 9 0
      MediaBrowser.Controller/Entities/Person.cs
  33. 1 8
      MediaBrowser.Controller/Entities/Photo.cs
  34. 2 27
      MediaBrowser.Controller/Entities/PhotoAlbum.cs
  35. 0 7
      MediaBrowser.Controller/Entities/Studio.cs
  36. 17 16
      MediaBrowser.Controller/Entities/TV/Episode.cs
  37. 7 14
      MediaBrowser.Controller/Entities/TV/Season.cs
  38. 5 0
      MediaBrowser.Controller/Entities/TV/Series.cs
  39. 3 3
      MediaBrowser.Controller/Entities/Trailer.cs
  40. 20 1
      MediaBrowser.Controller/Entities/User.cs
  41. 9 1
      MediaBrowser.Controller/Entities/UserRootFolder.cs
  42. 73 20
      MediaBrowser.Controller/Entities/UserView.cs
  43. 88 165
      MediaBrowser.Controller/Entities/UserViewBuilder.cs
  44. 1 7
      MediaBrowser.Controller/Entities/Video.cs
  45. 24 3
      MediaBrowser.Controller/Library/ILibraryManager.cs
  46. 1 1
      MediaBrowser.Controller/Library/ItemResolveArgs.cs
  47. 7 3
      MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs
  48. 2 2
      MediaBrowser.Controller/LiveTv/LiveTvChannel.cs
  49. 23 25
      MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
  50. 7 3
      MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs
  51. 6 0
      MediaBrowser.Controller/LiveTv/RecordingGroup.cs
  52. 1 0
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  53. 0 1
      MediaBrowser.Controller/MediaEncoding/MediaInfoRequest.cs
  54. 14 0
      MediaBrowser.Controller/Persistence/IItemRepository.cs
  55. 1 0
      MediaBrowser.Controller/Playlists/Playlist.cs
  56. 0 32
      MediaBrowser.Controller/Providers/MetadataStatus.cs
  57. 3 2
      MediaBrowser.Controller/Resolvers/IResolverIgnoreRule.cs
  58. 5 2
      MediaBrowser.Dlna/ContentDirectory/ContentDirectory.cs
  59. 37 24
      MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs
  60. 1 1
      MediaBrowser.Dlna/Didl/DidlBuilder.cs
  61. 0 3
      MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj
  62. 0 166
      MediaBrowser.LocalMetadata/Savers/EpisodeXmlSaver.cs
  63. 0 147
      MediaBrowser.LocalMetadata/Savers/MovieXmlSaver.cs
  64. 0 154
      MediaBrowser.LocalMetadata/Savers/SeriesXmlSaver.cs
  65. 1 1
      MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs
  66. 21 18
      MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
  67. 1 1
      MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
  68. 20 3
      MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
  69. 3 0
      MediaBrowser.Model/Configuration/CinemaModeConfiguration.cs
  70. 9 22
      MediaBrowser.Model/Configuration/ServerConfiguration.cs
  71. 1 1
      MediaBrowser.Providers/Folders/DefaultImageProvider.cs
  72. 3 0
      MediaBrowser.Providers/Subtitles/SubtitleManager.cs
  73. 46 30
      MediaBrowser.Server.Implementations/Channels/ChannelManager.cs
  74. 4 4
      MediaBrowser.Server.Implementations/Channels/ChannelPostScanTask.cs
  75. 3 18
      MediaBrowser.Server.Implementations/Collections/CollectionImageProvider.cs
  76. 1 0
      MediaBrowser.Server.Implementations/Collections/CollectionManager.cs
  77. 3 8
      MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs
  78. 29 17
      MediaBrowser.Server.Implementations/Devices/CameraUploadsFolder.cs
  79. 9 9
      MediaBrowser.Server.Implementations/Devices/DeviceManager.cs
  80. 30 27
      MediaBrowser.Server.Implementations/Dto/DtoService.cs
  81. 1 1
      MediaBrowser.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs
  82. 11 4
      MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs
  83. 19 15
      MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs
  84. 36 27
      MediaBrowser.Server.Implementations/Library/CoreResolutionIgnoreRule.cs
  85. 186 95
      MediaBrowser.Server.Implementations/Library/LibraryManager.cs
  86. 3 5
      MediaBrowser.Server.Implementations/Library/MusicManager.cs
  87. 2 2
      MediaBrowser.Server.Implementations/Library/ResolverHelper.cs
  88. 13 9
      MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
  89. 1 1
      MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs
  90. 5 25
      MediaBrowser.Server.Implementations/Library/SearchEngine.cs
  91. 1 2
      MediaBrowser.Server.Implementations/Library/UserManager.cs
  92. 89 173
      MediaBrowser.Server.Implementations/Library/UserViewManager.cs
  93. 27 29
      MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
  94. 1 2
      MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs
  95. 26 9
      MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs
  96. 32 0
      MediaBrowser.Server.Implementations/Persistence/MediaStreamColumns.cs
  97. 481 22
      MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs
  98. 0 5
      MediaBrowser.Server.Implementations/Playlists/ManualPlaylistsFolder.cs
  99. 1 1
      MediaBrowser.Server.Implementations/Playlists/PlaylistImageProvider.cs
  100. 1 0
      MediaBrowser.Server.Implementations/Playlists/PlaylistManager.cs

+ 1 - 1
Emby.Drawing/Emby.Drawing.csproj

@@ -37,7 +37,7 @@
     </Reference>
     </Reference>
     <Reference Include="ImageMagickSharp, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
     <Reference Include="ImageMagickSharp, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
       <SpecificVersion>False</SpecificVersion>
       <SpecificVersion>False</SpecificVersion>
-      <HintPath>..\packages\ImageMagickSharp.1.0.0.16\lib\net45\ImageMagickSharp.dll</HintPath>
+      <HintPath>..\packages\ImageMagickSharp.1.0.0.17\lib\net45\ImageMagickSharp.dll</HintPath>
     </Reference>
     </Reference>
     <Reference Include="Patterns.Logging">
     <Reference Include="Patterns.Logging">
       <HintPath>..\packages\Patterns.Logging.1.0.0.2\lib\portable-net45+sl4+wp71+win8+wpa81\Patterns.Logging.dll</HintPath>
       <HintPath>..\packages\Patterns.Logging.1.0.0.2\lib\portable-net45+sl4+wp71+win8+wpa81\Patterns.Logging.dll</HintPath>

+ 10 - 11
Emby.Drawing/ImageMagick/ImageMagickEncoder.cs

@@ -148,7 +148,7 @@ namespace Emby.Drawing.ImageMagick
                     DrawIndicator(originalImage, width, height, options);
                     DrawIndicator(originalImage, width, height, options);
 
 
                     originalImage.CurrentImage.CompressionQuality = quality;
                     originalImage.CurrentImage.CompressionQuality = quality;
-                    //originalImage.CurrentImage.StripImage();
+                    originalImage.CurrentImage.StripImage();
 
 
                     originalImage.SaveImage(outputPath);
                     originalImage.SaveImage(outputPath);
                 }
                 }
@@ -165,7 +165,7 @@ namespace Emby.Drawing.ImageMagick
                         DrawIndicator(wand, width, height, options);
                         DrawIndicator(wand, width, height, options);
 
 
                         wand.CurrentImage.CompressionQuality = quality;
                         wand.CurrentImage.CompressionQuality = quality;
-                        //wand.CurrentImage.StripImage();
+                        wand.CurrentImage.StripImage();
 
 
                         wand.SaveImage(outputPath);
                         wand.SaveImage(outputPath);
                     }
                     }
@@ -176,15 +176,14 @@ namespace Emby.Drawing.ImageMagick
 
 
         private void ScaleImage(MagickWand wand, int width, int height)
         private void ScaleImage(MagickWand wand, int width, int height)
         {
         {
-            wand.CurrentImage.ResizeImage(width, height);
-            //if (_config.Configuration.EnableHighQualityImageScaling)
-            //{
-            //    wand.CurrentImage.ResizeImage(width, height);
-            //}
-            //else
-            //{
-            //    wand.CurrentImage.ScaleImage(width, height);
-            //}
+            if (_config.Configuration.EnableHighQualityImageScaling)
+            {
+                wand.CurrentImage.ResizeImage(width, height);
+            }
+            else
+            {
+                wand.CurrentImage.ScaleImage(width, height);
+            }
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 1 - 1
Emby.Drawing/packages.config

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
 <packages>
   <package id="CommonIO" version="1.0.0.5" targetFramework="net45" />
   <package id="CommonIO" version="1.0.0.5" targetFramework="net45" />
-  <package id="ImageMagickSharp" version="1.0.0.16" targetFramework="net45" />
+  <package id="ImageMagickSharp" version="1.0.0.17" targetFramework="net45" />
   <package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
   <package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
 </packages>
 </packages>

+ 0 - 44
MediaBrowser.Api/BaseApiService.cs

@@ -198,50 +198,6 @@ namespace MediaBrowser.Api
             return libraryManager.GetPerson(DeSlugPersonName(name, libraryManager));
             return libraryManager.GetPerson(DeSlugPersonName(name, libraryManager));
         }
         }
 
 
-        protected IList<BaseItem> GetAllLibraryItems(string userId, IUserManager userManager, ILibraryManager libraryManager, string parentId, Func<BaseItem,bool> filter)
-        {
-            if (!string.IsNullOrEmpty(parentId))
-            {
-                var folder = (Folder)libraryManager.GetItemById(new Guid(parentId));
-
-                if (!string.IsNullOrWhiteSpace(userId))
-                {
-                    var user = userManager.GetUserById(userId);
-
-                    if (user == null)
-                    {
-                        throw new ArgumentException("User not found");
-                    }
-
-                    return folder
-                        .GetRecursiveChildren(user, filter)
-                        .ToList();
-                }
-
-                return folder
-                    .GetRecursiveChildren(filter);
-            }
-            if (!string.IsNullOrWhiteSpace(userId))
-            {
-                var user = userManager.GetUserById(userId);
-
-                if (user == null)
-                {
-                    throw new ArgumentException("User not found");
-                }
-
-                return userManager
-                    .GetUserById(userId)
-                    .RootFolder
-                    .GetRecursiveChildren(user, filter)
-                    .ToList();
-            }
-
-            return libraryManager
-                .RootFolder
-                .GetRecursiveChildren(filter);
-        }
-
         /// <summary>
         /// <summary>
         /// Deslugs an artist name by finding the correct entry in the library
         /// Deslugs an artist name by finding the correct entry in the library
         /// </summary>
         /// </summary>

+ 16 - 5
MediaBrowser.Api/GamesService.cs

@@ -102,12 +102,16 @@ namespace MediaBrowser.Api
         /// <returns>System.Object.</returns>
         /// <returns>System.Object.</returns>
         public object Get(GetGameSystemSummaries request)
         public object Get(GetGameSystemSummaries request)
         {
         {
-            var gameSystems = GetAllLibraryItems(request.UserId, _userManager, _libraryManager, null, i => i is GameSystem)
+            var user = request.UserId == null ? null : _userManager.GetUserById(request.UserId);
+            var query = new InternalItemsQuery(user)
+            {
+                IncludeItemTypes = new[] { typeof(GameSystem).Name }
+            };
+            var parentIds = new string[] { } ;
+            var gameSystems = _libraryManager.GetItems(query, parentIds)
                 .Cast<GameSystem>()
                 .Cast<GameSystem>()
                 .ToList();
                 .ToList();
 
 
-            var user = request.UserId == null ? null : _userManager.GetUserById(request.UserId);
-
             var result = gameSystems
             var result = gameSystems
                 .Select(i => GetSummary(i, user))
                 .Select(i => GetSummary(i, user))
                 .ToList();
                 .ToList();
@@ -119,8 +123,15 @@ namespace MediaBrowser.Api
 
 
         public object Get(GetPlayerIndex request)
         public object Get(GetPlayerIndex request)
         {
         {
-            var games = GetAllLibraryItems(request.UserId, _userManager, _libraryManager, null, i => i is Game)
-                .Cast<Game>();
+            var user = request.UserId == null ? null : _userManager.GetUserById(request.UserId);
+            var query = new InternalItemsQuery(user)
+            {
+                IncludeItemTypes = new[] { typeof(Game).Name }
+            };
+            var parentIds = new string[] { };
+            var games = _libraryManager.GetItems(query, parentIds)
+                .Cast<Game>()
+                .ToList();
 
 
             var lookup = games
             var lookup = games
                 .ToLookup(i => i.PlayersSupported ?? -1)
                 .ToLookup(i => i.PlayersSupported ?? -1)

+ 52 - 8
MediaBrowser.Api/Library/LibraryService.cs

@@ -416,7 +416,7 @@ namespace MediaBrowser.Api.Library
 
 
         public object Get(GetMediaFolders request)
         public object Get(GetMediaFolders request)
         {
         {
-            var items = _libraryManager.GetUserRootFolder().Children.OrderBy(i => i.SortName).ToList();
+            var items = _libraryManager.GetUserRootFolder().Children.Concat(_libraryManager.RootFolder.VirtualChildren).OrderBy(i => i.SortName).ToList();
 
 
             if (request.IsHidden.HasValue)
             if (request.IsHidden.HasValue)
             {
             {
@@ -610,7 +610,7 @@ namespace MediaBrowser.Api.Library
 
 
             var dtoOptions = GetDtoOptions(request);
             var dtoOptions = GetDtoOptions(request);
 
 
-            BaseItem parent = item.Parent;
+            BaseItem parent = item.GetParent();
 
 
             while (parent != null)
             while (parent != null)
             {
             {
@@ -621,7 +621,7 @@ namespace MediaBrowser.Api.Library
 
 
                 baseItemDtos.Add(_dtoService.GetBaseItemDto(parent, dtoOptions, user));
                 baseItemDtos.Add(_dtoService.GetBaseItemDto(parent, dtoOptions, user));
 
 
-                parent = parent.Parent;
+                parent = parent.GetParent();
             }
             }
 
 
             return baseItemDtos.ToList();
             return baseItemDtos.ToList();
@@ -629,7 +629,7 @@ namespace MediaBrowser.Api.Library
 
 
         private BaseItem TranslateParentItem(BaseItem item, User user)
         private BaseItem TranslateParentItem(BaseItem item, User user)
         {
         {
-            if (item.Parent is AggregateFolder)
+            if (item.GetParent() is AggregateFolder)
             {
             {
                 return user.RootFolder.GetChildren(user, true).FirstOrDefault(i => i.PhysicalLocations.Contains(item.Path));
                 return user.RootFolder.GetChildren(user, true).FirstOrDefault(i => i.PhysicalLocations.Contains(item.Path));
             }
             }
@@ -677,6 +677,50 @@ namespace MediaBrowser.Api.Library
             return ToOptimizedSerializedResultUsingCache(counts);
             return ToOptimizedSerializedResultUsingCache(counts);
         }
         }
 
 
+        private IList<BaseItem> GetAllLibraryItems(string userId, IUserManager userManager, ILibraryManager libraryManager, string parentId, Func<BaseItem, bool> filter)
+        {
+            if (!string.IsNullOrEmpty(parentId))
+            {
+                var folder = (Folder)libraryManager.GetItemById(new Guid(parentId));
+
+                if (!string.IsNullOrWhiteSpace(userId))
+                {
+                    var user = userManager.GetUserById(userId);
+
+                    if (user == null)
+                    {
+                        throw new ArgumentException("User not found");
+                    }
+
+                    return folder
+                        .GetRecursiveChildren(user, filter)
+                        .ToList();
+                }
+
+                return folder
+                    .GetRecursiveChildren(filter);
+            }
+            if (!string.IsNullOrWhiteSpace(userId))
+            {
+                var user = userManager.GetUserById(userId);
+
+                if (user == null)
+                {
+                    throw new ArgumentException("User not found");
+                }
+
+                return userManager
+                    .GetUserById(userId)
+                    .RootFolder
+                    .GetRecursiveChildren(user, filter)
+                    .ToList();
+            }
+
+            return libraryManager
+                .RootFolder
+                .GetRecursiveChildren(filter);
+        }
+
         private bool FilterItem(BaseItem item, GetItemCounts request, string userId)
         private bool FilterItem(BaseItem item, GetItemCounts request, string userId)
         {
         {
             if (!string.IsNullOrWhiteSpace(userId))
             if (!string.IsNullOrWhiteSpace(userId))
@@ -817,9 +861,9 @@ namespace MediaBrowser.Api.Library
                                   : (Folder)_libraryManager.RootFolder)
                                   : (Folder)_libraryManager.RootFolder)
                            : _libraryManager.GetItemById(request.Id);
                            : _libraryManager.GetItemById(request.Id);
 
 
-            while (GetThemeSongIds(item).Count == 0 && request.InheritFromParent && item.Parent != null)
+            while (GetThemeSongIds(item).Count == 0 && request.InheritFromParent && item.GetParent() != null)
             {
             {
-                item = item.Parent;
+                item = item.GetParent();
             }
             }
 
 
             var dtoOptions = GetDtoOptions(request);
             var dtoOptions = GetDtoOptions(request);
@@ -860,9 +904,9 @@ namespace MediaBrowser.Api.Library
                                   : (Folder)_libraryManager.RootFolder)
                                   : (Folder)_libraryManager.RootFolder)
                            : _libraryManager.GetItemById(request.Id);
                            : _libraryManager.GetItemById(request.Id);
 
 
-            while (GetThemeVideoIds(item).Count == 0 && request.InheritFromParent && item.Parent != null)
+            while (GetThemeVideoIds(item).Count == 0 && request.InheritFromParent && item.GetParent() != null)
             {
             {
-                item = item.Parent;
+                item = item.GetParent();
             }
             }
 
 
             var dtoOptions = GetDtoOptions(request);
             var dtoOptions = GetDtoOptions(request);

+ 25 - 20
MediaBrowser.Api/Movies/MoviesService.cs

@@ -117,10 +117,7 @@ namespace MediaBrowser.Api.Movies
         public async Task<object> Get(GetSimilarMovies request)
         public async Task<object> Get(GetSimilarMovies request)
         {
         {
             var result = await GetSimilarItemsResult(
             var result = await GetSimilarItemsResult(
-                // Strip out secondary versions
-                request, item => (item is Movie) && !((Video)item).PrimaryVersionId.HasValue,
-
-                SimilarItemsHelper.GetSimiliarityScore).ConfigureAwait(false);
+                request, SimilarItemsHelper.GetSimiliarityScore).ConfigureAwait(false);
 
 
             return ToOptimizedSerializedResultUsingCache(result);
             return ToOptimizedSerializedResultUsingCache(result);
         }
         }
@@ -128,10 +125,7 @@ namespace MediaBrowser.Api.Movies
         public async Task<object> Get(GetSimilarTrailers request)
         public async Task<object> Get(GetSimilarTrailers request)
         {
         {
             var result = await GetSimilarItemsResult(
             var result = await GetSimilarItemsResult(
-                // Strip out secondary versions
-                request, item => (item is Movie) && !((Video)item).PrimaryVersionId.HasValue,
-
-                SimilarItemsHelper.GetSimiliarityScore).ConfigureAwait(false);
+                request, SimilarItemsHelper.GetSimiliarityScore).ConfigureAwait(false);
 
 
             return ToOptimizedSerializedResultUsingCache(result);
             return ToOptimizedSerializedResultUsingCache(result);
         }
         }
@@ -140,8 +134,12 @@ namespace MediaBrowser.Api.Movies
         {
         {
             var user = _userManager.GetUserById(request.UserId);
             var user = _userManager.GetUserById(request.UserId);
 
 
-            IEnumerable<BaseItem> movies = GetAllLibraryItems(request.UserId, _userManager, _libraryManager, request.ParentId, i => i is Movie);
-
+            var query = new InternalItemsQuery(user)
+            {
+                IncludeItemTypes = new[] { typeof(Movie).Name }
+            };
+            var parentIds = string.IsNullOrWhiteSpace(request.ParentId) ? new string[] { } : new[] { request.ParentId };
+            var movies = _libraryManager.GetItems(query, parentIds);
             movies = _libraryManager.ReplaceVideosWithPrimaryVersions(movies);
             movies = _libraryManager.ReplaceVideosWithPrimaryVersions(movies);
 
 
             var listEligibleForCategories = new List<BaseItem>();
             var listEligibleForCategories = new List<BaseItem>();
@@ -184,21 +182,27 @@ namespace MediaBrowser.Api.Movies
             return ToOptimizedResult(result);
             return ToOptimizedResult(result);
         }
         }
 
 
-        private async Task<ItemsResult> GetSimilarItemsResult(BaseGetSimilarItemsFromItem request, Func<BaseItem, bool> includeInSearch, Func<BaseItem, List<PersonInfo>, List<PersonInfo>, BaseItem, int> getSimilarityScore)
+        private async Task<ItemsResult> GetSimilarItemsResult(BaseGetSimilarItemsFromItem request, Func<BaseItem, List<PersonInfo>, List<PersonInfo>, BaseItem, int> getSimilarityScore)
         {
         {
             var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
             var user = !string.IsNullOrWhiteSpace(request.UserId) ? _userManager.GetUserById(request.UserId) : null;
 
 
             var item = string.IsNullOrEmpty(request.Id) ?
             var item = string.IsNullOrEmpty(request.Id) ?
                 (!string.IsNullOrWhiteSpace(request.UserId) ? user.RootFolder :
                 (!string.IsNullOrWhiteSpace(request.UserId) ? user.RootFolder :
                 _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id);
                 _libraryManager.RootFolder) : _libraryManager.GetItemById(request.Id);
-
-            Func<BaseItem, bool> filter = i => i.Id != item.Id && includeInSearch(i);
-
-            var inputItems = user == null
-                                 ? _libraryManager.RootFolder.GetRecursiveChildren(filter)
-                                 : user.RootFolder.GetRecursiveChildren(user, filter);
-
-            var list = inputItems.ToList();
+            
+            var query = new InternalItemsQuery(user)
+            {
+                IncludeItemTypes = new[] { typeof(Movie).Name }
+            };
+            var parentIds = new string[] { };
+            var list = _libraryManager.GetItems(query, parentIds)
+                .Where(i =>
+                {
+                    // Strip out secondary versions
+                    var v = i as Video;
+                    return v != null && !v.PrimaryVersionId.HasValue;
+                })
+                .ToList();
 
 
             if (user != null && user.Configuration.IncludeTrailersInSuggestions)
             if (user != null && user.Configuration.IncludeTrailersInSuggestions)
             {
             {
@@ -379,9 +383,10 @@ namespace MediaBrowser.Api.Movies
         {
         {
             foreach (var name in names)
             foreach (var name in names)
             {
             {
-                var itemsWithActor = _libraryManager.GetItemIds(new InternalItemsQuery
+                var itemsWithActor = _libraryManager.GetItemIds(new InternalItemsQuery(user)
                 {
                 {
                     Person = name
                     Person = name
+
                 });
                 });
 
 
                 var items = allMovies
                 var items = allMovies

+ 1 - 1
MediaBrowser.Api/SearchService.cs

@@ -283,7 +283,7 @@ namespace MediaBrowser.Api
         private T GetParentWithImage<T>(BaseItem item, ImageType type)
         private T GetParentWithImage<T>(BaseItem item, ImageType type)
             where T : BaseItem
             where T : BaseItem
         {
         {
-            return item.Parents.OfType<T>().FirstOrDefault(i => i.HasImage(type));
+            return item.GetParents().OfType<T>().FirstOrDefault(i => i.HasImage(type));
         }
         }
     }
     }
 }
 }

+ 0 - 4
MediaBrowser.Api/StartupWizardService.cs

@@ -68,11 +68,7 @@ namespace MediaBrowser.Api
             _config.Configuration.EnableLocalizedGuids = true;
             _config.Configuration.EnableLocalizedGuids = true;
             _config.Configuration.MergeMetadataAndImagesByName = true;
             _config.Configuration.MergeMetadataAndImagesByName = true;
             _config.Configuration.EnableStandaloneMetadata = true;
             _config.Configuration.EnableStandaloneMetadata = true;
-            _config.Configuration.EnableLibraryMetadataSubFolder = true;
             _config.Configuration.EnableCustomPathSubFolders = true;
             _config.Configuration.EnableCustomPathSubFolders = true;
-            _config.Configuration.DisableXmlSavers = true;
-            _config.Configuration.DisableStartupScan = true;
-            _config.Configuration.EnableUserViews = true;
             _config.Configuration.EnableDateLastRefresh = true;
             _config.Configuration.EnableDateLastRefresh = true;
             _config.SaveConfiguration();
             _config.SaveConfiguration();
         }
         }

+ 16 - 17
MediaBrowser.Api/TvShowsService.cs

@@ -159,7 +159,7 @@ namespace MediaBrowser.Api
 
 
         [ApiMember(Name = "StartItemId", Description = "Optional. Skip through the list until a given item is found.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
         [ApiMember(Name = "StartItemId", Description = "Optional. Skip through the list until a given item is found.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
         public string StartItemId { get; set; }
         public string StartItemId { get; set; }
-        
+
         /// <summary>
         /// <summary>
         /// Skips over a given number of items within the results. Use for paging.
         /// Skips over a given number of items within the results. Use for paging.
         /// </summary>
         /// </summary>
@@ -273,29 +273,28 @@ namespace MediaBrowser.Api
         {
         {
             var user = _userManager.GetUserById(request.UserId);
             var user = _userManager.GetUserById(request.UserId);
 
 
-            var items = GetAllLibraryItems(request.UserId, _userManager, _libraryManager, request.ParentId, i => i is Episode);
-
-            var itemsList = _libraryManager
-                .Sort(items, user, new[] { "PremiereDate", "AirTime", "SortName" }, SortOrder.Ascending)
-                .Cast<Episode>()
-                .ToList();
-
-            var unairedEpisodes = itemsList.Where(i => i.IsUnaired).ToList();
-
             var minPremiereDate = DateTime.Now.Date.AddDays(-1).ToUniversalTime();
             var minPremiereDate = DateTime.Now.Date.AddDays(-1).ToUniversalTime();
-            var previousEpisodes = itemsList.Where(i => !i.IsUnaired && (i.PremiereDate ?? DateTime.MinValue) >= minPremiereDate).ToList();
 
 
-            previousEpisodes.AddRange(unairedEpisodes);
+            var parentIds = string.IsNullOrWhiteSpace(request.ParentId) ? new string[] { } : new[] { request.ParentId };
 
 
-            var pagedItems = ApplyPaging(previousEpisodes, request.StartIndex, request.Limit);
+            var itemsResult = _libraryManager.GetItemsResult(new InternalItemsQuery(user)
+            {
+                IncludeItemTypes = new[] { typeof(Episode).Name },
+                SortBy = new[] { "PremiereDate", "AirTime", "SortName" },
+                SortOrder = SortOrder.Ascending,
+                MinPremiereDate = minPremiereDate,
+                StartIndex = request.StartIndex,
+                Limit = request.Limit
+
+            }, parentIds);
 
 
             var options = GetDtoOptions(request);
             var options = GetDtoOptions(request);
 
 
-            var returnItems = _dtoService.GetBaseItemDtos(pagedItems, options, user).ToArray();
+            var returnItems = _dtoService.GetBaseItemDtos(itemsResult.Items, options, user).ToArray();
 
 
             var result = new ItemsResult
             var result = new ItemsResult
             {
             {
-                TotalRecordCount = itemsList.Count,
+                TotalRecordCount = itemsResult.TotalRecordCount,
                 Items = returnItems
                 Items = returnItems
             };
             };
 
 
@@ -440,7 +439,7 @@ namespace MediaBrowser.Api
                 }
                 }
 
 
                 episodes = season.GetEpisodes(user);
                 episodes = season.GetEpisodes(user);
-            } 
+            }
             else if (request.Season.HasValue)
             else if (request.Season.HasValue)
             {
             {
                 var series = _libraryManager.GetItemById(request.Id) as Series;
                 var series = _libraryManager.GetItemById(request.Id) as Series;
@@ -495,7 +494,7 @@ namespace MediaBrowser.Api
                 .ToList();
                 .ToList();
 
 
             var pagedItems = ApplyPaging(returnList, request.StartIndex, request.Limit);
             var pagedItems = ApplyPaging(returnList, request.StartIndex, request.Limit);
-            
+
             var dtoOptions = GetDtoOptions(request);
             var dtoOptions = GetDtoOptions(request);
 
 
             var dtos = _dtoService.GetBaseItemDtos(pagedItems, dtoOptions, user)
             var dtos = _dtoService.GetBaseItemDtos(pagedItems, dtoOptions, user)

+ 7 - 0
MediaBrowser.Api/UserLibrary/BaseItemsRequest.cs

@@ -199,6 +199,8 @@ namespace MediaBrowser.Api.UserLibrary
         [ApiMember(Name = "Genres", Description = "Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         [ApiMember(Name = "Genres", Description = "Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         public string Genres { get; set; }
         public string Genres { get; set; }
 
 
+        public string GenreIds { get; set; }
+        
         [ApiMember(Name = "OfficialRatings", Description = "Optional. If specified, results will be filtered based on OfficialRating. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         [ApiMember(Name = "OfficialRatings", Description = "Optional. If specified, results will be filtered based on OfficialRating. This allows multiple, pipe delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         public string OfficialRatings { get; set; }
         public string OfficialRatings { get; set; }
 
 
@@ -378,6 +380,11 @@ namespace MediaBrowser.Api.UserLibrary
             return (StudioIds ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
             return (StudioIds ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
         }
         }
 
 
+        public string[] GetGenreIds()
+        {
+            return (GenreIds ?? string.Empty).Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
+        }
+
         public string[] GetPersonTypes()
         public string[] GetPersonTypes()
         {
         {
             return (PersonTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
             return (PersonTypes ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

+ 6 - 9
MediaBrowser.Api/UserLibrary/ItemsService.cs

@@ -112,6 +112,11 @@ namespace MediaBrowser.Api.UserLibrary
                 user == null ? _libraryManager.RootFolder : user.RootFolder :
                 user == null ? _libraryManager.RootFolder : user.RootFolder :
                 parentItem;
                 parentItem;
 
 
+            if (string.Equals(request.IncludeItemTypes, "Playlist", StringComparison.OrdinalIgnoreCase))
+            {
+                item = user == null ? _libraryManager.RootFolder : user.RootFolder;
+            }
+
             // Default list type = children
             // Default list type = children
 
 
             if (!string.IsNullOrEmpty(request.Ids))
             if (!string.IsNullOrEmpty(request.Ids))
@@ -210,6 +215,7 @@ namespace MediaBrowser.Api.UserLibrary
                 Tags = request.GetTags(),
                 Tags = request.GetTags(),
                 OfficialRatings = request.GetOfficialRatings(),
                 OfficialRatings = request.GetOfficialRatings(),
                 Genres = request.GetGenres(),
                 Genres = request.GetGenres(),
+                GenreIds = request.GetGenreIds(),
                 Studios = request.GetStudios(),
                 Studios = request.GetStudios(),
                 StudioIds = request.GetStudioIds(),
                 StudioIds = request.GetStudioIds(),
                 Person = request.Person,
                 Person = request.Person,
@@ -422,15 +428,6 @@ namespace MediaBrowser.Api.UserLibrary
                 return false;
                 return false;
             }
             }
 
 
-            // Min index number
-            if (request.MinIndexNumber.HasValue)
-            {
-                if (!(i.IndexNumber.HasValue && i.IndexNumber.Value >= request.MinIndexNumber.Value))
-                {
-                    return false;
-                }
-            }
-
             // Min official rating
             // Min official rating
             if (!string.IsNullOrEmpty(request.MinOfficialRating))
             if (!string.IsNullOrEmpty(request.MinOfficialRating))
             {
             {

+ 19 - 4
MediaBrowser.Api/UserLibrary/UserViewsService.cs

@@ -26,6 +26,8 @@ namespace MediaBrowser.Api.UserLibrary
 
 
         [ApiMember(Name = "IncludeExternalContent", Description = "Whether or not to include external views such as channels or live tv", IsRequired = true, DataType = "boolean", ParameterType = "query", Verb = "POST")]
         [ApiMember(Name = "IncludeExternalContent", Description = "Whether or not to include external views such as channels or live tv", IsRequired = true, DataType = "boolean", ParameterType = "query", Verb = "POST")]
         public bool? IncludeExternalContent { get; set; }
         public bool? IncludeExternalContent { get; set; }
+
+        public string PresetViews { get; set; }
     }
     }
 
 
     [Route("/Users/{UserId}/SpecialViewOptions", "GET")]
     [Route("/Users/{UserId}/SpecialViewOptions", "GET")]
@@ -75,9 +77,24 @@ namespace MediaBrowser.Api.UserLibrary
                 query.IncludeExternalContent = request.IncludeExternalContent.Value;
                 query.IncludeExternalContent = request.IncludeExternalContent.Value;
             }
             }
 
 
+            if (!string.IsNullOrWhiteSpace(request.PresetViews))
+            {
+                query.PresetViews = request.PresetViews.Split(',');
+            }
+
+            var app = AuthorizationContext.GetAuthorizationInfo(Request).Client ?? string.Empty;
+            if (app.IndexOf("emby rt", StringComparison.OrdinalIgnoreCase) != -1)
+            {
+                query.PresetViews = new[] { CollectionType.Music, CollectionType.Movies, CollectionType.TvShows };
+            }
+            //query.PresetViews = new[] { CollectionType.Music, CollectionType.Movies, CollectionType.TvShows };
+
             var folders = await _userViewManager.GetUserViews(query, CancellationToken.None).ConfigureAwait(false);
             var folders = await _userViewManager.GetUserViews(query, CancellationToken.None).ConfigureAwait(false);
 
 
             var dtoOptions = GetDtoOptions(request);
             var dtoOptions = GetDtoOptions(request);
+            dtoOptions.Fields = new List<ItemFields>();
+            dtoOptions.Fields.Add(ItemFields.PrimaryImageAspectRatio);
+            dtoOptions.Fields.Add(ItemFields.DisplayPreferencesId);
 
 
             var user = _userManager.GetUserById(request.UserId);
             var user = _userManager.GetUserById(request.UserId);
 
 
@@ -123,7 +140,7 @@ namespace MediaBrowser.Api.UserLibrary
             var views = user.RootFolder
             var views = user.RootFolder
                 .GetChildren(user, true)
                 .GetChildren(user, true)
                 .OfType<Folder>()
                 .OfType<Folder>()
-                .Where(i => !UserView.IsExcludedFromGrouping(i))
+                .Where(UserView.IsEligibleForGrouping)
                 .ToList();
                 .ToList();
 
 
             var list = views
             var list = views
@@ -141,9 +158,7 @@ namespace MediaBrowser.Api.UserLibrary
 
 
         private bool IsEligibleForSpecialView(ICollectionFolder view)
         private bool IsEligibleForSpecialView(ICollectionFolder view)
         {
         {
-            var types = new[] { CollectionType.Movies, CollectionType.TvShows, CollectionType.Games, CollectionType.Music, CollectionType.Photos };
-
-            return types.Contains(view.CollectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase);
+            return UserView.IsEligibleForEnhancedView(view.CollectionType);
         }
         }
     }
     }
 
 

+ 2 - 2
MediaBrowser.Controller/Channels/ChannelAudioItem.cs

@@ -18,9 +18,9 @@ namespace MediaBrowser.Controller.Channels
 
 
         public List<ChannelMediaInfo> ChannelMediaSources { get; set; }
         public List<ChannelMediaInfo> ChannelMediaSources { get; set; }
 
 
-        protected override bool GetBlockUnratedValue(UserPolicy config)
+        public override UnratedItem GetBlockUnratedType()
         {
         {
-            return config.BlockUnratedItems.Contains(UnratedItem.ChannelContent);
+            return UnratedItem.ChannelContent;
         }
         }
 
 
         protected override string CreateUserDataKey()
         protected override string CreateUserDataKey()

+ 6 - 0
MediaBrowser.Controller/Channels/ChannelFolderItem.cs

@@ -6,6 +6,7 @@ using System;
 using System.Runtime.Serialization;
 using System.Runtime.Serialization;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
+using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 
 
 namespace MediaBrowser.Controller.Channels
 namespace MediaBrowser.Controller.Channels
@@ -20,6 +21,11 @@ namespace MediaBrowser.Controller.Channels
             return false;
             return false;
         }
         }
 
 
+        public override UnratedItem GetBlockUnratedType()
+        {
+            return UnratedItem.ChannelContent;
+        }
+
         [IgnoreDataMember]
         [IgnoreDataMember]
         public override bool SupportsLocalMetadata
         public override bool SupportsLocalMetadata
         {
         {

+ 2 - 2
MediaBrowser.Controller/Channels/ChannelVideoItem.cs

@@ -42,9 +42,9 @@ namespace MediaBrowser.Controller.Channels
             return ExternalId;
             return ExternalId;
         }
         }
 
 
-        protected override bool GetBlockUnratedValue(UserPolicy config)
+        public override UnratedItem GetBlockUnratedType()
         {
         {
-            return config.BlockUnratedItems.Contains(UnratedItem.ChannelContent);
+            return UnratedItem.ChannelContent;
         }
         }
 
 
         [IgnoreDataMember]
         [IgnoreDataMember]

+ 16 - 18
MediaBrowser.Controller/Entities/Audio/Audio.cs

@@ -27,9 +27,22 @@ namespace MediaBrowser.Controller.Entities.Audio
         public long? Size { get; set; }
         public long? Size { get; set; }
         public string Container { get; set; }
         public string Container { get; set; }
         public int? TotalBitrate { get; set; }
         public int? TotalBitrate { get; set; }
-        public List<string> Tags { get; set; }
         public ExtraType? ExtraType { get; set; }
         public ExtraType? ExtraType { get; set; }
 
 
+        /// <summary>
+        /// Gets or sets the artist.
+        /// </summary>
+        /// <value>The artist.</value>
+        public List<string> Artists { get; set; }
+
+        public List<string> AlbumArtists { get; set; }
+
+        /// <summary>
+        /// Gets or sets the album.
+        /// </summary>
+        /// <value>The album.</value>
+        public string Album { get; set; }
+
         [IgnoreDataMember]
         [IgnoreDataMember]
         public bool IsThemeMedia
         public bool IsThemeMedia
         {
         {
@@ -43,7 +56,6 @@ namespace MediaBrowser.Controller.Entities.Audio
         {
         {
             Artists = new List<string>();
             Artists = new List<string>();
             AlbumArtists = new List<string>();
             AlbumArtists = new List<string>();
-            Tags = new List<string>();
         }
         }
 
 
         [IgnoreDataMember]
         [IgnoreDataMember]
@@ -92,14 +104,6 @@ namespace MediaBrowser.Controller.Entities.Audio
                    locationType != LocationType.Virtual;
                    locationType != LocationType.Virtual;
         }
         }
 
 
-        /// <summary>
-        /// Gets or sets the artist.
-        /// </summary>
-        /// <value>The artist.</value>
-        public List<string> Artists { get; set; }
-
-        public List<string> AlbumArtists { get; set; }
-        
         [IgnoreDataMember]
         [IgnoreDataMember]
         public List<string> AllArtists
         public List<string> AllArtists
         {
         {
@@ -114,12 +118,6 @@ namespace MediaBrowser.Controller.Entities.Audio
             }
             }
         }
         }
 
 
-        /// <summary>
-        /// Gets or sets the album.
-        /// </summary>
-        /// <value>The album.</value>
-        public string Album { get; set; }
-
         [IgnoreDataMember]
         [IgnoreDataMember]
         public MusicAlbum AlbumEntity
         public MusicAlbum AlbumEntity
         {
         {
@@ -173,9 +171,9 @@ namespace MediaBrowser.Controller.Entities.Audio
             return base.CreateUserDataKey();
             return base.CreateUserDataKey();
         }
         }
 
 
-        protected override bool GetBlockUnratedValue(UserPolicy config)
+        public override UnratedItem GetBlockUnratedType()
         {
         {
-            return config.BlockUnratedItems.Contains(UnratedItem.Music);
+            return UnratedItem.Music;
         }
         }
 
 
         public SongInfo GetLookupInfo()
         public SongInfo GetLookupInfo()

+ 7 - 2
MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs

@@ -30,7 +30,7 @@ namespace MediaBrowser.Controller.Entities.Audio
         {
         {
             get
             get
             {
             {
-                return Parents.OfType<MusicArtist>().FirstOrDefault();
+                return GetParents().OfType<MusicArtist>().FirstOrDefault();
             }
             }
         }
         }
 
 
@@ -110,13 +110,18 @@ namespace MediaBrowser.Controller.Entities.Audio
             return config.BlockUnratedItems.Contains(UnratedItem.Music);
             return config.BlockUnratedItems.Contains(UnratedItem.Music);
         }
         }
 
 
+        public override UnratedItem GetBlockUnratedType()
+        {
+            return UnratedItem.Music;
+        }
+
         public AlbumInfo GetLookupInfo()
         public AlbumInfo GetLookupInfo()
         {
         {
             var id = GetItemLookupInfo<AlbumInfo>();
             var id = GetItemLookupInfo<AlbumInfo>();
 
 
             id.AlbumArtists = AlbumArtists;
             id.AlbumArtists = AlbumArtists;
 
 
-            var artist = Parents.OfType<MusicArtist>().FirstOrDefault();
+            var artist = GetParents().OfType<MusicArtist>().FirstOrDefault();
 
 
             if (artist != null)
             if (artist != null)
             {
             {

+ 5 - 0
MediaBrowser.Controller/Entities/Audio/MusicArtist.cs

@@ -138,6 +138,11 @@ namespace MediaBrowser.Controller.Entities.Audio
             return config.BlockUnratedItems.Contains(UnratedItem.Music);
             return config.BlockUnratedItems.Contains(UnratedItem.Music);
         }
         }
 
 
+        public override UnratedItem GetBlockUnratedType()
+        {
+            return UnratedItem.Music;
+        }
+
         public async Task RefreshAllMetadata(MetadataRefreshOptions refreshOptions, IProgress<double> progress, CancellationToken cancellationToken)
         public async Task RefreshAllMetadata(MetadataRefreshOptions refreshOptions, IProgress<double> progress, CancellationToken cancellationToken)
         {
         {
             var items = GetRecursiveChildren().ToList();
             var items = GetRecursiveChildren().ToList();

+ 180 - 75
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -1,5 +1,4 @@
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.IO;
 using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Collections;
 using MediaBrowser.Controller.Collections;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
@@ -24,6 +23,7 @@ using System.Runtime.Serialization;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using CommonIO;
 using CommonIO;
+using MediaBrowser.Model.LiveTv;
 
 
 namespace MediaBrowser.Controller.Entities
 namespace MediaBrowser.Controller.Entities
 {
 {
@@ -34,6 +34,7 @@ namespace MediaBrowser.Controller.Entities
     {
     {
         protected BaseItem()
         protected BaseItem()
         {
         {
+            Tags = new List<string>();
             Genres = new List<string>();
             Genres = new List<string>();
             Studios = new List<string>();
             Studios = new List<string>();
             ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
             ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
@@ -44,7 +45,7 @@ namespace MediaBrowser.Controller.Entities
         /// <summary>
         /// <summary>
         /// The supported image extensions
         /// The supported image extensions
         /// </summary>
         /// </summary>
-        public static readonly string[] SupportedImageExtensions = { ".png", ".jpg", ".jpeg" };
+        public static readonly string[] SupportedImageExtensions = { ".png", ".jpg", ".jpeg", ".tbn", ".gif" };
 
 
         public static readonly List<string> SupportedImageExtensionsList = SupportedImageExtensions.ToList();
         public static readonly List<string> SupportedImageExtensionsList = SupportedImageExtensions.ToList();
 
 
@@ -103,7 +104,8 @@ namespace MediaBrowser.Controller.Entities
         /// Gets or sets the name.
         /// Gets or sets the name.
         /// </summary>
         /// </summary>
         /// <value>The name.</value>
         /// <value>The name.</value>
-        public string Name
+        [IgnoreDataMember]
+        public virtual string Name
         {
         {
             get
             get
             {
             {
@@ -122,14 +124,23 @@ namespace MediaBrowser.Controller.Entities
         /// Gets or sets the id.
         /// Gets or sets the id.
         /// </summary>
         /// </summary>
         /// <value>The id.</value>
         /// <value>The id.</value>
+        [IgnoreDataMember]
         public Guid Id { get; set; }
         public Guid Id { get; set; }
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets a value indicating whether this instance is hd.
         /// Gets or sets a value indicating whether this instance is hd.
         /// </summary>
         /// </summary>
         /// <value><c>true</c> if this instance is hd; otherwise, <c>false</c>.</value>
         /// <value><c>true</c> if this instance is hd; otherwise, <c>false</c>.</value>
+        [IgnoreDataMember]
         public bool? IsHD { get; set; }
         public bool? IsHD { get; set; }
 
 
+        /// <summary>
+        /// Gets or sets the audio.
+        /// </summary>
+        /// <value>The audio.</value>
+        [IgnoreDataMember]
+        public ProgramAudio? Audio { get; set; }
+
         /// <summary>
         /// <summary>
         /// Return the id that should be used to key display prefs for this item.
         /// Return the id that should be used to key display prefs for this item.
         /// Default is based on the type for everything except actual generic folders.
         /// Default is based on the type for everything except actual generic folders.
@@ -149,6 +160,7 @@ namespace MediaBrowser.Controller.Entities
         /// Gets or sets the path.
         /// Gets or sets the path.
         /// </summary>
         /// </summary>
         /// <value>The path.</value>
         /// <value>The path.</value>
+        [IgnoreDataMember]
         public virtual string Path { get; set; }
         public virtual string Path { get; set; }
 
 
         [IgnoreDataMember]
         [IgnoreDataMember]
@@ -173,7 +185,7 @@ namespace MediaBrowser.Controller.Entities
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Id of the program.
+        /// If this content came from an external service, the id of the content on that service
         /// </summary>
         /// </summary>
         [IgnoreDataMember]
         [IgnoreDataMember]
         public string ExternalId
         public string ExternalId
@@ -201,11 +213,6 @@ namespace MediaBrowser.Controller.Entities
             }
             }
         }
         }
 
 
-        public virtual bool IsHiddenFromUser(User user)
-        {
-            return false;
-        }
-
         [IgnoreDataMember]
         [IgnoreDataMember]
         public virtual bool IsOwnedItem
         public virtual bool IsOwnedItem
         {
         {
@@ -325,12 +332,14 @@ namespace MediaBrowser.Controller.Entities
         /// Gets or sets the date created.
         /// Gets or sets the date created.
         /// </summary>
         /// </summary>
         /// <value>The date created.</value>
         /// <value>The date created.</value>
+        [IgnoreDataMember]
         public DateTime DateCreated { get; set; }
         public DateTime DateCreated { get; set; }
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets the date modified.
         /// Gets or sets the date modified.
         /// </summary>
         /// </summary>
         /// <value>The date modified.</value>
         /// <value>The date modified.</value>
+        [IgnoreDataMember]
         public DateTime DateModified { get; set; }
         public DateTime DateModified { get; set; }
 
 
         public DateTime DateLastSaved { get; set; }
         public DateTime DateLastSaved { get; set; }
@@ -407,6 +416,7 @@ namespace MediaBrowser.Controller.Entities
         /// Gets or sets the name of the forced sort.
         /// Gets or sets the name of the forced sort.
         /// </summary>
         /// </summary>
         /// <value>The name of the forced sort.</value>
         /// <value>The name of the forced sort.</value>
+        [IgnoreDataMember]
         public string ForcedSortName
         public string ForcedSortName
         {
         {
             get { return _forcedSortName; }
             get { return _forcedSortName; }
@@ -447,10 +457,7 @@ namespace MediaBrowser.Controller.Entities
         {
         {
             var idString = Id.ToString("N");
             var idString = Id.ToString("N");
 
 
-            if (ConfigurationManager.Configuration.EnableLibraryMetadataSubFolder)
-            {
-                basePath = System.IO.Path.Combine(basePath, "library");
-            }
+            basePath = System.IO.Path.Combine(basePath, "library");
 
 
             return System.IO.Path.Combine(basePath, idString.Substring(0, 2), idString);
             return System.IO.Path.Combine(basePath, idString.Substring(0, 2), idString);
         }
         }
@@ -493,6 +500,7 @@ namespace MediaBrowser.Controller.Entities
             return sortable;
             return sortable;
         }
         }
 
 
+        [IgnoreDataMember]
         public Guid ParentId { get; set; }
         public Guid ParentId { get; set; }
 
 
         /// <summary>
         /// <summary>
@@ -502,15 +510,7 @@ namespace MediaBrowser.Controller.Entities
         [IgnoreDataMember]
         [IgnoreDataMember]
         public Folder Parent
         public Folder Parent
         {
         {
-            get
-            {
-                if (ParentId != Guid.Empty)
-                {
-                    return LibraryManager.GetItemById(ParentId) as Folder;
-                }
-
-                return null;
-            }
+            get { return GetParent() as Folder; }
             set
             set
             {
             {
 
 
@@ -525,16 +525,28 @@ namespace MediaBrowser.Controller.Entities
         [IgnoreDataMember]
         [IgnoreDataMember]
         public IEnumerable<Folder> Parents
         public IEnumerable<Folder> Parents
         {
         {
-            get
+            get { return GetParents().OfType<Folder>(); }
+        }
+
+        public BaseItem GetParent()
+        {
+            if (ParentId != Guid.Empty)
             {
             {
-                var parent = Parent;
+                return LibraryManager.GetItemById(ParentId);
+            }
 
 
-                while (parent != null)
-                {
-                    yield return parent;
+            return null;
+        }
 
 
-                    parent = parent.Parent;
-                }
+        public IEnumerable<BaseItem> GetParents()
+        {
+            var parent = GetParent();
+
+            while (parent != null)
+            {
+                yield return parent;
+
+                parent = parent.GetParent();
             }
             }
         }
         }
 
 
@@ -546,19 +558,20 @@ namespace MediaBrowser.Controller.Entities
         public T FindParent<T>()
         public T FindParent<T>()
             where T : Folder
             where T : Folder
         {
         {
-            return Parents.OfType<T>().FirstOrDefault();
+            return GetParents().OfType<T>().FirstOrDefault();
         }
         }
 
 
         [IgnoreDataMember]
         [IgnoreDataMember]
         public virtual BaseItem DisplayParent
         public virtual BaseItem DisplayParent
         {
         {
-            get { return Parent; }
+            get { return GetParent(); }
         }
         }
 
 
         /// <summary>
         /// <summary>
         /// When the item first debuted. For movies this could be premiere date, episodes would be first aired
         /// When the item first debuted. For movies this could be premiere date, episodes would be first aired
         /// </summary>
         /// </summary>
         /// <value>The premiere date.</value>
         /// <value>The premiere date.</value>
+        [IgnoreDataMember]
         public DateTime? PremiereDate { get; set; }
         public DateTime? PremiereDate { get; set; }
 
 
         /// <summary>
         /// <summary>
@@ -572,31 +585,35 @@ namespace MediaBrowser.Controller.Entities
         /// Gets or sets the display type of the media.
         /// Gets or sets the display type of the media.
         /// </summary>
         /// </summary>
         /// <value>The display type of the media.</value>
         /// <value>The display type of the media.</value>
+        [IgnoreDataMember]
         public string DisplayMediaType { get; set; }
         public string DisplayMediaType { get; set; }
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets the official rating.
         /// Gets or sets the official rating.
         /// </summary>
         /// </summary>
         /// <value>The official rating.</value>
         /// <value>The official rating.</value>
+        [IgnoreDataMember]
         public string OfficialRating { get; set; }
         public string OfficialRating { get; set; }
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets the official rating description.
         /// Gets or sets the official rating description.
         /// </summary>
         /// </summary>
         /// <value>The official rating description.</value>
         /// <value>The official rating description.</value>
+        [IgnoreDataMember]
         public string OfficialRatingDescription { get; set; }
         public string OfficialRatingDescription { get; set; }
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets the custom rating.
         /// Gets or sets the custom rating.
         /// </summary>
         /// </summary>
         /// <value>The custom rating.</value>
         /// <value>The custom rating.</value>
-        //[IgnoreDataMember]
+        [IgnoreDataMember]
         public string CustomRating { get; set; }
         public string CustomRating { get; set; }
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets the overview.
         /// Gets or sets the overview.
         /// </summary>
         /// </summary>
         /// <value>The overview.</value>
         /// <value>The overview.</value>
+        [IgnoreDataMember]
         public string Overview { get; set; }
         public string Overview { get; set; }
 
 
         /// <summary>
         /// <summary>
@@ -609,37 +626,48 @@ namespace MediaBrowser.Controller.Entities
         /// Gets or sets the genres.
         /// Gets or sets the genres.
         /// </summary>
         /// </summary>
         /// <value>The genres.</value>
         /// <value>The genres.</value>
+        [IgnoreDataMember]
         public List<string> Genres { get; set; }
         public List<string> Genres { get; set; }
 
 
+        /// <summary>
+        /// Gets or sets the tags.
+        /// </summary>
+        /// <value>The tags.</value>
+        public List<string> Tags { get; set; }
+
         /// <summary>
         /// <summary>
         /// Gets or sets the home page URL.
         /// Gets or sets the home page URL.
         /// </summary>
         /// </summary>
         /// <value>The home page URL.</value>
         /// <value>The home page URL.</value>
+        [IgnoreDataMember]
         public string HomePageUrl { get; set; }
         public string HomePageUrl { get; set; }
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets the community rating.
         /// Gets or sets the community rating.
         /// </summary>
         /// </summary>
         /// <value>The community rating.</value>
         /// <value>The community rating.</value>
-        //[IgnoreDataMember]
+        [IgnoreDataMember]
         public float? CommunityRating { get; set; }
         public float? CommunityRating { get; set; }
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets the community rating vote count.
         /// Gets or sets the community rating vote count.
         /// </summary>
         /// </summary>
         /// <value>The community rating vote count.</value>
         /// <value>The community rating vote count.</value>
+        [IgnoreDataMember]
         public int? VoteCount { get; set; }
         public int? VoteCount { get; set; }
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets the run time ticks.
         /// Gets or sets the run time ticks.
         /// </summary>
         /// </summary>
         /// <value>The run time ticks.</value>
         /// <value>The run time ticks.</value>
+        [IgnoreDataMember]
         public long? RunTimeTicks { get; set; }
         public long? RunTimeTicks { get; set; }
 
 
         /// <summary>
         /// <summary>
         /// Gets or sets the production year.
         /// Gets or sets the production year.
         /// </summary>
         /// </summary>
         /// <value>The production year.</value>
         /// <value>The production year.</value>
+        [IgnoreDataMember]
         public int? ProductionYear { get; set; }
         public int? ProductionYear { get; set; }
 
 
         /// <summary>
         /// <summary>
@@ -647,19 +675,34 @@ namespace MediaBrowser.Controller.Entities
         /// This could be episode number, album track number, etc.
         /// This could be episode number, album track number, etc.
         /// </summary>
         /// </summary>
         /// <value>The index number.</value>
         /// <value>The index number.</value>
-        //[IgnoreDataMember]
+        [IgnoreDataMember]
         public int? IndexNumber { get; set; }
         public int? IndexNumber { get; set; }
 
 
         /// <summary>
         /// <summary>
         /// For an episode this could be the season number, or for a song this could be the disc number.
         /// For an episode this could be the season number, or for a song this could be the disc number.
         /// </summary>
         /// </summary>
         /// <value>The parent index number.</value>
         /// <value>The parent index number.</value>
+        [IgnoreDataMember]
         public int? ParentIndexNumber { get; set; }
         public int? ParentIndexNumber { get; set; }
 
 
         [IgnoreDataMember]
         [IgnoreDataMember]
-        public virtual string OfficialRatingForComparison
+        public string OfficialRatingForComparison
         {
         {
-            get { return OfficialRating; }
+            get
+            {
+                if (!string.IsNullOrWhiteSpace(OfficialRating))
+                {
+                    return OfficialRating;
+                }
+
+                var parent = DisplayParent;
+                if (parent != null)
+                {
+                    return parent.OfficialRatingForComparison;
+                }
+
+                return null;
+            }
         }
         }
 
 
         [IgnoreDataMember]
         [IgnoreDataMember]
@@ -721,21 +764,21 @@ namespace MediaBrowser.Controller.Entities
             return LibraryManager.ResolvePaths(files, directoryService, null)
             return LibraryManager.ResolvePaths(files, directoryService, null)
                 .OfType<Audio.Audio>()
                 .OfType<Audio.Audio>()
                 .Select(audio =>
                 .Select(audio =>
-            {
-                // Try to retrieve it from the db. If we don't find it, use the resolved version
-                var dbItem = LibraryManager.GetItemById(audio.Id) as Audio.Audio;
-
-                if (dbItem != null)
                 {
                 {
-                    audio = dbItem;
-                }
+                    // Try to retrieve it from the db. If we don't find it, use the resolved version
+                    var dbItem = LibraryManager.GetItemById(audio.Id) as Audio.Audio;
 
 
-                audio.ExtraType = ExtraType.ThemeSong;
+                    if (dbItem != null)
+                    {
+                        audio = dbItem;
+                    }
 
 
-                return audio;
+                    audio.ExtraType = ExtraType.ThemeSong;
 
 
-                // Sort them so that the list can be easily compared for changes
-            }).OrderBy(i => i.Path).ToList();
+                    return audio;
+
+                    // Sort them so that the list can be easily compared for changes
+                }).OrderBy(i => i.Path).ToList();
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -751,21 +794,21 @@ namespace MediaBrowser.Controller.Entities
             return LibraryManager.ResolvePaths(files, directoryService, null)
             return LibraryManager.ResolvePaths(files, directoryService, null)
                 .OfType<Video>()
                 .OfType<Video>()
                 .Select(item =>
                 .Select(item =>
-            {
-                // Try to retrieve it from the db. If we don't find it, use the resolved version
-                var dbItem = LibraryManager.GetItemById(item.Id) as Video;
-
-                if (dbItem != null)
                 {
                 {
-                    item = dbItem;
-                }
+                    // Try to retrieve it from the db. If we don't find it, use the resolved version
+                    var dbItem = LibraryManager.GetItemById(item.Id) as Video;
+
+                    if (dbItem != null)
+                    {
+                        item = dbItem;
+                    }
 
 
-                item.ExtraType = ExtraType.ThemeVideo;
+                    item.ExtraType = ExtraType.ThemeVideo;
 
 
-                return item;
+                    return item;
 
 
-                // Sort them so that the list can be easily compared for changes
-            }).OrderBy(i => i.Path).ToList();
+                    // Sort them so that the list can be easily compared for changes
+                }).OrderBy(i => i.Path).ToList();
         }
         }
 
 
         public Task RefreshMetadata(CancellationToken cancellationToken)
         public Task RefreshMetadata(CancellationToken cancellationToken)
@@ -821,7 +864,7 @@ namespace MediaBrowser.Controller.Entities
         [IgnoreDataMember]
         [IgnoreDataMember]
         protected virtual bool SupportsOwnedItems
         protected virtual bool SupportsOwnedItems
         {
         {
-            get { return IsFolder || Parent != null; }
+            get { return IsFolder || GetParent() != null; }
         }
         }
 
 
         [IgnoreDataMember]
         [IgnoreDataMember]
@@ -846,7 +889,7 @@ namespace MediaBrowser.Controller.Entities
 
 
             var localTrailersChanged = false;
             var localTrailersChanged = false;
 
 
-            if (LocationType == LocationType.FileSystem && Parent != null)
+            if (LocationType == LocationType.FileSystem && GetParent() != null)
             {
             {
                 var hasThemeMedia = this as IHasThemeMedia;
                 var hasThemeMedia = this as IHasThemeMedia;
                 if (hasThemeMedia != null)
                 if (hasThemeMedia != null)
@@ -1008,7 +1051,7 @@ namespace MediaBrowser.Controller.Entities
 
 
             if (string.IsNullOrWhiteSpace(lang))
             if (string.IsNullOrWhiteSpace(lang))
             {
             {
-                lang = Parents
+                lang = GetParents()
                     .Select(i => i.PreferredMetadataLanguage)
                     .Select(i => i.PreferredMetadataLanguage)
                     .FirstOrDefault(i => !string.IsNullOrWhiteSpace(i));
                     .FirstOrDefault(i => !string.IsNullOrWhiteSpace(i));
             }
             }
@@ -1038,7 +1081,7 @@ namespace MediaBrowser.Controller.Entities
 
 
             if (string.IsNullOrWhiteSpace(lang))
             if (string.IsNullOrWhiteSpace(lang))
             {
             {
-                lang = Parents
+                lang = GetParents()
                     .Select(i => i.PreferredMetadataCountryCode)
                     .Select(i => i.PreferredMetadataCountryCode)
                     .FirstOrDefault(i => !string.IsNullOrWhiteSpace(i));
                     .FirstOrDefault(i => !string.IsNullOrWhiteSpace(i));
             }
             }
@@ -1119,6 +1162,23 @@ namespace MediaBrowser.Controller.Entities
         }
         }
 
 
         public int? GetParentalRatingValue()
         public int? GetParentalRatingValue()
+        {
+            var rating = CustomRating;
+
+            if (string.IsNullOrWhiteSpace(rating))
+            {
+                rating = OfficialRating;
+            }
+
+            if (string.IsNullOrWhiteSpace(rating))
+            {
+                return null;
+            }
+
+            return LocalizationManager.GetRatingLevel(rating);
+        }
+
+        public int? GetInheritedParentalRatingValue()
         {
         {
             var rating = CustomRatingForComparison;
             var rating = CustomRatingForComparison;
 
 
@@ -1156,6 +1216,11 @@ namespace MediaBrowser.Controller.Entities
             return true;
             return true;
         }
         }
 
 
+        public virtual UnratedItem GetBlockUnratedType()
+        {
+            return UnratedItem.Other;
+        }
+
         /// <summary>
         /// <summary>
         /// Gets the block unrated value.
         /// Gets the block unrated value.
         /// </summary>
         /// </summary>
@@ -1174,7 +1239,7 @@ namespace MediaBrowser.Controller.Entities
                 return false;
                 return false;
             }
             }
 
 
-            return config.BlockUnratedItems.Contains(UnratedItem.Other);
+            return config.BlockUnratedItems.Contains(GetBlockUnratedType());
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -1206,14 +1271,14 @@ namespace MediaBrowser.Controller.Entities
                 return false;
                 return false;
             }
             }
 
 
-            if (Parents.Any(i => !i.IsVisible(user)))
+            if (GetParents().Any(i => !i.IsVisible(user)))
             {
             {
                 return false;
                 return false;
             }
             }
 
 
             if (checkFolders)
             if (checkFolders)
             {
             {
-                var topParent = Parents.LastOrDefault() ?? this;
+                var topParent = GetParents().LastOrDefault() ?? this;
 
 
                 if (string.IsNullOrWhiteSpace(topParent.Path))
                 if (string.IsNullOrWhiteSpace(topParent.Path))
                 {
                 {
@@ -1307,15 +1372,6 @@ namespace MediaBrowser.Controller.Entities
             return null;
             return null;
         }
         }
 
 
-        /// <summary>
-        /// Adds a person to the item
-        /// </summary>
-        /// <param name="person">The person.</param>
-        /// <exception cref="System.ArgumentNullException"></exception>
-        public void AddPerson(PersonInfo person)
-        {
-        }
-
         /// <summary>
         /// <summary>
         /// Adds a studio to the item
         /// Adds a studio to the item
         /// </summary>
         /// </summary>
@@ -1875,5 +1931,54 @@ namespace MediaBrowser.Controller.Entities
                 DateLastSaved.Ticks.ToString(CultureInfo.InvariantCulture)
                 DateLastSaved.Ticks.ToString(CultureInfo.InvariantCulture)
             };
             };
         }
         }
+
+        public virtual IEnumerable<Guid> GetAncestorIds()
+        {
+            return GetParents().Select(i => i.Id).Concat(LibraryManager.GetCollectionFolders(this).Select(i => i.Id));
+        }
+
+        public BaseItem GetTopParent()
+        {
+            if (IsTopParent)
+            {
+                return this;
+            }
+
+            return GetParents().FirstOrDefault(i => i.IsTopParent);
+        }
+
+        [IgnoreDataMember]
+        public virtual bool IsTopParent
+        {
+            get
+            {
+                if (GetParent() is AggregateFolder || this is Channel || this is BasePluginFolder)
+                {
+                    return true;
+                }
+
+                var view = this as UserView;
+                if (view != null && string.Equals(view.ViewType, CollectionType.LiveTv, StringComparison.OrdinalIgnoreCase))
+                {
+                    return true;
+                }
+
+                return false;
+            }
+        }
+
+        [IgnoreDataMember]
+        public virtual bool SupportsAncestors
+        {
+            get
+            {
+                return true;
+            }
+        }
+
+        public virtual IEnumerable<Guid> GetIdsForAncestorQuery()
+        {
+            return new[] { Id };
+        }
     }
     }
-}
+}

+ 3 - 14
MediaBrowser.Controller/Entities/Book.cs

@@ -17,19 +17,8 @@ namespace MediaBrowser.Controller.Entities
             }
             }
         }
         }
 
 
-        /// <summary>
-        /// Gets or sets the tags.
-        /// </summary>
-        /// <value>The tags.</value>
-        public List<string> Tags { get; set; }
-
         public string SeriesName { get; set; }
         public string SeriesName { get; set; }
 
 
-        public Book()
-        {
-            Tags = new List<string>();
-        }
-
         public override bool CanDownload()
         public override bool CanDownload()
         {
         {
             var locationType = LocationType;
             var locationType = LocationType;
@@ -37,9 +26,9 @@ namespace MediaBrowser.Controller.Entities
                    locationType != LocationType.Virtual;
                    locationType != LocationType.Virtual;
         }
         }
 
 
-        protected override bool GetBlockUnratedValue(UserPolicy config)
+        public override UnratedItem GetBlockUnratedType()
         {
         {
-            return config.BlockUnratedItems.Contains(UnratedItem.Book);
+            return UnratedItem.Book;
         }
         }
 
 
         public BookInfo GetLookupInfo()
         public BookInfo GetLookupInfo()
@@ -48,7 +37,7 @@ namespace MediaBrowser.Controller.Entities
 
 
             if (string.IsNullOrEmpty(SeriesName))
             if (string.IsNullOrEmpty(SeriesName))
             {
             {
-                info.SeriesName = Parents.Select(i => i.Name).FirstOrDefault();
+                info.SeriesName = GetParents().Select(i => i.Name).FirstOrDefault();
             }
             }
             else
             else
             {
             {

+ 8 - 7
MediaBrowser.Controller/Entities/CollectionFolder.cs

@@ -181,9 +181,7 @@ namespace MediaBrowser.Controller.Entities
         }
         }
         private List<LinkedChild> GetLinkedChildrenInternal()
         private List<LinkedChild> GetLinkedChildrenInternal()
         {
         {
-            return LibraryManager.RootFolder.Children
-                .OfType<Folder>()
-                .Where(i => i.Path != null && PhysicalLocations.Contains(i.Path, StringComparer.OrdinalIgnoreCase))
+            return GetPhysicalParents()
                 .SelectMany(c => c.LinkedChildren)
                 .SelectMany(c => c.LinkedChildren)
                 .ToList();
                 .ToList();
         }
         }
@@ -199,11 +197,14 @@ namespace MediaBrowser.Controller.Entities
 
 
         private IEnumerable<BaseItem> GetActualChildren()
         private IEnumerable<BaseItem> GetActualChildren()
         {
         {
-            return
-                LibraryManager.RootFolder.Children
+            return GetPhysicalParents().SelectMany(c => c.Children);
+        }
+
+        public IEnumerable<Folder> GetPhysicalParents()
+        {
+            return LibraryManager.RootFolder.Children
                 .OfType<Folder>()
                 .OfType<Folder>()
-                .Where(i => i.Path != null && PhysicalLocations.Contains(i.Path, StringComparer.OrdinalIgnoreCase))
-                .SelectMany(c => c.Children);
+                .Where(i => i.Path != null && PhysicalLocations.Contains(i.Path, StringComparer.OrdinalIgnoreCase));
         }
         }
 
 
         [IgnoreDataMember]
         [IgnoreDataMember]

+ 55 - 77
MediaBrowser.Controller/Entities/Folder.cs

@@ -28,7 +28,6 @@ namespace MediaBrowser.Controller.Entities
 
 
         public List<Guid> ThemeSongIds { get; set; }
         public List<Guid> ThemeSongIds { get; set; }
         public List<Guid> ThemeVideoIds { get; set; }
         public List<Guid> ThemeVideoIds { get; set; }
-        public List<string> Tags { get; set; }
 
 
         public Folder()
         public Folder()
         {
         {
@@ -36,7 +35,6 @@ namespace MediaBrowser.Controller.Entities
 
 
             ThemeSongIds = new List<Guid>();
             ThemeSongIds = new List<Guid>();
             ThemeVideoIds = new List<Guid>();
             ThemeVideoIds = new List<Guid>();
-            Tags = new List<string>();
         }
         }
 
 
         [IgnoreDataMember]
         [IgnoreDataMember]
@@ -151,7 +149,15 @@ namespace MediaBrowser.Controller.Entities
 
 
             await LibraryManager.CreateItem(item, cancellationToken).ConfigureAwait(false);
             await LibraryManager.CreateItem(item, cancellationToken).ConfigureAwait(false);
 
 
-            await ItemRepository.SaveChildren(Id, ActualChildren.Select(i => i.Id).ToList(), cancellationToken).ConfigureAwait(false);
+            if (!EnableNewFolderQuerying())
+            {
+                await ItemRepository.SaveChildren(Id, ActualChildren.Select(i => i.Id).ToList(), cancellationToken).ConfigureAwait(false);
+            }
+        }
+
+        private static bool EnableNewFolderQuerying()
+        {
+            return ConfigurationManager.Configuration.MigrationVersion >= 1;
         }
         }
 
 
         protected void AddChildrenInternal(IEnumerable<BaseItem> children)
         protected void AddChildrenInternal(IEnumerable<BaseItem> children)
@@ -196,21 +202,6 @@ namespace MediaBrowser.Controller.Entities
             }
             }
         }
         }
 
 
-        [IgnoreDataMember]
-        public override string OfficialRatingForComparison
-        {
-            get
-            {
-                // Never want folders to be blocked by "BlockNotRated"
-                if (this is Series)
-                {
-                    return base.OfficialRatingForComparison;
-                }
-
-                return !string.IsNullOrWhiteSpace(base.OfficialRatingForComparison) ? base.OfficialRatingForComparison : "None";
-            }
-        }
-
         /// <summary>
         /// <summary>
         /// Removes the child.
         /// Removes the child.
         /// </summary>
         /// </summary>
@@ -224,7 +215,12 @@ namespace MediaBrowser.Controller.Entities
 
 
             item.SetParent(null);
             item.SetParent(null);
 
 
-            return ItemRepository.SaveChildren(Id, ActualChildren.Select(i => i.Id).ToList(), cancellationToken);
+            if (!EnableNewFolderQuerying())
+            {
+                return ItemRepository.SaveChildren(Id, ActualChildren.Select(i => i.Id).ToList(), cancellationToken);
+            }
+
+            return Task.FromResult(true);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -457,32 +453,25 @@ namespace MediaBrowser.Controller.Entities
                 {
                 {
                     BaseItem currentChild;
                     BaseItem currentChild;
 
 
-                    if (currentChildren.TryGetValue(child.Id, out currentChild))
+                    if (currentChildren.TryGetValue(child.Id, out currentChild) && IsValidFromResolver(currentChild, child))
                     {
                     {
-                        if (IsValidFromResolver(currentChild, child))
+                        var currentChildLocationType = currentChild.LocationType;
+                        if (currentChildLocationType != LocationType.Remote &&
+                            currentChildLocationType != LocationType.Virtual)
                         {
                         {
-                            var currentChildLocationType = currentChild.LocationType;
-                            if (currentChildLocationType != LocationType.Remote &&
-                                currentChildLocationType != LocationType.Virtual)
-                            {
-                                currentChild.DateModified = child.DateModified;
-                            }
-
-                            await UpdateIsOffline(currentChild, false).ConfigureAwait(false);
-                            validChildren.Add(currentChild);
-                        }
-                        else
-                        {
-                            newItems.Add(child);
-                            validChildren.Add(child);
+                            currentChild.DateModified = child.DateModified;
                         }
                         }
+
+                        await UpdateIsOffline(currentChild, false).ConfigureAwait(false);
+                        validChildren.Add(currentChild);
+
+                        continue;
                     }
                     }
-                    else
-                    {
-                        // Brand new item - needs to be added
-                        newItems.Add(child);
-                        validChildren.Add(child);
-                    }
+
+                    // Brand new item - needs to be added
+                    child.SetParent(this);
+                    newItems.Add(child);
+                    validChildren.Add(child);
                 }
                 }
 
 
                 // If any items were added or removed....
                 // If any items were added or removed....
@@ -508,7 +497,6 @@ namespace MediaBrowser.Controller.Entities
                         }
                         }
                         else
                         else
                         {
                         {
-                            await UpdateIsOffline(item, false).ConfigureAwait(false);
                             actualRemovals.Add(item);
                             actualRemovals.Add(item);
                         }
                         }
                     }
                     }
@@ -519,6 +507,11 @@ namespace MediaBrowser.Controller.Entities
 
 
                         foreach (var item in actualRemovals)
                         foreach (var item in actualRemovals)
                         {
                         {
+                            Logger.Debug("Removed item: " + item.Path);
+
+                            item.SetParent(null);
+                            item.IsOffline = false;
+                            await LibraryManager.DeleteItem(item, new DeleteOptions { DeleteFileLocation = false }).ConfigureAwait(false);
                             LibraryManager.ReportItemRemoved(item);
                             LibraryManager.ReportItemRemoved(item);
                         }
                         }
                     }
                     }
@@ -527,7 +520,10 @@ namespace MediaBrowser.Controller.Entities
 
 
                     AddChildrenInternal(newItems);
                     AddChildrenInternal(newItems);
 
 
-                    await ItemRepository.SaveChildren(Id, ActualChildren.Select(i => i.Id).ToList(), cancellationToken).ConfigureAwait(false);
+                    if (!EnableNewFolderQuerying())
+                    {
+                        await ItemRepository.SaveChildren(Id, ActualChildren.Select(i => i.Id).ToList(), cancellationToken).ConfigureAwait(false);
+                    }
                 }
                 }
             }
             }
 
 
@@ -721,7 +717,7 @@ namespace MediaBrowser.Controller.Entities
                 return true;
                 return true;
             }
             }
 
 
-            return ContainsPath(LibraryManager.GetVirtualFolders(), originalPath);
+            return false;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -757,19 +753,16 @@ namespace MediaBrowser.Controller.Entities
         /// <returns>IEnumerable{BaseItem}.</returns>
         /// <returns>IEnumerable{BaseItem}.</returns>
         protected IEnumerable<BaseItem> GetCachedChildren()
         protected IEnumerable<BaseItem> GetCachedChildren()
         {
         {
-            if (ConfigurationManager.Configuration.DisableStartupScan)
+            if (EnableNewFolderQuerying())
             {
             {
-                return ItemRepository.GetChildrenItems(Id).Select(RetrieveChild).Where(i => i != null);
-                //return ItemRepository.GetItems(new InternalItemsQuery
-                //{
-                //    ParentId = Id
+                return ItemRepository.GetItemList(new InternalItemsQuery
+                {
+                    ParentId = Id
 
 
-                //}).Items.Select(RetrieveChild).Where(i => i != null);
-            }
-            else
-            {
-                return ItemRepository.GetChildrenItems(Id).Select(RetrieveChild).Where(i => i != null);
+                }).Select(RetrieveChild).Where(i => i != null);
             }
             }
+
+            return ItemRepository.GetChildrenItems(Id).Select(RetrieveChild).Where(i => i != null);
         }
         }
 
 
         private BaseItem RetrieveChild(BaseItem child)
         private BaseItem RetrieveChild(BaseItem child)
@@ -832,19 +825,7 @@ namespace MediaBrowser.Controller.Entities
             return UserViewBuilder.PostFilterAndSort(items, this, null, query, LibraryManager);
             return UserViewBuilder.PostFilterAndSort(items, this, null, query, LibraryManager);
         }
         }
 
 
-        /// <summary>
-        /// Gets allowed children of an item
-        /// </summary>
-        /// <param name="user">The user.</param>
-        /// <param name="includeLinkedChildren">if set to <c>true</c> [include linked children].</param>
-        /// <returns>IEnumerable{BaseItem}.</returns>
-        /// <exception cref="System.ArgumentNullException"></exception>
         public virtual IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren)
         public virtual IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren)
-        {
-            return GetChildren(user, includeLinkedChildren, false);
-        }
-
-        internal IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren, bool includeHidden)
         {
         {
             if (user == null)
             if (user == null)
             {
             {
@@ -856,7 +837,7 @@ namespace MediaBrowser.Controller.Entities
 
 
             var result = new Dictionary<Guid, BaseItem>();
             var result = new Dictionary<Guid, BaseItem>();
 
 
-            AddChildren(user, includeLinkedChildren, result, includeHidden, false, null);
+            AddChildren(user, includeLinkedChildren, result, false, null);
 
 
             return result.Values;
             return result.Values;
         }
         }
@@ -872,29 +853,25 @@ namespace MediaBrowser.Controller.Entities
         /// <param name="user">The user.</param>
         /// <param name="user">The user.</param>
         /// <param name="includeLinkedChildren">if set to <c>true</c> [include linked children].</param>
         /// <param name="includeLinkedChildren">if set to <c>true</c> [include linked children].</param>
         /// <param name="result">The result.</param>
         /// <param name="result">The result.</param>
-        /// <param name="includeHidden">if set to <c>true</c> [include hidden].</param>
         /// <param name="recursive">if set to <c>true</c> [recursive].</param>
         /// <param name="recursive">if set to <c>true</c> [recursive].</param>
         /// <param name="filter">The filter.</param>
         /// <param name="filter">The filter.</param>
         /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
         /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
-        private void AddChildren(User user, bool includeLinkedChildren, Dictionary<Guid, BaseItem> result, bool includeHidden, bool recursive, Func<BaseItem, bool> filter)
+        private void AddChildren(User user, bool includeLinkedChildren, Dictionary<Guid, BaseItem> result, bool recursive, Func<BaseItem, bool> filter)
         {
         {
             foreach (var child in GetEligibleChildrenForRecursiveChildren(user))
             foreach (var child in GetEligibleChildrenForRecursiveChildren(user))
             {
             {
                 if (child.IsVisible(user))
                 if (child.IsVisible(user))
                 {
                 {
-                    if (includeHidden || !child.IsHiddenFromUser(user))
+                    if (filter == null || filter(child))
                     {
                     {
-                        if (filter == null || filter(child))
-                        {
-                            result[child.Id] = child;
-                        }
+                        result[child.Id] = child;
                     }
                     }
 
 
                     if (recursive && child.IsFolder)
                     if (recursive && child.IsFolder)
                     {
                     {
                         var folder = (Folder)child;
                         var folder = (Folder)child;
 
 
-                        folder.AddChildren(user, includeLinkedChildren, result, includeHidden, true, filter);
+                        folder.AddChildren(user, includeLinkedChildren, result, true, filter);
                     }
                     }
                 }
                 }
             }
             }
@@ -935,7 +912,7 @@ namespace MediaBrowser.Controller.Entities
 
 
             var result = new Dictionary<Guid, BaseItem>();
             var result = new Dictionary<Guid, BaseItem>();
 
 
-            AddChildren(user, true, result, false, true, filter);
+            AddChildren(user, true, result, true, filter);
 
 
             return result.Values;
             return result.Values;
         }
         }
@@ -1184,6 +1161,7 @@ namespace MediaBrowser.Controller.Entities
                 Recursive = true,
                 Recursive = true,
                 IsFolder = false,
                 IsFolder = false,
                 IsUnaired = false
                 IsUnaired = false
+
             };
             };
 
 
             if (!user.Configuration.DisplayMissingEpisodes)
             if (!user.Configuration.DisplayMissingEpisodes)
@@ -1322,4 +1300,4 @@ namespace MediaBrowser.Controller.Entities
             }
             }
         }
         }
     }
     }
-}
+}

+ 2 - 9
MediaBrowser.Controller/Entities/Game.cs

@@ -21,7 +21,6 @@ namespace MediaBrowser.Controller.Entities
             RemoteTrailerIds = new List<Guid>();
             RemoteTrailerIds = new List<Guid>();
             ThemeSongIds = new List<Guid>();
             ThemeSongIds = new List<Guid>();
             ThemeVideoIds = new List<Guid>();
             ThemeVideoIds = new List<Guid>();
-            Tags = new List<string>();
         }
         }
 
 
         public List<Guid> LocalTrailerIds { get; set; }
         public List<Guid> LocalTrailerIds { get; set; }
@@ -34,12 +33,6 @@ namespace MediaBrowser.Controller.Entities
                    locationType != LocationType.Virtual;
                    locationType != LocationType.Virtual;
         }
         }
 
 
-        /// <summary>
-        /// Gets or sets the tags.
-        /// </summary>
-        /// <value>The tags.</value>
-        public List<string> Tags { get; set; }
-
         /// <summary>
         /// <summary>
         /// Gets or sets the remote trailers.
         /// Gets or sets the remote trailers.
         /// </summary>
         /// </summary>
@@ -105,9 +98,9 @@ namespace MediaBrowser.Controller.Entities
             return base.GetDeletePaths();
             return base.GetDeletePaths();
         }
         }
 
 
-        protected override bool GetBlockUnratedValue(UserPolicy config)
+        public override UnratedItem GetBlockUnratedType()
         {
         {
-            return config.BlockUnratedItems.Contains(UnratedItem.Game);
+            return UnratedItem.Game;
         }
         }
 
 
         public GameInfo GetLookupInfo()
         public GameInfo GetLookupInfo()

+ 5 - 0
MediaBrowser.Controller/Entities/GameSystem.cs

@@ -50,6 +50,11 @@ namespace MediaBrowser.Controller.Entities
             return false;
             return false;
         }
         }
 
 
+        public override UnratedItem GetBlockUnratedType()
+        {
+            return UnratedItem.Game;
+        }
+
         public GameSystemInfo GetLookupInfo()
         public GameSystemInfo GetLookupInfo()
         {
         {
             var id = GetItemLookupInfo<GameSystemInfo>();
             var id = GetItemLookupInfo<GameSystemInfo>();

+ 5 - 0
MediaBrowser.Controller/Entities/ICollectionFolder.cs

@@ -16,6 +16,11 @@ namespace MediaBrowser.Controller.Entities
         IEnumerable<string> PhysicalLocations { get; }
         IEnumerable<string> PhysicalLocations { get; }
     }
     }
 
 
+    public interface ISupportsUserSpecificView
+    {
+        bool EnableUserSpecificView { get; }
+    }
+
     public static class CollectionFolderExtensions
     public static class CollectionFolderExtensions
     {
     {
         public static string GetViewType(this ICollectionFolder folder, User user)
         public static string GetViewType(this ICollectionFolder folder, User user)

+ 18 - 0
MediaBrowser.Controller/Entities/IHiddenFromDisplay.cs

@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Controller.Entities
+{
+    public interface IHiddenFromDisplay
+    {
+        /// <summary>
+        /// Determines whether the specified user is hidden.
+        /// </summary>
+        /// <param name="user">The user.</param>
+        /// <returns><c>true</c> if the specified user is hidden; otherwise, <c>false</c>.</returns>
+        bool IsHiddenFromUser(User user);
+    }
+}

+ 35 - 0
MediaBrowser.Controller/Entities/InternalItemsQuery.cs

@@ -1,6 +1,7 @@
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using MediaBrowser.Model.Configuration;
 
 
 namespace MediaBrowser.Controller.Entities
 namespace MediaBrowser.Controller.Entities
 {
 {
@@ -30,6 +31,7 @@ namespace MediaBrowser.Controller.Entities
         public string[] MediaTypes { get; set; }
         public string[] MediaTypes { get; set; }
         public string[] IncludeItemTypes { get; set; }
         public string[] IncludeItemTypes { get; set; }
         public string[] ExcludeItemTypes { get; set; }
         public string[] ExcludeItemTypes { get; set; }
+        public string[] ExcludeTags { get; set; }
         public string[] Genres { get; set; }
         public string[] Genres { get; set; }
 
 
         public bool? IsMissing { get; set; }
         public bool? IsMissing { get; set; }
@@ -69,12 +71,15 @@ namespace MediaBrowser.Controller.Entities
 
 
         public string[] Studios { get; set; }
         public string[] Studios { get; set; }
         public string[] StudioIds { get; set; }
         public string[] StudioIds { get; set; }
+        public string[] GenreIds { get; set; }
         public ImageType[] ImageTypes { get; set; }
         public ImageType[] ImageTypes { get; set; }
         public VideoType[] VideoTypes { get; set; }
         public VideoType[] VideoTypes { get; set; }
+        public UnratedItem[] BlockUnratedItems { get; set; }
         public int[] Years { get; set; }
         public int[] Years { get; set; }
         public string[] Tags { get; set; }
         public string[] Tags { get; set; }
         public string[] OfficialRatings { get; set; }
         public string[] OfficialRatings { get; set; }
 
 
+        public DateTime? MinPremiereDate { get; set; }
         public DateTime? MinStartDate { get; set; }
         public DateTime? MinStartDate { get; set; }
         public DateTime? MaxStartDate { get; set; }
         public DateTime? MaxStartDate { get; set; }
         public DateTime? MinEndDate { get; set; }
         public DateTime? MinEndDate { get; set; }
@@ -87,6 +92,7 @@ namespace MediaBrowser.Controller.Entities
 
 
         public int? MinPlayers { get; set; }
         public int? MinPlayers { get; set; }
         public int? MaxPlayers { get; set; }
         public int? MaxPlayers { get; set; }
+        public int? MinIndexNumber { get; set; }
         public double? MinCriticRating { get; set; }
         public double? MinCriticRating { get; set; }
         public double? MinCommunityRating { get; set; }
         public double? MinCommunityRating { get; set; }
        
        
@@ -101,9 +107,14 @@ namespace MediaBrowser.Controller.Entities
         public LocationType? LocationType { get; set; }
         public LocationType? LocationType { get; set; }
 
 
         public Guid? ParentId { get; set; }
         public Guid? ParentId { get; set; }
+        public string[] AncestorIds { get; set; }
+        public string[] TopParentIds { get; set; }
+
+        public LocationType[] ExcludeLocationTypes { get; set; }
         
         
         public InternalItemsQuery()
         public InternalItemsQuery()
         {
         {
+            BlockUnratedItems = new UnratedItem[] { };
             Tags = new string[] { };
             Tags = new string[] { };
             OfficialRatings = new string[] { };
             OfficialRatings = new string[] { };
             SortBy = new string[] { };
             SortBy = new string[] { };
@@ -113,6 +124,7 @@ namespace MediaBrowser.Controller.Entities
             Genres = new string[] { };
             Genres = new string[] { };
             Studios = new string[] { };
             Studios = new string[] { };
             StudioIds = new string[] { };
             StudioIds = new string[] { };
+            GenreIds = new string[] { };
             ImageTypes = new ImageType[] { };
             ImageTypes = new ImageType[] { };
             VideoTypes = new VideoType[] { };
             VideoTypes = new VideoType[] { };
             Years = new int[] { };
             Years = new int[] { };
@@ -120,6 +132,29 @@ namespace MediaBrowser.Controller.Entities
             PersonIds = new string[] { };
             PersonIds = new string[] { };
             ChannelIds = new string[] { };
             ChannelIds = new string[] { };
             ItemIds = new string[] { };
             ItemIds = new string[] { };
+            AncestorIds = new string[] { };
+            TopParentIds = new string[] { };
+            ExcludeTags = new string[] { };
+            ExcludeLocationTypes = new LocationType[] { };
+        }
+
+        public InternalItemsQuery(User user)
+            : this()
+        {
+            if (user != null)
+            {
+                var policy = user.Policy;
+                MaxParentalRating = policy.MaxParentalRating;
+
+                if (policy.MaxParentalRating.HasValue)
+                {
+                    BlockUnratedItems = policy.BlockUnratedItems;
+                }
+
+                ExcludeTags = policy.BlockedTags;
+                
+                User = user;
+            }
         }
         }
     }
     }
 }
 }

+ 6 - 31
MediaBrowser.Controller/Entities/Movies/BoxSet.cs

@@ -8,15 +8,13 @@ using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using System.Runtime.Serialization;
 using System.Runtime.Serialization;
-using System.Threading;
-using System.Threading.Tasks;
 
 
 namespace MediaBrowser.Controller.Entities.Movies
 namespace MediaBrowser.Controller.Entities.Movies
 {
 {
     /// <summary>
     /// <summary>
     /// Class BoxSet
     /// Class BoxSet
     /// </summary>
     /// </summary>
-    public class BoxSet : Folder, IHasTrailers, IHasKeywords, IHasDisplayOrder, IHasLookupInfo<BoxSetInfo>, IMetadataContainer, IHasShares
+    public class BoxSet : Folder, IHasTrailers, IHasKeywords, IHasDisplayOrder, IHasLookupInfo<BoxSetInfo>, IHasShares
     {
     {
         public List<Share> Shares { get; set; }
         public List<Share> Shares { get; set; }
 
 
@@ -65,6 +63,11 @@ namespace MediaBrowser.Controller.Entities.Movies
             return config.BlockUnratedItems.Contains(UnratedItem.Movie);
             return config.BlockUnratedItems.Contains(UnratedItem.Movie);
         }
         }
 
 
+        public override UnratedItem GetBlockUnratedType()
+        {
+            return UnratedItem.Movie;
+        }
+
         [IgnoreDataMember]
         [IgnoreDataMember]
         public override bool IsPreSorted
         public override bool IsPreSorted
         {
         {
@@ -154,34 +157,6 @@ namespace MediaBrowser.Controller.Entities.Movies
             return GetItemLookupInfo<BoxSetInfo>();
             return GetItemLookupInfo<BoxSetInfo>();
         }
         }
 
 
-        public async Task RefreshAllMetadata(MetadataRefreshOptions refreshOptions, IProgress<double> progress, CancellationToken cancellationToken)
-        {
-            // Refresh bottom up, children first, then the boxset
-            // By then hopefully the  movies within will have Tmdb collection values
-            var items = GetRecursiveChildren().ToList();
-
-            var totalItems = items.Count;
-            var numComplete = 0;
-
-            // Refresh songs
-            foreach (var item in items)
-            {
-                cancellationToken.ThrowIfCancellationRequested();
-
-                await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
-
-                numComplete++;
-                double percent = numComplete;
-                percent /= totalItems;
-                progress.Report(percent * 100);
-            }
-
-            // Refresh current item
-            await RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
-
-            progress.Report(100);
-        }
-
         public override bool IsVisible(User user)
         public override bool IsVisible(User user)
         {
         {
             var userId = user.Id.ToString("N");
             var userId = user.Id.ToString("N");

+ 3 - 3
MediaBrowser.Controller/Entities/Movies/Movie.cs

@@ -130,7 +130,7 @@ namespace MediaBrowser.Controller.Entities.Movies
 
 
             // Must have a parent to have special features
             // Must have a parent to have special features
             // In other words, it must be part of the Parent/Child tree
             // In other words, it must be part of the Parent/Child tree
-            if (LocationType == LocationType.FileSystem && Parent != null && !IsInMixedFolder)
+            if (LocationType == LocationType.FileSystem && GetParent() != null && !IsInMixedFolder)
             {
             {
                 var specialFeaturesChanged = await RefreshSpecialFeatures(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
                 var specialFeaturesChanged = await RefreshSpecialFeatures(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
 
 
@@ -159,9 +159,9 @@ namespace MediaBrowser.Controller.Entities.Movies
             return itemsChanged;
             return itemsChanged;
         }
         }
 
 
-        protected override bool GetBlockUnratedValue(UserPolicy config)
+        public override UnratedItem GetBlockUnratedType()
         {
         {
-            return config.BlockUnratedItems.Contains(UnratedItem.Movie);
+            return UnratedItem.Movie;
         }
         }
 
 
         public MovieInfo GetLookupInfo()
         public MovieInfo GetLookupInfo()

+ 2 - 2
MediaBrowser.Controller/Entities/MusicVideo.cs

@@ -56,9 +56,9 @@ namespace MediaBrowser.Controller.Entities
             return this.GetProviderId(MetadataProviders.Tmdb) ?? this.GetProviderId(MetadataProviders.Imdb) ?? base.CreateUserDataKey();
             return this.GetProviderId(MetadataProviders.Tmdb) ?? this.GetProviderId(MetadataProviders.Imdb) ?? base.CreateUserDataKey();
         }
         }
 
 
-        protected override bool GetBlockUnratedValue(UserPolicy config)
+        public override UnratedItem GetBlockUnratedType()
         {
         {
-            return config.BlockUnratedItems.Contains(UnratedItem.Music);
+            return UnratedItem.Music;
         }
         }
 
 
         public MusicVideoInfo GetLookupInfo()
         public MusicVideoInfo GetLookupInfo()

+ 9 - 0
MediaBrowser.Controller/Entities/Person.cs

@@ -101,6 +101,15 @@ namespace MediaBrowser.Controller.Entities
                 return false;
                 return false;
             }
             }
         }
         }
+
+        [IgnoreDataMember]
+        public override bool SupportsAncestors
+        {
+            get
+            {
+                return false;
+            }
+        }
     }
     }
 
 
     /// <summary>
     /// <summary>

+ 1 - 8
MediaBrowser.Controller/Entities/Photo.cs

@@ -9,12 +9,10 @@ namespace MediaBrowser.Controller.Entities
 {
 {
     public class Photo : BaseItem, IHasTags, IHasTaglines
     public class Photo : BaseItem, IHasTags, IHasTaglines
     {
     {
-        public List<string> Tags { get; set; }
         public List<string> Taglines { get; set; }
         public List<string> Taglines { get; set; }
 
 
         public Photo()
         public Photo()
         {
         {
-            Tags = new List<string>();
             Taglines = new List<string>();
             Taglines = new List<string>();
         }
         }
 
 
@@ -51,7 +49,7 @@ namespace MediaBrowser.Controller.Entities
         {
         {
             get
             get
             {
             {
-                return Parents.OfType<PhotoAlbum>().FirstOrDefault();
+                return GetParents().OfType<PhotoAlbum>().FirstOrDefault();
             }
             }
         }
         }
 
 
@@ -70,10 +68,5 @@ namespace MediaBrowser.Controller.Entities
         public double? Longitude { get; set; }
         public double? Longitude { get; set; }
         public double? Altitude { get; set; }
         public double? Altitude { get; set; }
         public int? IsoSpeedRating { get; set; }
         public int? IsoSpeedRating { get; set; }
-
-        protected override bool GetBlockUnratedValue(UserPolicy config)
-        {
-            return config.BlockUnratedItems.Contains(UnratedItem.Other);
-        }
     }
     }
 }
 }

+ 2 - 27
MediaBrowser.Controller/Entities/PhotoAlbum.cs

@@ -9,8 +9,9 @@ using System.Threading.Tasks;
 
 
 namespace MediaBrowser.Controller.Entities
 namespace MediaBrowser.Controller.Entities
 {
 {
-    public class PhotoAlbum : Folder, IMetadataContainer
+    public class PhotoAlbum : Folder
     {
     {
+        [IgnoreDataMember]
         public override bool SupportsLocalMetadata
         public override bool SupportsLocalMetadata
         {
         {
             get
             get
@@ -32,31 +33,5 @@ namespace MediaBrowser.Controller.Entities
         {
         {
             return config.BlockUnratedItems.Contains(UnratedItem.Other);
             return config.BlockUnratedItems.Contains(UnratedItem.Other);
         }
         }
-
-        public async Task RefreshAllMetadata(MetadataRefreshOptions refreshOptions, IProgress<double> progress, CancellationToken cancellationToken)
-        {
-            var items = GetRecursiveChildren().ToList();
-
-            var totalItems = items.Count;
-            var numComplete = 0;
-
-            // Refresh songs
-            foreach (var item in items)
-            {
-                cancellationToken.ThrowIfCancellationRequested();
-
-                await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
-
-                numComplete++;
-                double percent = numComplete;
-                percent /= totalItems;
-                progress.Report(percent * 100);
-            }
-
-            // Refresh current item
-            await RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
-
-            progress.Report(100);
-        }
     }
     }
 }
 }

+ 0 - 7
MediaBrowser.Controller/Entities/Studio.cs

@@ -10,13 +10,6 @@ namespace MediaBrowser.Controller.Entities
     /// </summary>
     /// </summary>
     public class Studio : BaseItem, IItemByName, IHasTags
     public class Studio : BaseItem, IItemByName, IHasTags
     {
     {
-        public List<string> Tags { get; set; }
-
-        public Studio()
-        {
-            Tags = new List<string>();
-        }
-
         /// <summary>
         /// <summary>
         /// Gets the user data key.
         /// Gets the user data key.
         /// </summary>
         /// </summary>

+ 17 - 16
MediaBrowser.Controller/Entities/TV/Episode.cs

@@ -95,7 +95,7 @@ namespace MediaBrowser.Controller.Entities.TV
         {
         {
             get
             get
             {
             {
-                return Season ?? Parent;
+                return Season ?? GetParent();
             }
             }
         }
         }
 
 
@@ -115,19 +115,6 @@ namespace MediaBrowser.Controller.Entities.TV
             return base.CreateUserDataKey();
             return base.CreateUserDataKey();
         }
         }
 
 
-        /// <summary>
-        /// Our rating comes from our series
-        /// </summary>
-        [IgnoreDataMember]
-        public override string OfficialRatingForComparison
-        {
-            get
-            {
-                var series = Series;
-                return series != null ? series.OfficialRatingForComparison : base.OfficialRatingForComparison;
-            }
-        }
-
         /// <summary>
         /// <summary>
         /// This Episode's Series Instance
         /// This Episode's Series Instance
         /// </summary>
         /// </summary>
@@ -265,14 +252,28 @@ namespace MediaBrowser.Controller.Entities.TV
             }
             }
         }
         }
 
 
+        public override IEnumerable<Guid> GetAncestorIds()
+        {
+            var list = base.GetAncestorIds().ToList();
+
+            var seasonId = SeasonId;
+
+            if (seasonId.HasValue && !list.Contains(seasonId.Value))
+            {
+                list.Add(seasonId.Value);
+            }
+
+            return list;
+        }
+
         public override IEnumerable<string> GetDeletePaths()
         public override IEnumerable<string> GetDeletePaths()
         {
         {
             return new[] { Path };
             return new[] { Path };
         }
         }
 
 
-        protected override bool GetBlockUnratedValue(UserPolicy config)
+        public override UnratedItem GetBlockUnratedType()
         {
         {
-            return config.BlockUnratedItems.Contains(UnratedItem.Series);
+            return UnratedItem.Series;
         }
         }
 
 
         public EpisodeInfo GetLookupInfo()
         public EpisodeInfo GetLookupInfo()

+ 7 - 14
MediaBrowser.Controller/Entities/TV/Season.cs

@@ -6,6 +6,7 @@ using MoreLinq;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using System.Runtime.Serialization;
 using System.Runtime.Serialization;
+using MediaBrowser.Model.Configuration;
 
 
 namespace MediaBrowser.Controller.Entities.TV
 namespace MediaBrowser.Controller.Entities.TV
 {
 {
@@ -32,7 +33,7 @@ namespace MediaBrowser.Controller.Entities.TV
         [IgnoreDataMember]
         [IgnoreDataMember]
         public override BaseItem DisplayParent
         public override BaseItem DisplayParent
         {
         {
-            get { return Series ?? Parent; }
+            get { return Series ?? GetParent(); }
         }
         }
 
 
         // Genre, Rating and Stuido will all be the same
         // Genre, Rating and Stuido will all be the same
@@ -87,19 +88,6 @@ namespace MediaBrowser.Controller.Entities.TV
             }
             }
         }
         }
 
 
-        /// <summary>
-        /// Our rating comes from our series
-        /// </summary>
-        [IgnoreDataMember]
-        public override string OfficialRatingForComparison
-        {
-            get
-            {
-                var series = Series;
-                return series != null ? series.OfficialRatingForComparison : base.OfficialRatingForComparison;
-            }
-        }
-
         /// <summary>
         /// <summary>
         /// Creates the name of the sort.
         /// Creates the name of the sort.
         /// </summary>
         /// </summary>
@@ -234,6 +222,11 @@ namespace MediaBrowser.Controller.Entities.TV
             return false;
             return false;
         }
         }
 
 
+        public override UnratedItem GetBlockUnratedType()
+        {
+            return UnratedItem.Series;
+        }
+
         [IgnoreDataMember]
         [IgnoreDataMember]
         public string SeriesName
         public string SeriesName
         {
         {

+ 5 - 0
MediaBrowser.Controller/Entities/TV/Series.cs

@@ -333,6 +333,11 @@ namespace MediaBrowser.Controller.Entities.TV
             return config.BlockUnratedItems.Contains(UnratedItem.Series);
             return config.BlockUnratedItems.Contains(UnratedItem.Series);
         }
         }
 
 
+        public override UnratedItem GetBlockUnratedType()
+        {
+            return UnratedItem.Series;
+        }
+
         public SeriesInfo GetLookupInfo()
         public SeriesInfo GetLookupInfo()
         {
         {
             var info = GetItemLookupInfo<SeriesInfo>();
             var info = GetItemLookupInfo<SeriesInfo>();

+ 3 - 3
MediaBrowser.Controller/Entities/Trailer.cs

@@ -73,7 +73,7 @@ namespace MediaBrowser.Controller.Entities
             get
             get
             {
             {
                 // Local trailers are not part of children
                 // Local trailers are not part of children
-                return Parent == null;
+                return GetParent() == null;
             }
             }
         }
         }
 
 
@@ -97,9 +97,9 @@ namespace MediaBrowser.Controller.Entities
             return base.CreateUserDataKey();
             return base.CreateUserDataKey();
         }
         }
 
 
-        protected override bool GetBlockUnratedValue(UserPolicy config)
+        public override UnratedItem GetBlockUnratedType()
         {
         {
-            return config.BlockUnratedItems.Contains(UnratedItem.Trailer);
+            return UnratedItem.Trailer;
         }
         }
 
 
         public TrailerInfo GetLookupInfo()
         public TrailerInfo GetLookupInfo()

+ 20 - 1
MediaBrowser.Controller/Entities/User.cs

@@ -20,7 +20,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; }
-        public bool EnableUserViews { get; set; }
 
 
         /// <summary>
         /// <summary>
         /// From now on all user paths will be Id-based. 
         /// From now on all user paths will be Id-based. 
@@ -58,6 +57,26 @@ namespace MediaBrowser.Controller.Entities
             }
             }
         }
         }
 
 
+        private string _name;
+        /// <summary>
+        /// Gets or sets the name.
+        /// </summary>
+        /// <value>The name.</value>
+        public override string Name
+        {
+            get
+            {
+                return _name;
+            }
+            set
+            {
+                _name = value;
+
+                // lazy load this again
+                SortName = null;
+            }
+        }
+
         /// <summary>
         /// <summary>
         /// Returns the folder containing the item.
         /// Returns the folder containing the item.
         /// If the item is a folder, it returns the folder itself
         /// If the item is a folder, it returns the folder itself

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

@@ -55,13 +55,21 @@ namespace MediaBrowser.Controller.Entities
             }
             }
         }
         }
 
 
+        protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user)
+        {
+            var list = base.GetEligibleChildrenForRecursiveChildren(user).ToList();
+            list.AddRange(LibraryManager.RootFolder.VirtualChildren);
+
+            return list;
+        }
+
         /// <summary>
         /// <summary>
         /// Get the children of this folder from the actual file system
         /// Get the children of this folder from the actual file system
         /// </summary>
         /// </summary>
         /// <returns>IEnumerable{BaseItem}.</returns>
         /// <returns>IEnumerable{BaseItem}.</returns>
         protected override IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService)
         protected override IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService)
         {
         {
-            return base.GetNonCachedChildren(directoryService).Concat(LibraryManager.RootFolder.VirtualChildren);
+            return base.GetNonCachedChildren(directoryService);
         }
         }
 
 
         public override bool BeforeMetadataRefresh()
         public override bool BeforeMetadataRefresh()

+ 73 - 20
MediaBrowser.Controller/Entities/UserView.cs

@@ -6,6 +6,7 @@ using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Runtime.Serialization;
 using System.Runtime.Serialization;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
+using System.Linq;
 
 
 namespace MediaBrowser.Controller.Entities
 namespace MediaBrowser.Controller.Entities
 {
 {
@@ -16,7 +17,7 @@ namespace MediaBrowser.Controller.Entities
         public Guid DisplayParentId { get; set; }
         public Guid DisplayParentId { get; set; }
 
 
         public Guid? UserId { get; set; }
         public Guid? UserId { get; set; }
-        
+
         public static ITVSeriesManager TVSeriesManager;
         public static ITVSeriesManager TVSeriesManager;
         public static IPlaylistManager PlaylistManager;
         public static IPlaylistManager PlaylistManager;
 
 
@@ -24,7 +25,26 @@ namespace MediaBrowser.Controller.Entities
         {
         {
             return true;
             return true;
         }
         }
-        
+
+        public override IEnumerable<Guid> GetIdsForAncestorQuery()
+        {
+            var list = new List<Guid>();
+
+            if (DisplayParentId != Guid.Empty)
+            {
+                list.Add(DisplayParentId);
+            }
+            else if (ParentId != Guid.Empty)
+            {
+                list.Add(ParentId);
+            }
+            else
+            {
+                list.Add(Id);
+            }
+            return list;
+        }
+
         public override Task<QueryResult<BaseItem>> GetItems(InternalItemsQuery query)
         public override Task<QueryResult<BaseItem>> GetItems(InternalItemsQuery query)
         {
         {
             var parent = this as Folder;
             var parent = this as Folder;
@@ -81,16 +101,11 @@ namespace MediaBrowser.Controller.Entities
             return GetChildren(user, false);
             return GetChildren(user, false);
         }
         }
 
 
-        public static bool IsExcludedFromGrouping(Folder folder)
+        public static bool IsUserSpecific(Folder folder)
         {
         {
             var standaloneTypes = new List<string>
             var standaloneTypes = new List<string>
             {
             {
-                CollectionType.Books,
-                CollectionType.HomeVideos,
-                CollectionType.Photos,
-                CollectionType.Playlists,
-                CollectionType.BoxSets,
-                CollectionType.MusicVideos
+                CollectionType.Playlists
             };
             };
 
 
             var collectionFolder = folder as ICollectionFolder;
             var collectionFolder = folder as ICollectionFolder;
@@ -100,25 +115,63 @@ namespace MediaBrowser.Controller.Entities
                 return false;
                 return false;
             }
             }
 
 
+            var supportsUserSpecific = folder as ISupportsUserSpecificView;
+            if (supportsUserSpecific != null && supportsUserSpecific.EnableUserSpecificView)
+            {
+                return true;
+            }
+
             return standaloneTypes.Contains(collectionFolder.CollectionType ?? string.Empty);
             return standaloneTypes.Contains(collectionFolder.CollectionType ?? string.Empty);
         }
         }
 
 
-        public static bool IsUserSpecific(Folder folder)
+        public static bool IsEligibleForGrouping(Folder folder)
         {
         {
-            var standaloneTypes = new List<string>
-            {
-                CollectionType.Playlists,
-                CollectionType.BoxSets
+            var collectionFolder = folder as ICollectionFolder;
+            return collectionFolder != null && IsEligibleForGrouping(collectionFolder.CollectionType);
+        }
+
+        public static bool IsEligibleForGrouping(string viewType)
+        {
+            var types = new[] 
+            { 
+                CollectionType.Movies, 
+                CollectionType.TvShows,
+                string.Empty
             };
             };
 
 
-            var collectionFolder = folder as ICollectionFolder;
+            return types.Contains(viewType ?? string.Empty, StringComparer.OrdinalIgnoreCase);
+        }
 
 
-            if (collectionFolder == null)
-            {
-                return false;
-            }
+        public static bool IsEligibleForEnhancedView(string viewType)
+        {
+            var types = new[] 
+            { 
+                CollectionType.Movies, 
+                CollectionType.TvShows 
+            };
 
 
-            return standaloneTypes.Contains(collectionFolder.CollectionType ?? string.Empty);
+            return types.Contains(viewType ?? string.Empty, StringComparer.OrdinalIgnoreCase);
+        }
+
+        public static bool EnableOriginalFolder(string viewType)
+        {
+            var types = new[] 
+            { 
+                CollectionType.Games, 
+                CollectionType.Books, 
+                CollectionType.MusicVideos, 
+                CollectionType.HomeVideos, 
+                CollectionType.Photos, 
+                CollectionType.Music, 
+                CollectionType.BoxSets
+            };
+
+            return types.Contains(viewType ?? string.Empty, StringComparer.OrdinalIgnoreCase);
+        }
+
+        protected override Task ValidateChildrenInternal(IProgress<double> progress, System.Threading.CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, Providers.MetadataRefreshOptions refreshOptions, Providers.IDirectoryService directoryService)
+        {
+            return Task.FromResult(true);
         }
         }
 
 
         [IgnoreDataMember]
         [IgnoreDataMember]

+ 88 - 165
MediaBrowser.Controller/Entities/UserViewBuilder.cs

@@ -120,59 +120,34 @@ namespace MediaBrowser.Controller.Entities
                         return await GetLiveTvView(queryParent, user, query).ConfigureAwait(false);
                         return await GetLiveTvView(queryParent, user, query).ConfigureAwait(false);
                     }
                     }
 
 
+                case CollectionType.Photos:
                 case CollectionType.Books:
                 case CollectionType.Books:
                 case CollectionType.HomeVideos:
                 case CollectionType.HomeVideos:
+                case CollectionType.Games:
                 case CollectionType.MusicVideos:
                 case CollectionType.MusicVideos:
+                {
+                    if (query.Recursive)
+                    {
+                        return GetResult(queryParent.GetRecursiveChildren(user, true), queryParent, query);
+                    }
                     return GetResult(queryParent.GetChildren(user, true), queryParent, query);
                     return GetResult(queryParent.GetChildren(user, true), queryParent, query);
+                }
 
 
                 case CollectionType.Folders:
                 case CollectionType.Folders:
                     return GetResult(user.RootFolder.GetChildren(user, true), queryParent, query);
                     return GetResult(user.RootFolder.GetChildren(user, true), queryParent, query);
 
 
-                case CollectionType.Games:
-                    return await GetGameView(user, queryParent, query).ConfigureAwait(false);
-
                 case CollectionType.Playlists:
                 case CollectionType.Playlists:
                     return await GetPlaylistsView(queryParent, user, query).ConfigureAwait(false);
                     return await GetPlaylistsView(queryParent, user, query).ConfigureAwait(false);
 
 
                 case CollectionType.BoxSets:
                 case CollectionType.BoxSets:
                     return await GetBoxsetView(queryParent, user, query).ConfigureAwait(false);
                     return await GetBoxsetView(queryParent, user, query).ConfigureAwait(false);
 
 
-                case CollectionType.Photos:
-                    return await GetPhotosView(queryParent, user, query).ConfigureAwait(false);
-
                 case CollectionType.TvShows:
                 case CollectionType.TvShows:
                     return await GetTvView(queryParent, user, query).ConfigureAwait(false);
                     return await GetTvView(queryParent, user, query).ConfigureAwait(false);
 
 
-                case CollectionType.Music:
-                    return await GetMusicFolders(queryParent, user, query).ConfigureAwait(false);
-
                 case CollectionType.Movies:
                 case CollectionType.Movies:
                     return await GetMovieFolders(queryParent, user, query).ConfigureAwait(false);
                     return await GetMovieFolders(queryParent, user, query).ConfigureAwait(false);
 
 
-                case SpecialFolder.MusicGenres:
-                    return await GetMusicGenres(queryParent, user, query).ConfigureAwait(false);
-
-                case SpecialFolder.MusicGenre:
-                    return await GetMusicGenreItems(queryParent, displayParent, user, query).ConfigureAwait(false);
-
-                case SpecialFolder.GameGenres:
-                    return await GetGameGenres(queryParent, user, query).ConfigureAwait(false);
-
-                case SpecialFolder.GameGenre:
-                    return await GetGameGenreItems(queryParent, displayParent, user, query).ConfigureAwait(false);
-
-                case SpecialFolder.GameSystems:
-                    return GetGameSystems(queryParent, user, query);
-
-                case SpecialFolder.LatestGames:
-                    return GetLatestGames(queryParent, user, query);
-
-                case SpecialFolder.RecentlyPlayedGames:
-                    return GetRecentlyPlayedGames(queryParent, user, query);
-
-                case SpecialFolder.GameFavorites:
-                    return GetFavoriteGames(queryParent, user, query);
-
                 case SpecialFolder.TvShowSeries:
                 case SpecialFolder.TvShowSeries:
                     return GetTvSeries(queryParent, user, query);
                     return GetTvSeries(queryParent, user, query);
 
 
@@ -212,6 +187,21 @@ namespace MediaBrowser.Controller.Entities
                 case SpecialFolder.MovieCollections:
                 case SpecialFolder.MovieCollections:
                     return GetMovieCollections(queryParent, user, query);
                     return GetMovieCollections(queryParent, user, query);
 
 
+                case SpecialFolder.TvFavoriteEpisodes:
+                    return GetFavoriteEpisodes(queryParent, user, query);
+
+                case SpecialFolder.TvFavoriteSeries:
+                    return GetFavoriteSeries(queryParent, user, query);
+
+                case CollectionType.Music:
+                    return await GetMusicFolders(queryParent, user, query).ConfigureAwait(false);
+
+                case SpecialFolder.MusicGenres:
+                    return await GetMusicGenres(queryParent, user, query).ConfigureAwait(false);
+
+                case SpecialFolder.MusicGenre:
+                    return await GetMusicGenreItems(queryParent, displayParent, user, query).ConfigureAwait(false);
+
                 case SpecialFolder.MusicLatest:
                 case SpecialFolder.MusicLatest:
                     return GetMusicLatest(queryParent, user, query);
                     return GetMusicLatest(queryParent, user, query);
 
 
@@ -230,12 +220,6 @@ namespace MediaBrowser.Controller.Entities
                 case SpecialFolder.MusicSongs:
                 case SpecialFolder.MusicSongs:
                     return GetMusicSongs(queryParent, user, query);
                     return GetMusicSongs(queryParent, user, query);
 
 
-                case SpecialFolder.TvFavoriteEpisodes:
-                    return GetFavoriteEpisodes(queryParent, user, query);
-
-                case SpecialFolder.TvFavoriteSeries:
-                    return GetFavoriteSeries(queryParent, user, query);
-
                 case SpecialFolder.MusicFavorites:
                 case SpecialFolder.MusicFavorites:
                     return await GetMusicFavorites(queryParent, user, query).ConfigureAwait(false);
                     return await GetMusicFavorites(queryParent, user, query).ConfigureAwait(false);
 
 
@@ -262,18 +246,6 @@ namespace MediaBrowser.Controller.Entities
             }
             }
         }
         }
 
 
-        private async Task<QueryResult<BaseItem>> FindPlaylists(Folder parent, User user, InternalItemsQuery query)
-        {
-            var list = _playlistManager.GetPlaylists(user.Id.ToString("N"));
-
-            return GetResult(list, parent, query);
-        }
-
-        private int GetSpecialItemsLimit()
-        {
-            return 50;
-        }
-
         private async Task<QueryResult<BaseItem>> GetMusicFolders(Folder parent, User user, InternalItemsQuery query)
         private async Task<QueryResult<BaseItem>> GetMusicFolders(Folder parent, User user, InternalItemsQuery query)
         {
         {
             if (query.Recursive)
             if (query.Recursive)
@@ -422,6 +394,36 @@ namespace MediaBrowser.Controller.Entities
             return PostFilterAndSort(items, parent, null, query);
             return PostFilterAndSort(items, parent, null, query);
         }
         }
 
 
+        private QueryResult<BaseItem> GetFavoriteSongs(Folder parent, User user, InternalItemsQuery query)
+        {
+            query.IsFavorite = true;
+
+            var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Music }, i => (i is Audio.Audio) && FilterItem(i, query));
+
+            return PostFilterAndSort(items, parent, null, query);
+        }
+
+        private QueryResult<BaseItem> GetFavoriteAlbums(Folder parent, User user, InternalItemsQuery query)
+        {
+            query.IsFavorite = true;
+
+            var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Music }, i => (i is MusicAlbum) && FilterItem(i, query));
+
+            return PostFilterAndSort(items, parent, null, query);
+        }
+
+        private async Task<QueryResult<BaseItem>> FindPlaylists(Folder parent, User user, InternalItemsQuery query)
+        {
+            var list = _playlistManager.GetPlaylists(user.Id.ToString("N"));
+
+            return GetResult(list, parent, query);
+        }
+
+        private int GetSpecialItemsLimit()
+        {
+            return 50;
+        }
+
         private async Task<QueryResult<BaseItem>> GetMovieFolders(Folder parent, User user, InternalItemsQuery query)
         private async Task<QueryResult<BaseItem>> GetMovieFolders(Folder parent, User user, InternalItemsQuery query)
         {
         {
             if (query.Recursive)
             if (query.Recursive)
@@ -480,24 +482,6 @@ namespace MediaBrowser.Controller.Entities
             return PostFilterAndSort(items, parent, null, query);
             return PostFilterAndSort(items, parent, null, query);
         }
         }
 
 
-        private QueryResult<BaseItem> GetFavoriteSongs(Folder parent, User user, InternalItemsQuery query)
-        {
-            query.IsFavorite = true;
-
-            var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Music }, i => (i is Audio.Audio) && FilterItem(i, query));
-
-            return PostFilterAndSort(items, parent, null, query);
-        }
-
-        private QueryResult<BaseItem> GetFavoriteAlbums(Folder parent, User user, InternalItemsQuery query)
-        {
-            query.IsFavorite = true;
-
-            var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Music }, i => (i is MusicAlbum) && FilterItem(i, query));
-
-            return PostFilterAndSort(items, parent, null, query);
-        }
-
         private QueryResult<BaseItem> GetMovieMovies(Folder parent, User user, InternalItemsQuery query)
         private QueryResult<BaseItem> GetMovieMovies(Folder parent, User user, InternalItemsQuery query)
         {
         {
             var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty }, i => (i is Movie) && FilterItem(i, query));
             var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Movies, CollectionType.BoxSets, string.Empty }, i => (i is Movie) && FilterItem(i, query));
@@ -617,54 +601,6 @@ namespace MediaBrowser.Controller.Entities
             return GetResult(list, parent, query);
             return GetResult(list, parent, query);
         }
         }
 
 
-        private async Task<QueryResult<BaseItem>> GetGameView(User user, Folder parent, InternalItemsQuery query)
-        {
-            if (query.Recursive)
-            {
-                var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Games }, i => FilterItem(i, query));
-                return PostFilterAndSort(items, parent, null, query);
-            }
-
-            var list = new List<BaseItem>();
-
-            list.Add(await GetUserView(SpecialFolder.LatestGames, "0", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(SpecialFolder.RecentlyPlayedGames, "1", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(SpecialFolder.GameFavorites, "2", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(SpecialFolder.GameSystems, "3", parent).ConfigureAwait(false));
-            list.Add(await GetUserView(SpecialFolder.GameGenres, "4", parent).ConfigureAwait(false));
-
-            return GetResult(list, parent, query);
-        }
-
-        private QueryResult<BaseItem> GetLatestGames(Folder parent, User user, InternalItemsQuery query)
-        {
-            query.SortBy = new[] { ItemSortBy.DateCreated, ItemSortBy.SortName };
-            query.SortOrder = SortOrder.Descending;
-
-            var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Games }, i => i is Game && FilterItem(i, query));
-
-            return PostFilterAndSort(items, parent, GetSpecialItemsLimit(), query);
-        }
-
-        private QueryResult<BaseItem> GetRecentlyPlayedGames(Folder parent, User user, InternalItemsQuery query)
-        {
-            query.IsPlayed = true;
-            query.SortBy = new[] { ItemSortBy.DatePlayed, ItemSortBy.SortName };
-            query.SortOrder = SortOrder.Descending;
-
-            var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Games }, i => i is Game && FilterItem(i, query));
-
-            return PostFilterAndSort(items, parent, GetSpecialItemsLimit(), query);
-        }
-
-        private QueryResult<BaseItem> GetFavoriteGames(Folder parent, User user, InternalItemsQuery query)
-        {
-            query.IsFavorite = true;
-
-            var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Games }, i => i is Game && FilterItem(i, query));
-            return PostFilterAndSort(items, parent, null, query);
-        }
-
         private QueryResult<BaseItem> GetTvLatest(Folder parent, User user, InternalItemsQuery query)
         private QueryResult<BaseItem> GetTvLatest(Folder parent, User user, InternalItemsQuery query)
         {
         {
             query.SortBy = new[] { ItemSortBy.DateCreated, ItemSortBy.SortName };
             query.SortBy = new[] { ItemSortBy.DateCreated, ItemSortBy.SortName };
@@ -745,49 +681,6 @@ namespace MediaBrowser.Controller.Entities
             return GetResult(items, queryParent, query);
             return GetResult(items, queryParent, query);
         }
         }
 
 
-        private QueryResult<BaseItem> GetGameSystems(Folder parent, User user, InternalItemsQuery query)
-        {
-            var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Games }, i => i is GameSystem && FilterItem(i, query));
-
-            return PostFilterAndSort(items, parent, null, query);
-        }
-
-        private async Task<QueryResult<BaseItem>> GetGameGenreItems(Folder queryParent, Folder displayParent, User user, InternalItemsQuery query)
-        {
-            var items = GetRecursiveChildren(queryParent, user, new[] { CollectionType.Games },
-                i => i is Game && i.Genres.Contains(displayParent.Name, StringComparer.OrdinalIgnoreCase));
-
-            return GetResult(items, queryParent, query);
-        }
-
-        private async Task<QueryResult<BaseItem>> GetGameGenres(Folder parent, User user, InternalItemsQuery query)
-        {
-            var tasks = GetRecursiveChildren(parent, user, new[] { CollectionType.Games })
-                .OfType<Game>()
-                .SelectMany(i => i.Genres)
-                .DistinctNames()
-                .Select(i =>
-                {
-                    try
-                    {
-                        return _libraryManager.GetGameGenre(i);
-                    }
-                    catch
-                    {
-                        // Full exception logged at lower levels
-                        _logger.Error("Error getting game genre");
-                        return null;
-                    }
-
-                })
-                .Where(i => i != null)
-                .Select(i => GetUserView(i.Name, SpecialFolder.GameGenre, i.SortName, parent));
-
-            var genres = await Task.WhenAll(tasks).ConfigureAwait(false);
-
-            return GetResult(genres, parent, query);
-        }
-
         private QueryResult<BaseItem> GetResult<T>(QueryResult<T> result)
         private QueryResult<BaseItem> GetResult<T>(QueryResult<T> result)
             where T : BaseItem
             where T : BaseItem
         {
         {
@@ -1061,6 +954,11 @@ namespace MediaBrowser.Controller.Entities
                 return false;
                 return false;
             }
             }
 
 
+            if (request.GenreIds.Length > 0)
+            {
+                return false;
+            }
+
             if (request.VideoTypes.Length > 0)
             if (request.VideoTypes.Length > 0)
             {
             {
                 return false;
                 return false;
@@ -1101,10 +999,15 @@ namespace MediaBrowser.Controller.Entities
                 return false;
                 return false;
             }
             }
 
 
+            if (request.MinIndexNumber.HasValue)
+            {
+                return false;
+            }
+
             return true;
             return true;
         }
         }
 
 
-        public static IEnumerable<BaseItem> FilterVirtualEpisodes(
+        private static IEnumerable<BaseItem> FilterVirtualEpisodes(
             IEnumerable<BaseItem> items,
             IEnumerable<BaseItem> items,
             bool? isMissing,
             bool? isMissing,
             bool? isVirtualUnaired,
             bool? isVirtualUnaired,
@@ -1374,7 +1277,7 @@ namespace MediaBrowser.Controller.Entities
             if (query.IsInBoxSet.HasValue)
             if (query.IsInBoxSet.HasValue)
             {
             {
                 var val = query.IsInBoxSet.Value;
                 var val = query.IsInBoxSet.Value;
-                if (item.Parents.OfType<BoxSet>().Any() != val)
+                if (item.GetParents().OfType<BoxSet>().Any() != val)
                 {
                 {
                     return false;
                     return false;
                 }
                 }
@@ -1657,6 +1560,16 @@ namespace MediaBrowser.Controller.Entities
                 return false;
                 return false;
             }
             }
 
 
+            // Apply genre filter
+            if (query.GenreIds.Length > 0 && !query.GenreIds.Any(id =>
+            {
+                var genreItem = libraryManager.GetItemById(id);
+                return genreItem != null && item.Genres.Contains(genreItem.Name, StringComparer.OrdinalIgnoreCase);
+            }))
+            {
+                return false;
+            }
+
             // Apply year filter
             // Apply year filter
             if (query.Years.Length > 0)
             if (query.Years.Length > 0)
             {
             {
@@ -1779,6 +1692,16 @@ namespace MediaBrowser.Controller.Entities
                 }
                 }
             }
             }
 
 
+            if (query.MinIndexNumber.HasValue)
+            {
+                var val = query.MinIndexNumber.Value;
+
+                if (!(item.IndexNumber.HasValue && item.IndexNumber.Value >= val))
+                {
+                    return false;
+                }
+            }
+
             return true;
             return true;
         }
         }
 
 
@@ -1789,12 +1712,12 @@ namespace MediaBrowser.Controller.Entities
                 return _libraryManager.RootFolder
                 return _libraryManager.RootFolder
                     .Children
                     .Children
                     .OfType<Folder>()
                     .OfType<Folder>()
-                    .Where(i => !UserView.IsExcludedFromGrouping(i));
+                    .Where(UserView.IsEligibleForGrouping);
             }
             }
             return user.RootFolder
             return user.RootFolder
-                .GetChildren(user, true, true)
+                .GetChildren(user, true)
                 .OfType<Folder>()
                 .OfType<Folder>()
-                .Where(i => user.IsFolderGrouped(i.Id) && !UserView.IsExcludedFromGrouping(i));
+                .Where(i => user.IsFolderGrouped(i.Id) && UserView.IsEligibleForGrouping(i));
         }
         }
 
 
         private IEnumerable<Folder> GetMediaFolders(User user, IEnumerable<string> viewTypes)
         private IEnumerable<Folder> GetMediaFolders(User user, IEnumerable<string> viewTypes)

+ 1 - 7
MediaBrowser.Controller/Entities/Video.cs

@@ -185,12 +185,6 @@ namespace MediaBrowser.Controller.Entities
         public bool IsShortcut { get; set; }
         public bool IsShortcut { get; set; }
         public string ShortcutPath { get; set; }
         public string ShortcutPath { get; set; }
 
 
-        /// <summary>
-        /// Gets or sets the tags.
-        /// </summary>
-        /// <value>The tags.</value>
-        public List<string> Tags { get; set; }
-
         /// <summary>
         /// <summary>
         /// Gets or sets the video bit rate.
         /// Gets or sets the video bit rate.
         /// </summary>
         /// </summary>
@@ -356,7 +350,7 @@ namespace MediaBrowser.Controller.Entities
             // Must have a parent to have additional parts or alternate versions
             // Must have a parent to have additional parts or alternate versions
             // In other words, it must be part of the Parent/Child tree
             // In other words, it must be part of the Parent/Child tree
             // The additional parts won't have additional parts themselves
             // The additional parts won't have additional parts themselves
-            if (LocationType == LocationType.FileSystem && Parent != null)
+            if (LocationType == LocationType.FileSystem && GetParent() != null)
             {
             {
                 if (!IsStacked)
                 if (!IsStacked)
                 {
                 {

+ 24 - 3
MediaBrowser.Controller/Library/ILibraryManager.cs

@@ -337,7 +337,6 @@ namespace MediaBrowser.Controller.Library
             string parentId,
             string parentId,
             string viewType, 
             string viewType, 
             string sortName, 
             string sortName, 
-            string uniqueId,
             CancellationToken cancellationToken);
             CancellationToken cancellationToken);
 
 
         /// <summary>
         /// <summary>
@@ -391,13 +390,11 @@ namespace MediaBrowser.Controller.Library
         /// <param name="parent">The parent.</param>
         /// <param name="parent">The parent.</param>
         /// <param name="viewType">Type of the view.</param>
         /// <param name="viewType">Type of the view.</param>
         /// <param name="sortName">Name of the sort.</param>
         /// <param name="sortName">Name of the sort.</param>
-        /// <param name="uniqueId">The unique identifier.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task&lt;UserView&gt;.</returns>
         /// <returns>Task&lt;UserView&gt;.</returns>
         Task<UserView> GetShadowView(BaseItem parent,
         Task<UserView> GetShadowView(BaseItem parent,
           string viewType,
           string viewType,
           string sortName,
           string sortName,
-          string uniqueId,
           CancellationToken cancellationToken);
           CancellationToken cancellationToken);
         
         
         /// <summary>
         /// <summary>
@@ -543,5 +540,29 @@ namespace MediaBrowser.Controller.Library
         /// <param name="imageIndex">Index of the image.</param>
         /// <param name="imageIndex">Index of the image.</param>
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
         Task<ItemImageInfo> ConvertImageToLocal(IHasImages item, ItemImageInfo image, int imageIndex);
         Task<ItemImageInfo> ConvertImageToLocal(IHasImages item, ItemImageInfo image, int imageIndex);
+
+        /// <summary>
+        /// Gets the items.
+        /// </summary>
+        /// <param name="query">The query.</param>
+        /// <param name="parentIds">The parent ids.</param>
+        /// <returns>List&lt;BaseItem&gt;.</returns>
+        IEnumerable<BaseItem> GetItems(InternalItemsQuery query, IEnumerable<string> parentIds);
+
+        /// <summary>
+        /// Gets the items result.
+        /// </summary>
+        /// <param name="query">The query.</param>
+        /// <param name="parentIds">The parent ids.</param>
+        /// <returns>QueryResult&lt;BaseItem&gt;.</returns>
+        QueryResult<BaseItem> GetItemsResult(InternalItemsQuery query, IEnumerable<string> parentIds);
+
+        /// <summary>
+        /// Ignores the file.
+        /// </summary>
+        /// <param name="file">The file.</param>
+        /// <param name="parent">The parent.</param>
+        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
+        bool IgnoreFile(FileSystemMetadata file, BaseItem parent);
     }
     }
 }
 }

+ 1 - 1
MediaBrowser.Controller/Library/ItemResolveArgs.cs

@@ -155,7 +155,7 @@ namespace MediaBrowser.Controller.Library
                 // Not officially supported but in some cases we can handle it.
                 // Not officially supported but in some cases we can handle it.
                 if (item == null)
                 if (item == null)
                 {
                 {
-                    item = parent.Parents.OfType<T>().FirstOrDefault();
+                    item = parent.GetParents().OfType<T>().FirstOrDefault();
                 }
                 }
 
 
                 return item != null;
                 return item != null;

+ 7 - 3
MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs

@@ -36,7 +36,6 @@ namespace MediaBrowser.Controller.LiveTv
         public bool IsLive { get; set; }
         public bool IsLive { get; set; }
         [IgnoreDataMember]
         [IgnoreDataMember]
         public bool IsPremiere { get; set; }
         public bool IsPremiere { get; set; }
-        public ProgramAudio? Audio { get; set; }
 
 
         /// <summary>
         /// <summary>
         /// Gets the user data key.
         /// Gets the user data key.
@@ -106,9 +105,9 @@ namespace MediaBrowser.Controller.LiveTv
             }
             }
         }
         }
 
 
-        protected override bool GetBlockUnratedValue(UserPolicy config)
+        public override UnratedItem GetBlockUnratedType()
         {
         {
-            return config.BlockUnratedItems.Contains(UnratedItem.LiveTvProgram);
+            return UnratedItem.LiveTvProgram;
         }
         }
 
 
         protected override string GetInternalMetadataPath(string basePath)
         protected override string GetInternalMetadataPath(string basePath)
@@ -140,5 +139,10 @@ namespace MediaBrowser.Controller.LiveTv
 
 
             return list;
             return list;
         }
         }
+
+        public override bool IsVisibleStandalone(User user)
+        {
+            return IsVisible(user);
+        }
     }
     }
 }
 }

+ 2 - 2
MediaBrowser.Controller/LiveTv/LiveTvChannel.cs

@@ -22,9 +22,9 @@ namespace MediaBrowser.Controller.LiveTv
             return GetClientTypeName() + "-" + Name;
             return GetClientTypeName() + "-" + Name;
         }
         }
 
 
-        protected override bool GetBlockUnratedValue(UserPolicy config)
+        public override UnratedItem GetBlockUnratedType()
         {
         {
-            return config.BlockUnratedItems.Contains(UnratedItem.LiveTvChannel);
+            return UnratedItem.LiveTvChannel;
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 23 - 25
MediaBrowser.Controller/LiveTv/LiveTvProgram.cs

@@ -40,10 +40,11 @@ namespace MediaBrowser.Controller.LiveTv
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Gets or sets the type of the channel.
+        /// Gets or sets the name.
         /// </summary>
         /// </summary>
-        /// <value>The type of the channel.</value>
-        public ChannelType ChannelType { get; set; }
+        /// <value>The name.</value>
+        [IgnoreDataMember]
+        public string ServiceName { get; set; }
 
 
         /// <summary>
         /// <summary>
         /// The start date of the program, in UTC.
         /// The start date of the program, in UTC.
@@ -51,12 +52,6 @@ namespace MediaBrowser.Controller.LiveTv
         [IgnoreDataMember]
         [IgnoreDataMember]
         public DateTime StartDate { get; set; }
         public DateTime StartDate { get; set; }
 
 
-        /// <summary>
-        /// Gets or sets the audio.
-        /// </summary>
-        /// <value>The audio.</value>
-        public ProgramAudio? Audio { get; set; }
-
         /// <summary>
         /// <summary>
         /// Gets or sets a value indicating whether this instance is repeat.
         /// Gets or sets a value indicating whether this instance is repeat.
         /// </summary>
         /// </summary>
@@ -71,12 +66,6 @@ namespace MediaBrowser.Controller.LiveTv
         [IgnoreDataMember]
         [IgnoreDataMember]
         public string EpisodeTitle { get; set; }
         public string EpisodeTitle { get; set; }
 
 
-        /// <summary>
-        /// Gets or sets the name of the service.
-        /// </summary>
-        /// <value>The name of the service.</value>
-        public string ServiceName { get; set; }
-
         /// <summary>
         /// <summary>
         /// Gets or sets a value indicating whether this instance is movie.
         /// Gets or sets a value indicating whether this instance is movie.
         /// </summary>
         /// </summary>
@@ -153,14 +142,14 @@ namespace MediaBrowser.Controller.LiveTv
             }
             }
         }
         }
 
 
-        [IgnoreDataMember]
-        public override string MediaType
-        {
-            get
-            {
-                return ChannelType == ChannelType.TV ? Model.Entities.MediaType.Video : Model.Entities.MediaType.Audio;
-            }
-        }
+        //[IgnoreDataMember]
+        //public override string MediaType
+        //{
+        //    get
+        //    {
+        //        return ChannelType == ChannelType.TV ? Model.Entities.MediaType.Video : Model.Entities.MediaType.Audio;
+        //    }
+        //}
 
 
         [IgnoreDataMember]
         [IgnoreDataMember]
         public bool IsAiring
         public bool IsAiring
@@ -189,9 +178,9 @@ namespace MediaBrowser.Controller.LiveTv
             return "Program";
             return "Program";
         }
         }
 
 
-        protected override bool GetBlockUnratedValue(UserPolicy config)
+        public override UnratedItem GetBlockUnratedType()
         {
         {
-            return config.BlockUnratedItems.Contains(UnratedItem.LiveTvProgram);
+            return UnratedItem.LiveTvProgram;
         }
         }
 
 
         protected override string GetInternalMetadataPath(string basePath)
         protected override string GetInternalMetadataPath(string basePath)
@@ -236,5 +225,14 @@ namespace MediaBrowser.Controller.LiveTv
                 return base.SupportsPeople;
                 return base.SupportsPeople;
             }
             }
         }
         }
+
+        [IgnoreDataMember]
+        public override bool SupportsAncestors
+        {
+            get
+            {
+                return false;
+            }
+        }
     }
     }
 }
 }

+ 7 - 3
MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs

@@ -36,7 +36,6 @@ namespace MediaBrowser.Controller.LiveTv
         public bool IsLive { get; set; }
         public bool IsLive { get; set; }
         [IgnoreDataMember]
         [IgnoreDataMember]
         public bool IsPremiere { get; set; }
         public bool IsPremiere { get; set; }
-        public ProgramAudio? Audio { get; set; }
 
 
         /// <summary>
         /// <summary>
         /// Gets the user data key.
         /// Gets the user data key.
@@ -121,9 +120,9 @@ namespace MediaBrowser.Controller.LiveTv
             }
             }
         }
         }
 
 
-        protected override bool GetBlockUnratedValue(UserPolicy config)
+        public override UnratedItem GetBlockUnratedType()
         {
         {
-            return config.BlockUnratedItems.Contains(UnratedItem.LiveTvProgram);
+            return UnratedItem.LiveTvProgram;
         }
         }
 
 
         protected override string GetInternalMetadataPath(string basePath)
         protected override string GetInternalMetadataPath(string basePath)
@@ -155,5 +154,10 @@ namespace MediaBrowser.Controller.LiveTv
 
 
             return list;
             return list;
         }
         }
+
+        public override bool IsVisibleStandalone(User user)
+        {
+            return IsVisible(user);
+        }
     }
     }
 }
 }

+ 6 - 0
MediaBrowser.Controller/LiveTv/RecordingGroup.cs

@@ -1,4 +1,5 @@
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Users;
 using MediaBrowser.Model.Users;
 
 
 namespace MediaBrowser.Controller.LiveTv
 namespace MediaBrowser.Controller.LiveTv
@@ -11,6 +12,11 @@ namespace MediaBrowser.Controller.LiveTv
             return false;
             return false;
         }
         }
 
 
+        public override UnratedItem GetBlockUnratedType()
+        {
+            return UnratedItem.LiveTvProgram;
+        }
+
         public override bool SupportsLocalMetadata
         public override bool SupportsLocalMetadata
         {
         {
             get
             get

+ 1 - 0
MediaBrowser.Controller/MediaBrowser.Controller.csproj

@@ -162,6 +162,7 @@
     <Compile Include="Entities\IHasThemeMedia.cs" />
     <Compile Include="Entities\IHasThemeMedia.cs" />
     <Compile Include="Entities\IHasTrailers.cs" />
     <Compile Include="Entities\IHasTrailers.cs" />
     <Compile Include="Entities\IHasUserData.cs" />
     <Compile Include="Entities\IHasUserData.cs" />
+    <Compile Include="Entities\IHiddenFromDisplay.cs" />
     <Compile Include="Entities\IItemByName.cs" />
     <Compile Include="Entities\IItemByName.cs" />
     <Compile Include="Entities\ILibraryItem.cs" />
     <Compile Include="Entities\ILibraryItem.cs" />
     <Compile Include="Entities\ImageSourceInfo.cs" />
     <Compile Include="Entities\ImageSourceInfo.cs" />

+ 0 - 1
MediaBrowser.Controller/MediaEncoding/MediaInfoRequest.cs

@@ -15,7 +15,6 @@ namespace MediaBrowser.Controller.MediaEncoding
         public IIsoMount MountedIso { get; set; }
         public IIsoMount MountedIso { get; set; }
         public VideoType VideoType { get; set; }
         public VideoType VideoType { get; set; }
         public List<string> PlayableStreamFileNames { get; set; }
         public List<string> PlayableStreamFileNames { get; set; }
-        public bool ExtractKeyFrameInterval { get; set; }
 
 
         public MediaInfoRequest()
         public MediaInfoRequest()
         {
         {

+ 14 - 0
MediaBrowser.Controller/Persistence/IItemRepository.cs

@@ -176,6 +176,20 @@ namespace MediaBrowser.Controller.Persistence
         /// <param name="query">The query.</param>
         /// <param name="query">The query.</param>
         /// <returns>QueryResult&lt;Tuple&lt;Guid, System.String&gt;&gt;.</returns>
         /// <returns>QueryResult&lt;Tuple&lt;Guid, System.String&gt;&gt;.</returns>
         QueryResult<Tuple<Guid, string>> GetItemIdsWithPath(InternalItemsQuery query);
         QueryResult<Tuple<Guid, string>> GetItemIdsWithPath(InternalItemsQuery query);
+
+        /// <summary>
+        /// Gets the item list.
+        /// </summary>
+        /// <param name="query">The query.</param>
+        /// <returns>List&lt;BaseItem&gt;.</returns>
+        IEnumerable<BaseItem> GetItemList(InternalItemsQuery query);
+
+        /// <summary>
+        /// Updates the inherited values.
+        /// </summary>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task UpdateInheritedValues(CancellationToken cancellationToken);
     }
     }
 }
 }
 
 

+ 1 - 0
MediaBrowser.Controller/Playlists/Playlist.cs

@@ -144,6 +144,7 @@ namespace MediaBrowser.Controller.Playlists
 
 
         public string PlaylistMediaType { get; set; }
         public string PlaylistMediaType { get; set; }
 
 
+        [IgnoreDataMember]
         public override string MediaType
         public override string MediaType
         {
         {
             get
             get

+ 0 - 32
MediaBrowser.Controller/Providers/MetadataStatus.cs

@@ -10,24 +10,6 @@ namespace MediaBrowser.Controller.Providers
         /// <value>The item identifier.</value>
         /// <value>The item identifier.</value>
         public Guid ItemId { get; set; }
         public Guid ItemId { get; set; }
 
 
-        /// <summary>
-        /// Gets or sets the name of the item.
-        /// </summary>
-        /// <value>The name of the item.</value>
-        public string ItemName { get; set; }
-
-        /// <summary>
-        /// Gets or sets the type of the item.
-        /// </summary>
-        /// <value>The type of the item.</value>
-        public string ItemType { get; set; }
-        
-        /// <summary>
-        /// Gets or sets the name of the series.
-        /// </summary>
-        /// <value>The name of the series.</value>
-        public string SeriesName { get; set; }
-
         /// <summary>
         /// <summary>
         /// Gets or sets the date last metadata refresh.
         /// Gets or sets the date last metadata refresh.
         /// </summary>
         /// </summary>
@@ -40,22 +22,8 @@ namespace MediaBrowser.Controller.Providers
         /// <value>The date last images refresh.</value>
         /// <value>The date last images refresh.</value>
         public DateTime? DateLastImagesRefresh { get; set; }
         public DateTime? DateLastImagesRefresh { get; set; }
 
 
-        /// <summary>
-        /// Gets or sets the last result error message.
-        /// </summary>
-        /// <value>The last result error message.</value>
-        public string LastErrorMessage { get; set; }
-
         public DateTime? ItemDateModified { get; set; }
         public DateTime? ItemDateModified { get; set; }
 
 
-        public void AddStatus(string errorMessage)
-        {
-            if (string.IsNullOrEmpty(LastErrorMessage))
-            {
-                LastErrorMessage = errorMessage;
-            }
-        }
-
         public bool IsDirty { get; private set; }
         public bool IsDirty { get; private set; }
 
 
         public void SetDateLastMetadataRefresh(DateTime? date)
         public void SetDateLastMetadataRefresh(DateTime? date)

+ 3 - 2
MediaBrowser.Controller/Resolvers/IResolverIgnoreRule.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Library;
+using CommonIO;
+using MediaBrowser.Controller.Entities;
 
 
 namespace MediaBrowser.Controller.Resolvers
 namespace MediaBrowser.Controller.Resolvers
 {
 {
@@ -7,6 +8,6 @@ namespace MediaBrowser.Controller.Resolvers
     /// </summary>
     /// </summary>
     public interface IResolverIgnoreRule
     public interface IResolverIgnoreRule
     {
     {
-        bool ShouldIgnore(ItemResolveArgs args);
+        bool ShouldIgnore(FileSystemMetadata fileInfo, BaseItem parent);
     }
     }
 }
 }

+ 5 - 2
MediaBrowser.Dlna/ContentDirectory/ContentDirectory.cs

@@ -26,6 +26,7 @@ namespace MediaBrowser.Dlna.ContentDirectory
         private readonly ILocalizationManager _localization;
         private readonly ILocalizationManager _localization;
         private readonly IChannelManager _channelManager;
         private readonly IChannelManager _channelManager;
         private readonly IMediaSourceManager _mediaSourceManager;
         private readonly IMediaSourceManager _mediaSourceManager;
+        private readonly IUserViewManager _userViewManager;
 
 
         public ContentDirectory(IDlnaManager dlna,
         public ContentDirectory(IDlnaManager dlna,
             IUserDataManager userDataManager,
             IUserDataManager userDataManager,
@@ -34,7 +35,7 @@ namespace MediaBrowser.Dlna.ContentDirectory
             IServerConfigurationManager config,
             IServerConfigurationManager config,
             IUserManager userManager,
             IUserManager userManager,
             ILogger logger,
             ILogger logger,
-            IHttpClient httpClient, ILocalizationManager localization, IChannelManager channelManager, IMediaSourceManager mediaSourceManager)
+            IHttpClient httpClient, ILocalizationManager localization, IChannelManager channelManager, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager)
             : base(logger, httpClient)
             : base(logger, httpClient)
         {
         {
             _dlna = dlna;
             _dlna = dlna;
@@ -46,6 +47,7 @@ namespace MediaBrowser.Dlna.ContentDirectory
             _localization = localization;
             _localization = localization;
             _channelManager = channelManager;
             _channelManager = channelManager;
             _mediaSourceManager = mediaSourceManager;
             _mediaSourceManager = mediaSourceManager;
+            _userViewManager = userViewManager;
         }
         }
 
 
         private int SystemUpdateId
         private int SystemUpdateId
@@ -86,7 +88,8 @@ namespace MediaBrowser.Dlna.ContentDirectory
                 _config,
                 _config,
                 _localization,
                 _localization,
                 _channelManager,
                 _channelManager,
-                _mediaSourceManager)
+                _mediaSourceManager,
+                _userViewManager)
                 .ProcessControlRequest(request);
                 .ProcessControlRequest(request);
         }
         }
 
 

+ 37 - 24
MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs

@@ -24,6 +24,7 @@ using System.Text;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using System.Xml;
 using System.Xml;
+using MediaBrowser.Model.Library;
 
 
 namespace MediaBrowser.Dlna.ContentDirectory
 namespace MediaBrowser.Dlna.ContentDirectory
 {
 {
@@ -34,6 +35,7 @@ namespace MediaBrowser.Dlna.ContentDirectory
         private readonly IUserDataManager _userDataManager;
         private readonly IUserDataManager _userDataManager;
         private readonly IServerConfigurationManager _config;
         private readonly IServerConfigurationManager _config;
         private readonly User _user;
         private readonly User _user;
+        private readonly IUserViewManager _userViewManager;
 
 
         private const string NS_DC = "http://purl.org/dc/elements/1.1/";
         private const string NS_DC = "http://purl.org/dc/elements/1.1/";
         private const string NS_DIDL = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/";
         private const string NS_DIDL = "urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/";
@@ -47,7 +49,7 @@ namespace MediaBrowser.Dlna.ContentDirectory
 
 
         private readonly DeviceProfile _profile;
         private readonly DeviceProfile _profile;
 
 
-        public ControlHandler(ILogger logger, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, string accessToken, IImageProcessor imageProcessor, IUserDataManager userDataManager, User user, int systemUpdateId, IServerConfigurationManager config, ILocalizationManager localization, IChannelManager channelManager, IMediaSourceManager mediaSourceManager)
+        public ControlHandler(ILogger logger, ILibraryManager libraryManager, DeviceProfile profile, string serverAddress, string accessToken, IImageProcessor imageProcessor, IUserDataManager userDataManager, User user, int systemUpdateId, IServerConfigurationManager config, ILocalizationManager localization, IChannelManager channelManager, IMediaSourceManager mediaSourceManager, IUserViewManager userViewManager)
             : base(config, logger)
             : base(config, logger)
         {
         {
             _libraryManager = libraryManager;
             _libraryManager = libraryManager;
@@ -55,6 +57,7 @@ namespace MediaBrowser.Dlna.ContentDirectory
             _user = user;
             _user = user;
             _systemUpdateId = systemUpdateId;
             _systemUpdateId = systemUpdateId;
             _channelManager = channelManager;
             _channelManager = channelManager;
+            _userViewManager = userViewManager;
             _profile = profile;
             _profile = profile;
             _config = config;
             _config = config;
 
 
@@ -450,16 +453,32 @@ namespace MediaBrowser.Dlna.ContentDirectory
                 sortOrders.Add(ItemSortBy.SortName);
                 sortOrders.Add(ItemSortBy.SortName);
             }
             }
 
 
-            var queryResult = await folder.GetItems(new InternalItemsQuery
+            QueryResult<BaseItem> queryResult;
+
+            if (folder is UserRootFolder)
             {
             {
-                Limit = limit,
-                StartIndex = startIndex,
-                SortBy = sortOrders.ToArray(),
-                SortOrder = sort.SortOrder,
-                User = user,
-                Filter = FilterUnsupportedContent
+                var views = await _userViewManager.GetUserViews(new UserViewQuery { UserId = user.Id.ToString("N"), PresetViews = new[] { CollectionType.Movies, CollectionType.TvShows, CollectionType.Music } }, CancellationToken.None)
+                            .ConfigureAwait(false);
+
+                queryResult = new QueryResult<BaseItem>
+                {
+                    Items = views.Cast<BaseItem>().ToArray()
+                };
+                queryResult.TotalRecordCount = queryResult.Items.Length;
+            }
+            else
+            {
+                queryResult = await folder.GetItems(new InternalItemsQuery
+               {
+                   Limit = limit,
+                   StartIndex = startIndex,
+                   SortBy = sortOrders.ToArray(),
+                   SortOrder = sort.SortOrder,
+                   User = user,
+                   Filter = FilterUnsupportedContent
 
 
-            }).ConfigureAwait(false);
+               }).ConfigureAwait(false);
+            }
 
 
             var options = _config.GetDlnaConfiguration();
             var options = _config.GetDlnaConfiguration();
 
 
@@ -481,23 +500,17 @@ namespace MediaBrowser.Dlna.ContentDirectory
 
 
         private QueryResult<ServerItem> GetItemsFromPerson(Person person, User user, int? startIndex, int? limit)
         private QueryResult<ServerItem> GetItemsFromPerson(Person person, User user, int? startIndex, int? limit)
         {
         {
-            var itemsWithPerson = _libraryManager.GetItems(new InternalItemsQuery
+            var itemsResult = _libraryManager.GetItemsResult(new InternalItemsQuery(user)
             {
             {
-                Person = person.Name
-
-            }).Items;
-
-            var items = itemsWithPerson
-                .Where(i => i is Movie || i is Series || i is IChannelItem)
-                .Where(i => i.IsVisibleStandalone(user))
-                .ToList();
+                Person = person.Name,
+                IncludeItemTypes = new[] { typeof(Movie).Name, typeof(Series).Name, typeof(ChannelVideoItem).Name },
+                SortBy = new[] { ItemSortBy.SortName },
+                Limit = limit,
+                StartIndex = startIndex
 
 
-            items = _libraryManager.Sort(items, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending)
-                .Skip(startIndex ?? 0)
-                .Take(limit ?? int.MaxValue)
-                .ToList();
+            }, new string[] { });
 
 
-            var serverItems = items.Select(i => new ServerItem
+            var serverItems = itemsResult.Items.Select(i => new ServerItem
             {
             {
                 Item = i,
                 Item = i,
                 StubType = null
                 StubType = null
@@ -506,7 +519,7 @@ namespace MediaBrowser.Dlna.ContentDirectory
 
 
             return new QueryResult<ServerItem>
             return new QueryResult<ServerItem>
             {
             {
-                TotalRecordCount = serverItems.Length,
+                TotalRecordCount = itemsResult.TotalRecordCount,
                 Items = serverItems
                 Items = serverItems
             };
             };
         }
         }

+ 1 - 1
MediaBrowser.Dlna/Didl/DidlBuilder.cs

@@ -966,7 +966,7 @@ namespace MediaBrowser.Dlna.Didl
                 }
                 }
             }
             }
 
 
-            item = item.Parents.FirstOrDefault(i => i.HasImage(ImageType.Primary));
+            item = item.GetParents().FirstOrDefault(i => i.HasImage(ImageType.Primary));
 
 
             if (item != null)
             if (item != null)
             {
             {

+ 0 - 3
MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj

@@ -79,14 +79,11 @@
     <Compile Include="Providers\SeriesXmlProvider.cs" />
     <Compile Include="Providers\SeriesXmlProvider.cs" />
     <Compile Include="Providers\VideoXmlProvider.cs" />
     <Compile Include="Providers\VideoXmlProvider.cs" />
     <Compile Include="Savers\BoxSetXmlSaver.cs" />
     <Compile Include="Savers\BoxSetXmlSaver.cs" />
-    <Compile Include="Savers\EpisodeXmlSaver.cs" />
     <Compile Include="Savers\FolderXmlSaver.cs" />
     <Compile Include="Savers\FolderXmlSaver.cs" />
     <Compile Include="Savers\GameSystemXmlSaver.cs" />
     <Compile Include="Savers\GameSystemXmlSaver.cs" />
     <Compile Include="Savers\GameXmlSaver.cs" />
     <Compile Include="Savers\GameXmlSaver.cs" />
-    <Compile Include="Savers\MovieXmlSaver.cs" />
     <Compile Include="Savers\PersonXmlSaver.cs" />
     <Compile Include="Savers\PersonXmlSaver.cs" />
     <Compile Include="Savers\PlaylistXmlSaver.cs" />
     <Compile Include="Savers\PlaylistXmlSaver.cs" />
-    <Compile Include="Savers\SeriesXmlSaver.cs" />
     <Compile Include="Savers\XmlSaverHelpers.cs" />
     <Compile Include="Savers\XmlSaverHelpers.cs" />
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>

+ 0 - 166
MediaBrowser.LocalMetadata/Savers/EpisodeXmlSaver.cs

@@ -1,166 +0,0 @@
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Persistence;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Security;
-using System.Text;
-using System.Threading;
-using CommonIO;
-using MediaBrowser.Common.IO;
-
-namespace MediaBrowser.LocalMetadata.Savers
-{
-    public class EpisodeXmlProvider : IMetadataFileSaver, IConfigurableProvider
-    {
-        private readonly IItemRepository _itemRepository;
-
-        private readonly CultureInfo _usCulture = new CultureInfo("en-US");
-        private readonly IServerConfigurationManager _config;
-        private readonly ILibraryManager _libraryManager;
-        private IFileSystem _fileSystem;
-
-        public EpisodeXmlProvider(IItemRepository itemRepository, IServerConfigurationManager config, ILibraryManager libraryManager, IFileSystem fileSystem)
-        {
-            _itemRepository = itemRepository;
-            _config = config;
-            _libraryManager = libraryManager;
-            _fileSystem = fileSystem;
-        }
-
-        /// <summary>
-        /// Determines whether [is enabled for] [the specified item].
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="updateType">Type of the update.</param>
-        /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns>
-        public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType)
-        {
-            if (!item.SupportsLocalMetadata)
-            {
-                return false;
-            }
-
-            return item is Episode && updateType >= ItemUpdateType.MetadataDownload;
-        }
-
-        public string Name
-        {
-            get
-            {
-                return XmlProviderUtils.Name;
-            }
-        }
-
-        public bool IsEnabled
-        {
-            get { return !_config.Configuration.DisableXmlSavers; }
-        }
-
-        /// <summary>
-        /// Saves the specified item.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        public void Save(IHasMetadata item, CancellationToken cancellationToken)
-        {
-            var episode = (Episode)item;
-
-            var builder = new StringBuilder();
-
-            builder.Append("<Item>");
-
-            if (!string.IsNullOrEmpty(item.Name))
-            {
-                builder.Append("<EpisodeName>" + SecurityElement.Escape(episode.Name) + "</EpisodeName>");
-            }
-
-            if (episode.IndexNumber.HasValue)
-            {
-                builder.Append("<EpisodeNumber>" + SecurityElement.Escape(episode.IndexNumber.Value.ToString(_usCulture)) + "</EpisodeNumber>");
-            }
-
-            if (episode.IndexNumberEnd.HasValue)
-            {
-                builder.Append("<EpisodeNumberEnd>" + SecurityElement.Escape(episode.IndexNumberEnd.Value.ToString(_usCulture)) + "</EpisodeNumberEnd>");
-            }
-
-            if (episode.AirsAfterSeasonNumber.HasValue)
-            {
-                builder.Append("<airsafter_season>" + SecurityElement.Escape(episode.AirsAfterSeasonNumber.Value.ToString(_usCulture)) + "</airsafter_season>");
-            }
-            if (episode.AirsBeforeEpisodeNumber.HasValue)
-            {
-                builder.Append("<airsbefore_episode>" + SecurityElement.Escape(episode.AirsBeforeEpisodeNumber.Value.ToString(_usCulture)) + "</airsbefore_episode>");
-            }
-            if (episode.AirsBeforeSeasonNumber.HasValue)
-            {
-                builder.Append("<airsbefore_season>" + SecurityElement.Escape(episode.AirsBeforeSeasonNumber.Value.ToString(_usCulture)) + "</airsbefore_season>");
-            }
-   
-            if (episode.ParentIndexNumber.HasValue)
-            {
-                builder.Append("<SeasonNumber>" + SecurityElement.Escape(episode.ParentIndexNumber.Value.ToString(_usCulture)) + "</SeasonNumber>");
-            }
-
-            if (episode.AbsoluteEpisodeNumber.HasValue)
-            {
-                builder.Append("<absolute_number>" + SecurityElement.Escape(episode.AbsoluteEpisodeNumber.Value.ToString(_usCulture)) + "</absolute_number>");
-            }
-            
-            if (episode.DvdEpisodeNumber.HasValue)
-            {
-                builder.Append("<DVD_episodenumber>" + SecurityElement.Escape(episode.DvdEpisodeNumber.Value.ToString(_usCulture)) + "</DVD_episodenumber>");
-            }
-
-            if (episode.DvdSeasonNumber.HasValue)
-            {
-                builder.Append("<DVD_season>" + SecurityElement.Escape(episode.DvdSeasonNumber.Value.ToString(_usCulture)) + "</DVD_season>");
-            } 
-            
-            if (episode.PremiereDate.HasValue)
-            {
-                builder.Append("<FirstAired>" + SecurityElement.Escape(episode.PremiereDate.Value.ToLocalTime().ToString("yyyy-MM-dd")) + "</FirstAired>");
-            }
-
-            XmlSaverHelpers.AddCommonNodes(episode, _libraryManager, builder);
-            XmlSaverHelpers.AddMediaInfo(episode, builder, _itemRepository);
-
-            builder.Append("</Item>");
-
-            var xmlFilePath = GetSavePath(item);
-
-            XmlSaverHelpers.Save(builder, xmlFilePath, new List<string>
-                {
-                    "FirstAired",
-                    "SeasonNumber",
-                    "EpisodeNumber",
-                    "EpisodeName",
-                    "EpisodeNumberEnd",
-                    "airsafter_season",
-                    "airsbefore_episode",
-                    "airsbefore_season",
-                    "DVD_episodenumber",
-                    "DVD_season",
-                    "absolute_number"
-
-                }, _config, _fileSystem);
-        }
-
-        /// <summary>
-        /// Gets the save path.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns>System.String.</returns>
-        public string GetSavePath(IHasMetadata item)
-        {
-            var filename = Path.ChangeExtension(Path.GetFileName(item.Path), ".xml");
-
-            return Path.Combine(Path.GetDirectoryName(item.Path), "metadata", filename);
-        }
-    }
-}

+ 0 - 147
MediaBrowser.LocalMetadata/Savers/MovieXmlSaver.cs

@@ -1,147 +0,0 @@
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Movies;
-using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Persistence;
-using System.Collections.Generic;
-using System.IO;
-using System.Security;
-using System.Text;
-using System.Threading;
-using CommonIO;
-using MediaBrowser.Common.IO;
-
-namespace MediaBrowser.LocalMetadata.Savers
-{
-    /// <summary>
-    /// Saves movie.xml for movies, trailers and music videos
-    /// </summary>
-    public class MovieXmlProvider : IMetadataFileSaver, IConfigurableProvider
-    {
-        private readonly IItemRepository _itemRepository;
-        private readonly IServerConfigurationManager _config;
-        private readonly ILibraryManager _libraryManager;
-        private IFileSystem _fileSystem;
-
-        public MovieXmlProvider(IItemRepository itemRepository, IServerConfigurationManager config, ILibraryManager libraryManager, IFileSystem fileSystem)
-        {
-            _itemRepository = itemRepository;
-            _config = config;
-            _libraryManager = libraryManager;
-            _fileSystem = fileSystem;
-        }
-
-        public string Name
-        {
-            get
-            {
-                return XmlProviderUtils.Name;
-            }
-        }
-
-        public bool IsEnabled
-        {
-            get { return !_config.Configuration.DisableXmlSavers; }
-        }
-
-        /// <summary>
-        /// Determines whether [is enabled for] [the specified item].
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="updateType">Type of the update.</param>
-        /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns>
-        public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType)
-        {
-            if (!item.SupportsLocalMetadata)
-            {
-                return false;
-            }
-
-            var video = item as Video;
-
-            // Check parent for null to avoid running this against things like video backdrops
-            if (video != null && !(item is Episode) && !video.IsOwnedItem)
-            {
-                return updateType >= ItemUpdateType.MetadataDownload;
-            }
-
-            return false;
-        }
-
-        /// <summary>
-        /// Saves the specified item.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        public void Save(IHasMetadata item, CancellationToken cancellationToken)
-        {
-            var video = (Video)item;
-
-            var builder = new StringBuilder();
-
-            builder.Append("<Title>");
-
-            XmlSaverHelpers.AddCommonNodes(video, _libraryManager, builder);
-
-            var musicVideo = item as MusicVideo;
-
-            if (musicVideo != null)
-            {
-                if (musicVideo.Artists.Count > 0)
-                {
-                    builder.Append("<Artist>" + SecurityElement.Escape(string.Join(";", musicVideo.Artists.ToArray())) + "</Artist>");
-                }
-                if (!string.IsNullOrEmpty(musicVideo.Album))
-                {
-                    builder.Append("<Album>" + SecurityElement.Escape(musicVideo.Album) + "</Album>");
-                }
-            }
-
-            var movie = item as Movie;
-
-            if (movie != null)
-            {
-                if (!string.IsNullOrEmpty(movie.TmdbCollectionName))
-                {
-                    builder.Append("<TmdbCollectionName>" + SecurityElement.Escape(movie.TmdbCollectionName) + "</TmdbCollectionName>");
-                }
-            }
-            
-            XmlSaverHelpers.AddMediaInfo(video, builder, _itemRepository);
-
-            builder.Append("</Title>");
-
-            var xmlFilePath = GetSavePath(item);
-
-            XmlSaverHelpers.Save(builder, xmlFilePath, new List<string>
-                {
-                    // Deprecated. No longer saving in this field.
-                    "IMDBrating",
-                    
-                    // Deprecated. No longer saving in this field.
-                    "Description",
-
-                    "Artist",
-                    "Album",
-                    "TmdbCollectionName"
-                }, _config, _fileSystem);
-        }
-
-        public string GetSavePath(IHasMetadata item)
-        {
-            return GetMovieSavePath((Video)item);
-        }
-
-        public static string GetMovieSavePath(Video item)
-        {
-            if (item.IsInMixedFolder)
-            {
-                return Path.ChangeExtension(item.Path, ".xml");
-            }
-
-            return Path.Combine(item.ContainingFolderPath, "movie.xml");
-        }
-    }
-}

+ 0 - 154
MediaBrowser.LocalMetadata/Savers/SeriesXmlSaver.cs

@@ -1,154 +0,0 @@
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Model.Entities;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Security;
-using System.Text;
-using System.Threading;
-using CommonIO;
-using MediaBrowser.Common.IO;
-
-namespace MediaBrowser.LocalMetadata.Savers
-{
-    public class SeriesXmlProvider : IMetadataFileSaver, IConfigurableProvider
-    {
-        private readonly IServerConfigurationManager _config;
-        private readonly ILibraryManager _libraryManager;
-        private IFileSystem _fileSystem;
-
-        public SeriesXmlProvider(IServerConfigurationManager config, ILibraryManager libraryManager, IFileSystem fileSystem)
-        {
-            _config = config;
-            _libraryManager = libraryManager;
-            _fileSystem = fileSystem;
-        }
-
-        public string Name
-        {
-            get
-            {
-                return XmlProviderUtils.Name;
-            }
-        }
-
-        /// <summary>
-        /// Determines whether [is enabled for] [the specified item].
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="updateType">Type of the update.</param>
-        /// <returns><c>true</c> if [is enabled for] [the specified item]; otherwise, <c>false</c>.</returns>
-        public bool IsEnabledFor(IHasMetadata item, ItemUpdateType updateType)
-        {
-            if (!item.SupportsLocalMetadata)
-            {
-                return false;
-            }
-
-            return item is Series && updateType >= ItemUpdateType.MetadataDownload;
-        }
-
-        public bool IsEnabled
-        {
-            get { return !_config.Configuration.DisableXmlSavers; }
-        }
-
-        private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
-
-        /// <summary>
-        /// Saves the specified item.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        public void Save(IHasMetadata item, CancellationToken cancellationToken)
-        {
-            var series = (Series)item;
-
-            var builder = new StringBuilder();
-
-            builder.Append("<Series>");
-
-            var tvdb = item.GetProviderId(MetadataProviders.Tvdb);
-
-            if (!string.IsNullOrEmpty(tvdb))
-            {
-                builder.Append("<id>" + SecurityElement.Escape(tvdb) + "</id>");
-            }
-
-            if (series.Status.HasValue)
-            {
-                builder.Append("<Status>" + SecurityElement.Escape(series.Status.Value.ToString()) + "</Status>");
-            }
-
-            if (series.Studios.Count > 0)
-            {
-                builder.Append("<Network>" + SecurityElement.Escape(series.Studios[0]) + "</Network>");
-            }
-
-            if (!string.IsNullOrEmpty(series.AirTime))
-            {
-                builder.Append("<Airs_Time>" + SecurityElement.Escape(series.AirTime) + "</Airs_Time>");
-            }
-
-            if (series.AirDays != null)
-            {
-                if (series.AirDays.Count == 7)
-                {
-                    builder.Append("<Airs_DayOfWeek>" + SecurityElement.Escape("Daily") + "</Airs_DayOfWeek>");
-                }
-                else if (series.AirDays.Count > 0)
-                {
-                    builder.Append("<Airs_DayOfWeek>" + SecurityElement.Escape(series.AirDays[0].ToString()) + "</Airs_DayOfWeek>");
-                }
-            }
-
-            if (series.PremiereDate.HasValue)
-            {
-                builder.Append("<FirstAired>" + SecurityElement.Escape(series.PremiereDate.Value.ToLocalTime().ToString("yyyy-MM-dd")) + "</FirstAired>");
-            }
-
-            if (series.AnimeSeriesIndex.HasValue)
-            {
-                builder.Append("<AnimeSeriesIndex>" + SecurityElement.Escape(series.AnimeSeriesIndex.Value.ToString(UsCulture)) + "</AnimeSeriesIndex>");
-            }
-
-            XmlSaverHelpers.AddCommonNodes(series, _libraryManager, builder);
-
-            builder.Append("</Series>");
-
-            var xmlFilePath = GetSavePath(item);
-
-            XmlSaverHelpers.Save(builder, xmlFilePath, new List<string>
-                {
-                    "id", 
-                    "Status",
-                    "Network",
-                    "Airs_Time",
-                    "Airs_DayOfWeek",
-                    "FirstAired",
-
-                    // Don't preserve old series node
-                    "Series",
-
-                    "SeriesName",
-
-                    // Deprecated. No longer saving in this field.
-                    "AnimeSeriesIndex"
-                }, _config, _fileSystem);
-        }
-
-        /// <summary>
-        /// Gets the save path.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns>System.String.</returns>
-        public string GetSavePath(IHasMetadata item)
-        {
-            return Path.Combine(item.Path, "series.xml");
-        }
-    }
-}

+ 1 - 1
MediaBrowser.MediaEncoding/Encoder/EncodingJobFactory.cs

@@ -505,7 +505,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
             {
             {
                 return "libx264";
                 return "libx264";
             }
             }
-            if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase))
+            if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase))
             {
             {
                 return "libx265";
                 return "libx265";
             }
             }

+ 21 - 18
MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs

@@ -258,17 +258,17 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
 
                     var mediaInfo = new ProbeResultNormalizer(_logger, FileSystem).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol);
                     var mediaInfo = new ProbeResultNormalizer(_logger, FileSystem).GetMediaInfo(result, videoType, isAudio, primaryPath, protocol);
 
 
-                    //var videoStream = mediaInfo.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
+                    var videoStream = mediaInfo.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
 
 
-                    //if (videoStream != null)
-                    //{
-                    //    var isInterlaced = await DetectInterlaced(mediaInfo, videoStream, inputPath, probeSizeArgument).ConfigureAwait(false);
+                    if (videoStream != null)
+                    {
+                        var isInterlaced = await DetectInterlaced(mediaInfo, videoStream, inputPath, probeSizeArgument).ConfigureAwait(false);
 
 
-                    //    if (isInterlaced)
-                    //    {
-                    //        videoStream.IsInterlaced = true;
-                    //    }
-                    //}
+                        if (isInterlaced)
+                        {
+                            videoStream.IsInterlaced = true;
+                        }
+                    }
 
 
                     return mediaInfo;
                     return mediaInfo;
                 }
                 }
@@ -292,16 +292,19 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 return false;
                 return false;
             }
             }
 
 
-            var formats = (video.Container ?? string.Empty).Split(',').ToList();
-
-            // Take a shortcut and limit this to containers that are likely to have interlaced content
-            if (!formats.Contains("vob", StringComparer.OrdinalIgnoreCase) &&
-                !formats.Contains("m2ts", StringComparer.OrdinalIgnoreCase) &&
-                !formats.Contains("ts", StringComparer.OrdinalIgnoreCase) &&
-                !formats.Contains("mpegts", StringComparer.OrdinalIgnoreCase) &&
-                !formats.Contains("wtv", StringComparer.OrdinalIgnoreCase))
+            // If the video codec is not some form of mpeg, then take a shortcut and limit this to containers that are likely to have interlaced content
+            if ((videoStream.Codec ?? string.Empty).IndexOf("mpeg", StringComparison.OrdinalIgnoreCase) == -1)
             {
             {
-                return false;
+                var formats = (video.Container ?? string.Empty).Split(',').ToList();
+
+                if (!formats.Contains("vob", StringComparer.OrdinalIgnoreCase) &&
+                    !formats.Contains("m2ts", StringComparer.OrdinalIgnoreCase) &&
+                    !formats.Contains("ts", StringComparer.OrdinalIgnoreCase) &&
+                    !formats.Contains("mpegts", StringComparer.OrdinalIgnoreCase) &&
+                    !formats.Contains("wtv", StringComparer.OrdinalIgnoreCase))
+                {
+                    return false;
+                }
             }
             }
 
 
             var args = "{0} -i {1} -map 0:v:{2} -filter:v idet -frames:v 500 -an -f null /dev/null";
             var args = "{0} -i {1} -map 0:v:{2} -filter:v idet -frames:v 500 -an -f null /dev/null";

+ 1 - 1
MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs

@@ -864,7 +864,7 @@ namespace MediaBrowser.MediaEncoding.Probing
             }
             }
         }
         }
 
 
-        private void ExtractTimestamp(Model.MediaInfo.MediaInfo video)
+        private void ExtractTimestamp(MediaInfo video)
         {
         {
             if (video.VideoType == VideoType.VideoFile)
             if (video.VideoType == VideoType.VideoFile)
             {
             {

+ 20 - 3
MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs

@@ -122,10 +122,15 @@ namespace MediaBrowser.MediaEncoding.Subtitles
             var subtitle = await GetSubtitleStream(itemId, mediaSourceId, subtitleStreamIndex, cancellationToken)
             var subtitle = await GetSubtitleStream(itemId, mediaSourceId, subtitleStreamIndex, cancellationToken)
                         .ConfigureAwait(false);
                         .ConfigureAwait(false);
 
 
-            using (var stream = subtitle.Item1)
+            var inputFormat = subtitle.Item2;
+
+            if (string.Equals(inputFormat, outputFormat, StringComparison.OrdinalIgnoreCase) && TryGetWriter(outputFormat) == null)
             {
             {
-                var inputFormat = subtitle.Item2;
+                return subtitle.Item1;
+            }
 
 
+            using (var stream = subtitle.Item1)
+            {
                 return await ConvertSubtitles(stream, inputFormat, outputFormat, startTimeTicks, endTimeTicks, cancellationToken).ConfigureAwait(false);
                 return await ConvertSubtitles(stream, inputFormat, outputFormat, startTimeTicks, endTimeTicks, cancellationToken).ConfigureAwait(false);
             }
             }
         }
         }
@@ -288,7 +293,7 @@ namespace MediaBrowser.MediaEncoding.Subtitles
             return null;
             return null;
         }
         }
 
 
-        private ISubtitleWriter GetWriter(string format)
+        private ISubtitleWriter TryGetWriter(string format)
         {
         {
             if (string.IsNullOrEmpty(format))
             if (string.IsNullOrEmpty(format))
             {
             {
@@ -312,6 +317,18 @@ namespace MediaBrowser.MediaEncoding.Subtitles
                 return new TtmlWriter();
                 return new TtmlWriter();
             }
             }
 
 
+            return null;
+        }
+
+        private ISubtitleWriter GetWriter(string format)
+        {
+            var writer = TryGetWriter(format);
+
+            if (writer != null)
+            {
+                return writer;
+            }
+
             throw new ArgumentException("Unsupported format: " + format);
             throw new ArgumentException("Unsupported format: " + format);
         }
         }
 
 

+ 3 - 0
MediaBrowser.Model/Configuration/CinemaModeConfiguration.cs

@@ -11,16 +11,19 @@ namespace MediaBrowser.Model.Configuration
         public bool EnableIntrosParentalControl { get; set; }
         public bool EnableIntrosParentalControl { get; set; }
         public bool EnableIntrosFromSimilarMovies { get; set; }
         public bool EnableIntrosFromSimilarMovies { get; set; }
         public string CustomIntroPath { get; set; }
         public string CustomIntroPath { get; set; }
+        public string MediaInfoIntroPath { get; set; }
         public bool EnableIntrosFromUpcomingDvdMovies { get; set; }
         public bool EnableIntrosFromUpcomingDvdMovies { get; set; }
         public bool EnableIntrosFromUpcomingStreamingMovies { get; set; }
         public bool EnableIntrosFromUpcomingStreamingMovies { get; set; }
 
 
         public int TrailerLimit { get; set; }
         public int TrailerLimit { get; set; }
+        public string[] Tags { get; set; }
         
         
         public CinemaModeConfiguration()
         public CinemaModeConfiguration()
         {
         {
             EnableIntrosParentalControl = true;
             EnableIntrosParentalControl = true;
             EnableIntrosFromSimilarMovies = true;
             EnableIntrosFromSimilarMovies = true;
             TrailerLimit = 2;
             TrailerLimit = 2;
+            Tags = new[] { "thx" };
         }
         }
     }
     }
 }
 }

+ 9 - 22
MediaBrowser.Model/Configuration/ServerConfiguration.cs

@@ -62,6 +62,12 @@ namespace MediaBrowser.Model.Configuration
         /// <value><c>true</c> if this instance is port authorized; otherwise, <c>false</c>.</value>
         /// <value><c>true</c> if this instance is port authorized; otherwise, <c>false</c>.</value>
         public bool IsPortAuthorized { get; set; }
         public bool IsPortAuthorized { get; set; }
 
 
+        /// <summary>
+        /// Gets or sets a value indicating whether [enable high quality image scaling].
+        /// </summary>
+        /// <value><c>true</c> if [enable high quality image scaling]; otherwise, <c>false</c>.</value>
+        public bool EnableHighQualityImageScaling { get; set; }
+
         /// <summary>
         /// <summary>
         /// Gets or sets the item by name path.
         /// Gets or sets the item by name path.
         /// </summary>
         /// </summary>
@@ -92,24 +98,6 @@ namespace MediaBrowser.Model.Configuration
         /// <value><c>true</c> if [enable localized guids]; otherwise, <c>false</c>.</value>
         /// <value><c>true</c> if [enable localized guids]; otherwise, <c>false</c>.</value>
         public bool EnableLocalizedGuids { get; set; }
         public bool EnableLocalizedGuids { get; set; }
 
 
-        /// <summary>
-        /// Gets or sets a value indicating whether [disable startup scan].
-        /// </summary>
-        /// <value><c>true</c> if [disable startup scan]; otherwise, <c>false</c>.</value>
-        public bool DisableStartupScan { get; set; }
-
-        /// <summary>
-        /// Gets or sets a value indicating whether [enable user views].
-        /// </summary>
-        /// <value><c>true</c> if [enable user views]; otherwise, <c>false</c>.</value>
-        public bool EnableUserViews { get; set; }
-
-        /// <summary>
-        /// Gets or sets a value indicating whether [enable library metadata sub folder].
-        /// </summary>
-        /// <value><c>true</c> if [enable library metadata sub folder]; otherwise, <c>false</c>.</value>
-        public bool EnableLibraryMetadataSubFolder { get; set; }
-
         /// <summary>
         /// <summary>
         /// Gets or sets the preferred metadata language.
         /// Gets or sets the preferred metadata language.
         /// </summary>
         /// </summary>
@@ -219,21 +207,20 @@ namespace MediaBrowser.Model.Configuration
 
 
         public int SharingExpirationDays { get; set; }
         public int SharingExpirationDays { get; set; }
 
 
-        public bool DisableXmlSavers { get; set; }
         public bool EnableWindowsShortcuts { get; set; }
         public bool EnableWindowsShortcuts { get; set; }
 
 
-        public bool EnableVideoFrameByFrameAnalysis { get; set; }
-
         public bool EnableDateLastRefresh { get; set; }
         public bool EnableDateLastRefresh { get; set; }
 
 
         public string[] Migrations { get; set; }
         public string[] Migrations { get; set; }
 
 
+        public int MigrationVersion { get; set; }
+
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="ServerConfiguration" /> class.
         /// Initializes a new instance of the <see cref="ServerConfiguration" /> class.
         /// </summary>
         /// </summary>
         public ServerConfiguration()
         public ServerConfiguration()
         {
         {
-            Migrations = new string[] {};
+            Migrations = new string[] { };
 
 
             ImageSavingConvention = ImageSavingConvention.Compatible;
             ImageSavingConvention = ImageSavingConvention.Compatible;
             PublicPort = 8096;
             PublicPort = 8096;

+ 1 - 1
MediaBrowser.Providers/Folders/DefaultImageProvider.cs

@@ -170,4 +170,4 @@ namespace MediaBrowser.Providers.Folders
             return GetSupportedImages(item).Any(i => !item.HasImage(i));
             return GetSupportedImages(item).Any(i => !item.HasImage(i));
         }
         }
     }
     }
-}
+}

+ 3 - 0
MediaBrowser.Providers/Subtitles/SubtitleManager.cs

@@ -15,6 +15,7 @@ using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
+using System.Text;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using CommonIO;
 using CommonIO;
@@ -130,6 +131,8 @@ namespace MediaBrowser.Providers.Subtitles
 
 
                     try
                     try
                     {
                     {
+                        //var isText = MediaStream.IsTextFormat(response.Format);
+
                         using (var fs = _fileSystem.GetFileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.Read, true))
                         using (var fs = _fileSystem.GetFileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.Read, true))
                         {
                         {
                             await stream.CopyToAsync(fs).ConfigureAwait(false);
                             await stream.CopyToAsync(fs).ConfigureAwait(false);

+ 46 - 30
MediaBrowser.Server.Implementations/Channels/ChannelManager.cs

@@ -104,6 +104,11 @@ namespace MediaBrowser.Server.Implementations.Channels
                 .OrderBy(i => i.Name);
                 .OrderBy(i => i.Name);
         }
         }
 
 
+        public IEnumerable<Guid> GetInstalledChannelIds()
+        {
+            return GetAllChannels().Select(i => GetInternalChannelId(i.Name));
+        }
+
         public Task<QueryResult<Channel>> GetChannelsInternal(ChannelQuery query, CancellationToken cancellationToken)
         public Task<QueryResult<Channel>> GetChannelsInternal(ChannelQuery query, CancellationToken cancellationToken)
         {
         {
             var user = string.IsNullOrWhiteSpace(query.UserId)
             var user = string.IsNullOrWhiteSpace(query.UserId)
@@ -408,25 +413,15 @@ namespace MediaBrowser.Server.Implementations.Channels
 
 
         private async Task<Channel> GetChannel(IChannel channelInfo, CancellationToken cancellationToken)
         private async Task<Channel> GetChannel(IChannel channelInfo, CancellationToken cancellationToken)
         {
         {
+            var parentFolder = await GetInternalChannelFolder(cancellationToken).ConfigureAwait(false);
+            var parentFolderId = parentFolder.Id;
+
             var id = GetInternalChannelId(channelInfo.Name);
             var id = GetInternalChannelId(channelInfo.Name);
 
 
             var path = Channel.GetInternalMetadataPath(_config.ApplicationPaths.InternalMetadataPath, id);
             var path = Channel.GetInternalMetadataPath(_config.ApplicationPaths.InternalMetadataPath, id);
 
 
             var isNew = false;
             var isNew = false;
-
-            if (!_fileSystem.DirectoryExists(path))
-            {
-                _logger.Debug("Creating directory {0}", path);
-
-                _fileSystem.CreateDirectory(path);
-
-                if (!_fileSystem.DirectoryExists(path))
-                {
-                    throw new IOException("Path not created: " + path);
-                }
-
-                isNew = true;
-            }
+            var forceUpdate = false;
 
 
             var item = _libraryManager.GetItemById(id) as Channel;
             var item = _libraryManager.GetItemById(id) as Channel;
             var channelId = channelInfo.Name.GetMD5().ToString("N");
             var channelId = channelInfo.Name.GetMD5().ToString("N");
@@ -438,18 +433,29 @@ namespace MediaBrowser.Server.Implementations.Channels
                     Name = channelInfo.Name,
                     Name = channelInfo.Name,
                     Id = id,
                     Id = id,
                     DateCreated = _fileSystem.GetCreationTimeUtc(path),
                     DateCreated = _fileSystem.GetCreationTimeUtc(path),
-                    DateModified = _fileSystem.GetLastWriteTimeUtc(path),
-                    Path = path,
-                    ChannelId = channelId
+                    DateModified = _fileSystem.GetLastWriteTimeUtc(path)
                 };
                 };
 
 
                 isNew = true;
                 isNew = true;
             }
             }
 
 
-            if (!string.Equals(item.ChannelId, channelId, StringComparison.OrdinalIgnoreCase))
+            if (!string.Equals(item.Path, path, StringComparison.OrdinalIgnoreCase))
             {
             {
                 isNew = true;
                 isNew = true;
             }
             }
+            item.Path = path;
+
+            if (!string.Equals(item.ChannelId, channelId, StringComparison.OrdinalIgnoreCase))
+            {
+                forceUpdate = true;
+            }
+            item.ChannelId = channelId;
+
+            if (item.ParentId != parentFolderId)
+            {
+                forceUpdate = true;
+            }
+            item.ParentId = parentFolderId;
 
 
             item.OfficialRating = GetOfficialRating(channelInfo.ParentalRating);
             item.OfficialRating = GetOfficialRating(channelInfo.ParentalRating);
             item.Overview = channelInfo.Description;
             item.Overview = channelInfo.Description;
@@ -459,13 +465,17 @@ namespace MediaBrowser.Server.Implementations.Channels
             {
             {
                 item.Name = channelInfo.Name;
                 item.Name = channelInfo.Name;
             }
             }
-            
-            await item.RefreshMetadata(new MetadataRefreshOptions(_fileSystem)
-            {
-                ForceSave = isNew
 
 
-            }, cancellationToken);
+            if (isNew)
+            {
+                await _libraryManager.CreateItem(item, cancellationToken).ConfigureAwait(false);
+            }
+            else if (forceUpdate)
+            {
+                await item.UpdateToRepository(ItemUpdateType.None, cancellationToken).ConfigureAwait(false);
+            }
 
 
+            await item.RefreshMetadata(new MetadataRefreshOptions(_fileSystem), cancellationToken);
             return item;
             return item;
         }
         }
 
 
@@ -1225,6 +1235,7 @@ namespace MediaBrowser.Server.Implementations.Channels
         {
         {
             BaseItem item;
             BaseItem item;
             bool isNew;
             bool isNew;
+            bool forceUpdate = false;
 
 
             if (info.Type == ChannelItemType.Folder)
             if (info.Type == ChannelItemType.Folder)
             {
             {
@@ -1254,24 +1265,25 @@ namespace MediaBrowser.Server.Implementations.Channels
                 item.ProductionYear = info.ProductionYear;
                 item.ProductionYear = info.ProductionYear;
                 item.ProviderIds = info.ProviderIds;
                 item.ProviderIds = info.ProviderIds;
                 item.OfficialRating = info.OfficialRating;
                 item.OfficialRating = info.OfficialRating;
-
                 item.DateCreated = info.DateCreated ?? DateTime.UtcNow;
                 item.DateCreated = info.DateCreated ?? DateTime.UtcNow;
+                item.Tags = info.Tags;
             }
             }
 
 
             var channelItem = (IChannelItem)item;
             var channelItem = (IChannelItem)item;
 
 
             channelItem.ChannelId = internalChannelId.ToString("N");
             channelItem.ChannelId = internalChannelId.ToString("N");
 
 
-            if (!string.Equals(channelItem.ExternalId, info.Id, StringComparison.OrdinalIgnoreCase))
+            if (item.ParentId != internalChannelId)
             {
             {
-                isNew = true;
+                forceUpdate = true;
             }
             }
-            channelItem.ExternalId = info.Id;
+            item.ParentId = internalChannelId;
 
 
-            if (isNew)
+            if (!string.Equals(channelItem.ExternalId, info.Id, StringComparison.OrdinalIgnoreCase))
             {
             {
-                channelItem.Tags = info.Tags;
+                forceUpdate = true;
             }
             }
+            channelItem.ExternalId = info.Id;
 
 
             var channelMediaItem = item as IChannelMediaItem;
             var channelMediaItem = item as IChannelMediaItem;
 
 
@@ -1300,6 +1312,10 @@ namespace MediaBrowser.Server.Implementations.Channels
                     await _libraryManager.UpdatePeople(item, info.People ?? new List<PersonInfo>()).ConfigureAwait(false);
                     await _libraryManager.UpdatePeople(item, info.People ?? new List<PersonInfo>()).ConfigureAwait(false);
                 }
                 }
             }
             }
+            else if (forceUpdate)
+            {
+                await item.UpdateToRepository(ItemUpdateType.None, cancellationToken).ConfigureAwait(false);
+            }
 
 
             return item;
             return item;
         }
         }
@@ -1573,4 +1589,4 @@ namespace MediaBrowser.Server.Implementations.Channels
             }
             }
         }
         }
     }
     }
-}
+}

+ 4 - 4
MediaBrowser.Server.Implementations/Channels/ChannelPostScanTask.cs

@@ -123,15 +123,15 @@ namespace MediaBrowser.Server.Implementations.Channels
 
 
         private async Task CleanDatabase(CancellationToken cancellationToken)
         private async Task CleanDatabase(CancellationToken cancellationToken)
         {
         {
-            var allChannels = await _channelManager.GetChannelsInternal(new ChannelQuery { }, cancellationToken);
+            var installedChannelIds = ((ChannelManager)_channelManager).GetInstalledChannelIds();
 
 
-            var allIds = _libraryManager.GetItemIds(new InternalItemsQuery
+            var databaseIds = _libraryManager.GetItemIds(new InternalItemsQuery
             {
             {
                 IncludeItemTypes = new[] { typeof(Channel).Name }
                 IncludeItemTypes = new[] { typeof(Channel).Name }
             });
             });
 
 
-            var invalidIds = allIds
-                .Except(allChannels.Items.Select(i => i.Id).ToList())
+            var invalidIds = databaseIds
+                .Except(installedChannelIds)
                 .ToList();
                 .ToList();
 
 
             foreach (var id in invalidIds)
             foreach (var id in invalidIds)

+ 3 - 18
MediaBrowser.Server.Implementations/Collections/CollectionImageProvider.cs

@@ -59,7 +59,7 @@ namespace MediaBrowser.Server.Implementations.Collections
                         return subItem;
                         return subItem;
                     }
                     }
 
 
-                    var parent = subItem.Parent;
+                    var parent = subItem.GetParent();
 
 
                     if (parent != null && parent.HasImage(ImageType.Primary))
                     if (parent != null && parent.HasImage(ImageType.Primary))
                     {
                     {
@@ -78,24 +78,9 @@ namespace MediaBrowser.Server.Implementations.Collections
             return Task.FromResult(GetFinalItems(items, 2));
             return Task.FromResult(GetFinalItems(items, 2));
         }
         }
 
 
-        protected override async Task<string> CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
+        protected override Task<string> CreateImage(IHasImages item, List<BaseItem> itemsWithImages, string outputPathWithoutExtension, ImageType imageType, int imageIndex)
         {
         {
-            var image = itemsWithImages
-                .Where(i => i.HasImage(ImageType.Primary) && i.GetImageInfo(ImageType.Primary, 0).IsLocalFile && Path.HasExtension(i.GetImagePath(ImageType.Primary)))
-                .Select(i => i.GetImagePath(ImageType.Primary))
-                .FirstOrDefault();
-
-            if (string.IsNullOrWhiteSpace(image))
-            {
-                return null;
-            }
-
-            var ext = Path.GetExtension(image);
-
-            var outputPath = Path.ChangeExtension(outputPathWithoutExtension, ext);
-            File.Copy(image, outputPath);
-
-            return outputPath;
+            return CreateSingleImage(itemsWithImages, outputPathWithoutExtension, ImageType.Primary);
         }
         }
     }
     }
 }
 }

+ 1 - 0
MediaBrowser.Server.Implementations/Collections/CollectionManager.cs

@@ -40,6 +40,7 @@ namespace MediaBrowser.Server.Implementations.Collections
         public Folder GetCollectionsFolder(string userId)
         public Folder GetCollectionsFolder(string userId)
         {
         {
             return _libraryManager.RootFolder.Children.OfType<ManualCollectionsFolder>()
             return _libraryManager.RootFolder.Children.OfType<ManualCollectionsFolder>()
+                .FirstOrDefault() ?? _libraryManager.GetUserRootFolder().Children.OfType<ManualCollectionsFolder>()
                 .FirstOrDefault();
                 .FirstOrDefault();
         }
         }
 
 

+ 3 - 8
MediaBrowser.Server.Implementations/Collections/ManualCollectionsFolder.cs

@@ -3,7 +3,7 @@ using System.Linq;
 
 
 namespace MediaBrowser.Server.Implementations.Collections
 namespace MediaBrowser.Server.Implementations.Collections
 {
 {
-    public class ManualCollectionsFolder : BasePluginFolder
+    public class ManualCollectionsFolder : BasePluginFolder, IHiddenFromDisplay
     {
     {
         public ManualCollectionsFolder()
         public ManualCollectionsFolder()
         {
         {
@@ -11,11 +11,6 @@ namespace MediaBrowser.Server.Implementations.Collections
             DisplayMediaType = "CollectionFolder";
             DisplayMediaType = "CollectionFolder";
         }
         }
 
 
-        public override bool IsVisible(User user)
-        {
-            return base.IsVisible(user) && GetChildren(user, false).Any();
-        }
-
         public override bool IsHidden
         public override bool IsHidden
         {
         {
             get
             get
@@ -24,7 +19,7 @@ namespace MediaBrowser.Server.Implementations.Collections
             }
             }
         }
         }
 
 
-        public override bool IsHiddenFromUser(User user)
+        public bool IsHiddenFromUser(User user)
         {
         {
             return !user.Configuration.DisplayCollectionsView;
             return !user.Configuration.DisplayCollectionsView;
         }
         }
@@ -36,7 +31,7 @@ namespace MediaBrowser.Server.Implementations.Collections
 
 
         public override string GetClientTypeName()
         public override string GetClientTypeName()
         {
         {
-            return typeof (CollectionFolder).Name;
+            return typeof(CollectionFolder).Name;
         }
         }
     }
     }
 }
 }

+ 29 - 17
MediaBrowser.Server.Implementations/Devices/CameraUploadsFolder.cs

@@ -3,12 +3,15 @@ using MediaBrowser.Controller.Entities;
 using System;
 using System;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
+using System.Runtime.Serialization;
+using System.Threading;
+using System.Threading.Tasks;
 using CommonIO;
 using CommonIO;
-using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Providers;
 
 
 namespace MediaBrowser.Server.Implementations.Devices
 namespace MediaBrowser.Server.Implementations.Devices
 {
 {
-    public class CameraUploadsFolder : BasePluginFolder
+    public class CameraUploadsFolder : BasePluginFolder, ISupportsUserSpecificView
     {
     {
         public CameraUploadsFolder()
         public CameraUploadsFolder()
         {
         {
@@ -21,32 +24,41 @@ namespace MediaBrowser.Server.Implementations.Devices
             {
             {
                 return false;
                 return false;
             }
             }
-            
-            return GetChildren(user, true).Any() &&
-                base.IsVisible(user);
+
+            return base.IsVisible(user) && HasChildren();
         }
         }
 
 
-        public override bool IsHidden
+        public override string CollectionType
         {
         {
-            get
-            {
-                return base.IsHidden || !Children.Any();
-            }
+            get { return Model.Entities.CollectionType.Photos; }
         }
         }
 
 
-        public override bool IsHiddenFromUser(User user)
+        public override string GetClientTypeName()
         {
         {
-            return false;
+            return typeof(CollectionFolder).Name;
         }
         }
 
 
-        public override string CollectionType
+        private bool? _hasChildren;
+        private bool HasChildren()
         {
         {
-            get { return Model.Entities.CollectionType.Photos; }
+            if (!_hasChildren.HasValue)
+            {
+                _hasChildren = LibraryManager.GetItemIds(new InternalItemsQuery { ParentId = Id }).Count > 0;
+            }
+
+            return _hasChildren.Value;
         }
         }
 
 
-        public override string GetClientTypeName()
+        protected override Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService)
         {
         {
-            return typeof(CollectionFolder).Name;
+            _hasChildren = null;
+            return base.ValidateChildrenInternal(progress, cancellationToken, recursive, refreshChildMetadata, refreshOptions, directoryService);
+        }
+
+        [IgnoreDataMember]
+        public bool EnableUserSpecificView
+        {
+            get { return true; }
         }
         }
     }
     }
 
 
@@ -65,7 +77,7 @@ namespace MediaBrowser.Server.Implementations.Devices
         {
         {
             var path = Path.Combine(_appPaths.DataPath, "camerauploads");
             var path = Path.Combine(_appPaths.DataPath, "camerauploads");
 
 
-			_fileSystem.CreateDirectory(path);
+            _fileSystem.CreateDirectory(path);
 
 
             return new CameraUploadsFolder
             return new CameraUploadsFolder
             {
             {

+ 9 - 9
MediaBrowser.Server.Implementations/Devices/DeviceManager.cs

@@ -1,6 +1,5 @@
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Events;
 using MediaBrowser.Common.Events;
-using MediaBrowser.Common.IO;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Devices;
 using MediaBrowser.Controller.Devices;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
@@ -18,6 +17,7 @@ using System.IO;
 using System.Linq;
 using System.Linq;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using CommonIO;
 using CommonIO;
+using MediaBrowser.Controller.Configuration;
 
 
 namespace MediaBrowser.Server.Implementations.Devices
 namespace MediaBrowser.Server.Implementations.Devices
 {
 {
@@ -27,7 +27,7 @@ namespace MediaBrowser.Server.Implementations.Devices
         private readonly IUserManager _userManager;
         private readonly IUserManager _userManager;
         private readonly IFileSystem _fileSystem;
         private readonly IFileSystem _fileSystem;
         private readonly ILibraryMonitor _libraryMonitor;
         private readonly ILibraryMonitor _libraryMonitor;
-        private readonly IConfigurationManager _config;
+        private readonly IServerConfigurationManager _config;
         private readonly ILogger _logger;
         private readonly ILogger _logger;
         private readonly INetworkManager _network;
         private readonly INetworkManager _network;
 
 
@@ -38,7 +38,7 @@ namespace MediaBrowser.Server.Implementations.Devices
         /// </summary>
         /// </summary>
         public event EventHandler<GenericEventArgs<DeviceInfo>> DeviceOptionsUpdated;
         public event EventHandler<GenericEventArgs<DeviceInfo>> DeviceOptionsUpdated;
 
 
-        public DeviceManager(IDeviceRepository repo, IUserManager userManager, IFileSystem fileSystem, ILibraryMonitor libraryMonitor, IConfigurationManager config, ILogger logger, INetworkManager network)
+        public DeviceManager(IDeviceRepository repo, IUserManager userManager, IFileSystem fileSystem, ILibraryMonitor libraryMonitor, IServerConfigurationManager config, ILogger logger, INetworkManager network)
         {
         {
             _repo = repo;
             _repo = repo;
             _userManager = userManager;
             _userManager = userManager;
@@ -187,11 +187,6 @@ namespace MediaBrowser.Server.Implementations.Devices
             }
             }
         }
         }
 
 
-        private string GetUploadPath(string deviceId)
-        {
-            return GetUploadPath(GetDevice(deviceId));
-        }
-
         private string GetUploadPath(DeviceInfo device)
         private string GetUploadPath(DeviceInfo device)
         {
         {
             if (!string.IsNullOrWhiteSpace(device.CameraUploadPath))
             if (!string.IsNullOrWhiteSpace(device.CameraUploadPath))
@@ -205,7 +200,7 @@ namespace MediaBrowser.Server.Implementations.Devices
                 return config.CameraUploadPath;
                 return config.CameraUploadPath;
             }
             }
 
 
-            var path = Path.Combine(_config.CommonApplicationPaths.DataPath, "camerauploads");
+            var path = DefaultCameraUploadsPath;
 
 
             if (config.EnableCameraUploadSubfolders)
             if (config.EnableCameraUploadSubfolders)
             {
             {
@@ -215,6 +210,11 @@ namespace MediaBrowser.Server.Implementations.Devices
             return path;
             return path;
         }
         }
 
 
+        private string DefaultCameraUploadsPath
+        {
+            get { return Path.Combine(_config.CommonApplicationPaths.DataPath, "camerauploads"); }
+        }
+
         public async Task UpdateDeviceInfo(string id, DeviceOptions options)
         public async Task UpdateDeviceInfo(string id, DeviceOptions options)
         {
         {
             var device = GetDevice(id);
             var device = GetDevice(id);

+ 30 - 27
MediaBrowser.Server.Implementations/Dto/DtoService.cs

@@ -163,16 +163,11 @@ namespace MediaBrowser.Server.Implementations.Dto
 
 
             if (person != null)
             if (person != null)
             {
             {
-                var items = _libraryManager.GetItems(new InternalItemsQuery
+                var items = _libraryManager.GetItems(new InternalItemsQuery(user)
                 {
                 {
                     Person = byName.Name
                     Person = byName.Name
 
 
-                }).Items;
-
-                if (user != null)
-                {
-                    return items.Where(i => i.IsVisibleStandalone(user)).ToList();
-                }
+                }, new string[] { });
 
 
                 return items.ToList();
                 return items.ToList();
             }
             }
@@ -361,6 +356,8 @@ namespace MediaBrowser.Server.Implementations.Dto
             var collectionFolder = item as ICollectionFolder;
             var collectionFolder = item as ICollectionFolder;
             if (collectionFolder != null)
             if (collectionFolder != null)
             {
             {
+                dto.OriginalCollectionType = collectionFolder.CollectionType;
+
                 dto.CollectionType = user == null ?
                 dto.CollectionType = user == null ?
                     collectionFolder.CollectionType :
                     collectionFolder.CollectionType :
                     collectionFolder.GetViewType(user);
                     collectionFolder.GetViewType(user);
@@ -468,13 +465,15 @@ namespace MediaBrowser.Server.Implementations.Dto
 
 
                 var folder = (Folder)item;
                 var folder = (Folder)item;
 
 
-                dto.ChildCount = GetChildCount(folder, user);
-
-                // These are just far too slow. 
-                // TODO: Disable for CollectionFolder
-                if (!(folder is UserRootFolder) && !(folder is UserView))
+                if (!(folder is IChannelItem) && !(folder is Channel))
                 {
                 {
-                    SetSpecialCounts(folder, user, dto, fields, syncProgress);
+                    dto.ChildCount = GetChildCount(folder, user);
+
+                    // These are just far too slow. 
+                    if (!(folder is UserRootFolder) && !(folder is UserView) && !(folder is ICollectionFolder))
+                    {
+                        SetSpecialCounts(folder, user, dto, fields, syncProgress);
+                    }
                 }
                 }
 
 
                 dto.UserData.Played = dto.UserData.PlayedPercentage.HasValue && dto.UserData.PlayedPercentage.Value >= 100;
                 dto.UserData.Played = dto.UserData.PlayedPercentage.HasValue && dto.UserData.PlayedPercentage.Value >= 100;
@@ -815,7 +814,7 @@ namespace MediaBrowser.Server.Implementations.Dto
         /// <returns>BaseItem.</returns>
         /// <returns>BaseItem.</returns>
         private BaseItem GetParentBackdropItem(BaseItem item, BaseItem owner)
         private BaseItem GetParentBackdropItem(BaseItem item, BaseItem owner)
         {
         {
-            var parent = item.Parent ?? owner;
+            var parent = item.GetParent() ?? owner;
 
 
             while (parent != null)
             while (parent != null)
             {
             {
@@ -824,7 +823,7 @@ namespace MediaBrowser.Server.Implementations.Dto
                     return parent;
                     return parent;
                 }
                 }
 
 
-                parent = parent.Parent;
+                parent = parent.GetParent();
             }
             }
 
 
             return null;
             return null;
@@ -839,7 +838,7 @@ namespace MediaBrowser.Server.Implementations.Dto
         /// <returns>BaseItem.</returns>
         /// <returns>BaseItem.</returns>
         private BaseItem GetParentImageItem(BaseItem item, ImageType type, BaseItem owner)
         private BaseItem GetParentImageItem(BaseItem item, ImageType type, BaseItem owner)
         {
         {
-            var parent = item.Parent ?? owner;
+            var parent = item.GetParent() ?? owner;
 
 
             while (parent != null)
             while (parent != null)
             {
             {
@@ -848,7 +847,7 @@ namespace MediaBrowser.Server.Implementations.Dto
                     return parent;
                     return parent;
                 }
                 }
 
 
-                parent = parent.Parent;
+                parent = parent.GetParent();
             }
             }
 
 
             return null;
             return null;
@@ -1042,7 +1041,11 @@ namespace MediaBrowser.Server.Implementations.Dto
             dto.IsFolder = item.IsFolder;
             dto.IsFolder = item.IsFolder;
             dto.MediaType = item.MediaType;
             dto.MediaType = item.MediaType;
             dto.LocationType = item.LocationType;
             dto.LocationType = item.LocationType;
-            dto.IsHD = item.IsHD;
+            if (item.IsHD.HasValue && item.IsHD.Value)
+            {
+                dto.IsHD = item.IsHD;
+            }
+            dto.Audio = item.Audio;
 
 
             dto.PreferredMetadataCountryCode = item.PreferredMetadataCountryCode;
             dto.PreferredMetadataCountryCode = item.PreferredMetadataCountryCode;
             dto.PreferredMetadataLanguage = item.PreferredMetadataLanguage;
             dto.PreferredMetadataLanguage = item.PreferredMetadataLanguage;
@@ -1209,15 +1212,15 @@ namespace MediaBrowser.Server.Implementations.Dto
                 dto.VoteCount = item.VoteCount;
                 dto.VoteCount = item.VoteCount;
             }
             }
 
 
-            if (item.IsFolder)
-            {
-                var folder = (Folder)item;
+            //if (item.IsFolder)
+            //{
+            //    var folder = (Folder)item;
 
 
-                if (fields.Contains(ItemFields.IndexOptions))
-                {
-                    dto.IndexOptions = folder.IndexByOptionStrings.ToArray();
-                }
-            }
+            //    if (fields.Contains(ItemFields.IndexOptions))
+            //    {
+            //        dto.IndexOptions = folder.IndexByOptionStrings.ToArray();
+            //    }
+            //}
 
 
             var supportsPlaceHolders = item as ISupportsPlaceHolders;
             var supportsPlaceHolders = item as ISupportsPlaceHolders;
             if (supportsPlaceHolders != null)
             if (supportsPlaceHolders != null)
@@ -1520,7 +1523,7 @@ namespace MediaBrowser.Server.Implementations.Dto
             }
             }
 
 
             dto.ChannelId = item.ChannelId;
             dto.ChannelId = item.ChannelId;
-            
+
             var channelItem = item as IChannelItem;
             var channelItem = item as IChannelItem;
             if (channelItem != null)
             if (channelItem != null)
             {
             {

+ 1 - 1
MediaBrowser.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs

@@ -74,7 +74,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
                 // Go up one level for indicators
                 // Go up one level for indicators
                 if (baseItem != null)
                 if (baseItem != null)
                 {
                 {
-                    var parent = baseItem.Parent;
+                    var parent = baseItem.GetParent();
 
 
                     if (parent != null)
                     if (parent != null)
                     {
                     {

+ 11 - 4
MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs

@@ -217,7 +217,7 @@ namespace MediaBrowser.Server.Implementations.IO
         /// <param name="e">The <see cref="ItemChangeEventArgs"/> instance containing the event data.</param>
         /// <param name="e">The <see cref="ItemChangeEventArgs"/> instance containing the event data.</param>
         void LibraryManager_ItemRemoved(object sender, ItemChangeEventArgs e)
         void LibraryManager_ItemRemoved(object sender, ItemChangeEventArgs e)
         {
         {
-            if (e.Item.Parent is AggregateFolder)
+            if (e.Item.GetParent() is AggregateFolder)
             {
             {
                 StopWatchingPath(e.Item.Path);
                 StopWatchingPath(e.Item.Path);
             }
             }
@@ -230,7 +230,7 @@ namespace MediaBrowser.Server.Implementations.IO
         /// <param name="e">The <see cref="ItemChangeEventArgs"/> instance containing the event data.</param>
         /// <param name="e">The <see cref="ItemChangeEventArgs"/> instance containing the event data.</param>
         void LibraryManager_ItemAdded(object sender, ItemChangeEventArgs e)
         void LibraryManager_ItemAdded(object sender, ItemChangeEventArgs e)
         {
         {
-            if (e.Item.Parent is AggregateFolder)
+            if (e.Item.GetParent() is AggregateFolder)
             {
             {
                 StartWatchingPath(e.Item.Path);
                 StartWatchingPath(e.Item.Path);
             }
             }
@@ -532,9 +532,16 @@ namespace MediaBrowser.Server.Implementations.IO
                 return false;
                 return false;
             }
             }
 
 
+            // In order to determine if the file is being written to, we have to request write access
+            // But if the server only has readonly access, this is going to cause this entire algorithm to fail
+            // So we'll take a best guess about our access level
+            var requestedFileAccess = ConfigurationManager.Configuration.SaveLocalMeta
+                ? FileAccess.ReadWrite
+                : FileAccess.Read;
+
             try
             try
             {
             {
-                using (_fileSystem.GetFileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
+                using (_fileSystem.GetFileStream(path, FileMode.Open, requestedFileAccess, FileShare.ReadWrite))
                 {
                 {
                     if (_updateTimer != null)
                     if (_updateTimer != null)
                     {
                     {
@@ -651,7 +658,7 @@ namespace MediaBrowser.Server.Implementations.IO
                 // If the item has been deleted find the first valid parent that still exists
                 // If the item has been deleted find the first valid parent that still exists
 				while (!_fileSystem.DirectoryExists(item.Path) && !_fileSystem.FileExists(item.Path))
 				while (!_fileSystem.DirectoryExists(item.Path) && !_fileSystem.FileExists(item.Path))
                 {
                 {
-                    item = item.Parent;
+                    item = item.GetParent();
 
 
                     if (item == null)
                     if (item == null)
                     {
                     {

+ 19 - 15
MediaBrowser.Server.Implementations/Intros/DefaultIntroProvider.cs

@@ -78,13 +78,11 @@ namespace MediaBrowser.Server.Implementations.Intros
 
 
             if (config.EnableIntrosFromMoviesInLibrary)
             if (config.EnableIntrosFromMoviesInLibrary)
             {
             {
-                var inputItems = _libraryManager.GetItems(new InternalItemsQuery
+                var inputItems = _libraryManager.GetItems(new InternalItemsQuery(user)
                 {
                 {
-                    IncludeItemTypes = new[] { typeof(Movie).Name },
+                    IncludeItemTypes = new[] { typeof(Movie).Name }
 
 
-                    User = user
-
-                }).Items;
+                }, new string[]{});
 
 
                 var itemsWithTrailers = inputItems
                 var itemsWithTrailers = inputItems
                     .Where(i =>
                     .Where(i =>
@@ -163,7 +161,7 @@ namespace MediaBrowser.Server.Implementations.Intros
         private IEnumerable<IntroInfo> GetResult(BaseItem item, IEnumerable<ItemWithTrailer> candidates, CinemaModeConfiguration config, int? ratingLevel)
         private IEnumerable<IntroInfo> GetResult(BaseItem item, IEnumerable<ItemWithTrailer> candidates, CinemaModeConfiguration config, int? ratingLevel)
         {
         {
             var customIntros = !string.IsNullOrWhiteSpace(config.CustomIntroPath) ?
             var customIntros = !string.IsNullOrWhiteSpace(config.CustomIntroPath) ?
-                GetCustomIntros(item) :
+                GetCustomIntros(config) :
                 new List<IntroInfo>();
                 new List<IntroInfo>();
 
 
             var trailerLimit = config.TrailerLimit;
             var trailerLimit = config.TrailerLimit;
@@ -212,11 +210,11 @@ namespace MediaBrowser.Server.Implementations.Intros
             return _serverConfig.GetConfiguration<CinemaModeConfiguration>("cinemamode");
             return _serverConfig.GetConfiguration<CinemaModeConfiguration>("cinemamode");
         }
         }
 
 
-        private List<IntroInfo> GetCustomIntros(BaseItem item)
+        private List<IntroInfo> GetCustomIntros(CinemaModeConfiguration options)
         {
         {
             try
             try
             {
             {
-                return GetCustomIntroFiles()
+                return GetCustomIntroFiles(options, true, false)
                     .OrderBy(i => Guid.NewGuid())
                     .OrderBy(i => Guid.NewGuid())
                     .Select(i => new IntroInfo
                     .Select(i => new IntroInfo
                     {
                     {
@@ -230,17 +228,23 @@ namespace MediaBrowser.Server.Implementations.Intros
             }
             }
         }
         }
 
 
-        private IEnumerable<string> GetCustomIntroFiles(CinemaModeConfiguration options = null)
+        private IEnumerable<string> GetCustomIntroFiles(CinemaModeConfiguration options, bool enableCustomIntros, bool enableMediaInfoIntros)
         {
         {
-            options = options ?? GetOptions();
+            var list = new List<string>();
+
+            if (enableCustomIntros && !string.IsNullOrWhiteSpace(options.CustomIntroPath))
+            {
+                list.AddRange(_fileSystem.GetFilePaths(options.CustomIntroPath, true)
+                    .Where(_libraryManager.IsVideoFile));
+            }
 
 
-            if (string.IsNullOrWhiteSpace(options.CustomIntroPath))
+            if (enableMediaInfoIntros && !string.IsNullOrWhiteSpace(options.MediaInfoIntroPath))
             {
             {
-                return new List<string>();
+                list.AddRange(_fileSystem.GetFilePaths(options.MediaInfoIntroPath, true)
+                    .Where(_libraryManager.IsVideoFile));
             }
             }
 
 
-            return _fileSystem.GetFilePaths(options.CustomIntroPath, true)
-                .Where(_libraryManager.IsVideoFile);
+            return list.Distinct(StringComparer.OrdinalIgnoreCase);
         }
         }
 
 
         private bool FilterByParentalRating(int? ratingLevel, BaseItem item)
         private bool FilterByParentalRating(int? ratingLevel, BaseItem item)
@@ -341,7 +345,7 @@ namespace MediaBrowser.Server.Implementations.Intros
 
 
         public IEnumerable<string> GetAllIntroFiles()
         public IEnumerable<string> GetAllIntroFiles()
         {
         {
-            return GetCustomIntroFiles();
+            return GetCustomIntroFiles(GetOptions(), true, true);
         }
         }
 
 
         private bool IsSupporter
         private bool IsSupporter

+ 36 - 27
MediaBrowser.Server.Implementations/Library/CoreResolutionIgnoreRule.cs

@@ -47,11 +47,14 @@ namespace MediaBrowser.Server.Implementations.Library
         /// <summary>
         /// <summary>
         /// Shoulds the ignore.
         /// Shoulds the ignore.
         /// </summary>
         /// </summary>
-        /// <param name="args">The args.</param>
+        /// <param name="fileInfo">The file information.</param>
+        /// <param name="parent">The parent.</param>
         /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
         /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
-        public bool ShouldIgnore(ItemResolveArgs args)
+        public bool ShouldIgnore(FileSystemMetadata fileInfo, BaseItem parent)
         {
         {
-            var filename = args.FileInfo.Name;
+            var filename = fileInfo.Name;
+            var isHidden = (fileInfo.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden;
+            var path = fileInfo.FullName;
 
 
             // Handle mac .DS_Store
             // Handle mac .DS_Store
             // https://github.com/MediaBrowser/MediaBrowser/issues/427
             // https://github.com/MediaBrowser/MediaBrowser/issues/427
@@ -61,21 +64,24 @@ namespace MediaBrowser.Server.Implementations.Library
             }
             }
 
 
             // Ignore hidden files and folders
             // Ignore hidden files and folders
-            if (args.IsHidden)
+            if (isHidden)
             {
             {
-                var parentFolderName = Path.GetFileName(Path.GetDirectoryName(args.Path));
-
-                if (string.Equals(parentFolderName, BaseItem.ThemeSongsFolderName, StringComparison.OrdinalIgnoreCase))
-                {
-                    return false;
-                }
-                if (string.Equals(parentFolderName, BaseItem.ThemeVideosFolderName, StringComparison.OrdinalIgnoreCase))
+                if (parent == null)
                 {
                 {
-                    return false;
+                    var parentFolderName = Path.GetFileName(Path.GetDirectoryName(path));
+
+                    if (string.Equals(parentFolderName, BaseItem.ThemeSongsFolderName, StringComparison.OrdinalIgnoreCase))
+                    {
+                        return false;
+                    }
+                    if (string.Equals(parentFolderName, BaseItem.ThemeVideosFolderName, StringComparison.OrdinalIgnoreCase))
+                    {
+                        return false;
+                    }
                 }
                 }
 
 
                 // Sometimes these are marked hidden
                 // Sometimes these are marked hidden
-                if (_fileSystem.IsRootPath(args.Path))
+                if (_fileSystem.IsRootPath(path))
                 {
                 {
                     return false;
                     return false;
                 }
                 }
@@ -83,7 +89,7 @@ namespace MediaBrowser.Server.Implementations.Library
                 return true;
                 return true;
             }
             }
 
 
-            if (args.IsDirectory)
+            if (fileInfo.IsDirectory)
             {
             {
                 // Ignore any folders in our list
                 // Ignore any folders in our list
                 if (IgnoreFolders.Contains(filename, StringComparer.OrdinalIgnoreCase))
                 if (IgnoreFolders.Contains(filename, StringComparer.OrdinalIgnoreCase))
@@ -91,26 +97,29 @@ namespace MediaBrowser.Server.Implementations.Library
                     return true;
                     return true;
                 }
                 }
 
 
-                // Ignore trailer folders but allow it at the collection level
-                if (string.Equals(filename, BaseItem.TrailerFolderName, StringComparison.OrdinalIgnoreCase) &&
-                    !(args.Parent is AggregateFolder) && !(args.Parent is UserRootFolder))
+                if (parent != null)
                 {
                 {
-                    return true;
-                }
+                    // Ignore trailer folders but allow it at the collection level
+                    if (string.Equals(filename, BaseItem.TrailerFolderName, StringComparison.OrdinalIgnoreCase) &&
+                        !(parent is AggregateFolder) && !(parent is UserRootFolder))
+                    {
+                        return true;
+                    }
 
 
-                if (string.Equals(filename, BaseItem.ThemeVideosFolderName, StringComparison.OrdinalIgnoreCase))
-                {
-                    return true;
-                }
+                    if (string.Equals(filename, BaseItem.ThemeVideosFolderName, StringComparison.OrdinalIgnoreCase))
+                    {
+                        return true;
+                    }
 
 
-                if (string.Equals(filename, BaseItem.ThemeSongsFolderName, StringComparison.OrdinalIgnoreCase))
-                {
-                    return true;
+                    if (string.Equals(filename, BaseItem.ThemeSongsFolderName, StringComparison.OrdinalIgnoreCase))
+                    {
+                        return true;
+                    }
                 }
                 }
             }
             }
             else
             else
             {
             {
-                if (args.Parent != null)
+                if (parent != null)
                 {
                 {
                     // Don't resolve these into audio files
                     // Don't resolve these into audio files
                     if (string.Equals(_fileSystem.GetFileNameWithoutExtension(filename), BaseItem.ThemeSongFilename) && _libraryManager.IsAudioFile(filename))
                     if (string.Equals(_fileSystem.GetFileNameWithoutExtension(filename), BaseItem.ThemeSongFilename) && _libraryManager.IsAudioFile(filename))

+ 186 - 95
MediaBrowser.Server.Implementations/Library/LibraryManager.cs

@@ -27,6 +27,7 @@ using MediaBrowser.Server.Implementations.Library.Validators;
 using MediaBrowser.Server.Implementations.Logging;
 using MediaBrowser.Server.Implementations.Logging;
 using MediaBrowser.Server.Implementations.ScheduledTasks;
 using MediaBrowser.Server.Implementations.ScheduledTasks;
 using System;
 using System;
+using System.Collections;
 using System.Collections.Concurrent;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Globalization;
 using System.Globalization;
@@ -36,6 +37,7 @@ using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using CommonIO;
 using CommonIO;
 using MediaBrowser.Model.Extensions;
 using MediaBrowser.Model.Extensions;
+using MediaBrowser.Model.Library;
 using MoreLinq;
 using MoreLinq;
 using SortOrder = MediaBrowser.Model.Entities.SortOrder;
 using SortOrder = MediaBrowser.Model.Entities.SortOrder;
 
 
@@ -140,6 +142,7 @@ namespace MediaBrowser.Server.Implementations.Library
 
 
         private readonly Func<ILibraryMonitor> _libraryMonitorFactory;
         private readonly Func<ILibraryMonitor> _libraryMonitorFactory;
         private readonly Func<IProviderManager> _providerManagerFactory;
         private readonly Func<IProviderManager> _providerManagerFactory;
+        private readonly Func<IUserViewManager> _userviewManager;
 
 
         /// <summary>
         /// <summary>
         /// The _library items cache
         /// The _library items cache
@@ -167,7 +170,7 @@ namespace MediaBrowser.Server.Implementations.Library
         /// <param name="userManager">The user manager.</param>
         /// <param name="userManager">The user manager.</param>
         /// <param name="configurationManager">The configuration manager.</param>
         /// <param name="configurationManager">The configuration manager.</param>
         /// <param name="userDataRepository">The user data repository.</param>
         /// <param name="userDataRepository">The user data repository.</param>
-        public LibraryManager(ILogger logger, ITaskManager taskManager, IUserManager userManager, IServerConfigurationManager configurationManager, IUserDataManager userDataRepository, Func<ILibraryMonitor> libraryMonitorFactory, IFileSystem fileSystem, Func<IProviderManager> providerManagerFactory)
+        public LibraryManager(ILogger logger, ITaskManager taskManager, IUserManager userManager, IServerConfigurationManager configurationManager, IUserDataManager userDataRepository, Func<ILibraryMonitor> libraryMonitorFactory, IFileSystem fileSystem, Func<IProviderManager> providerManagerFactory, Func<IUserViewManager> userviewManager)
         {
         {
             _logger = logger;
             _logger = logger;
             _taskManager = taskManager;
             _taskManager = taskManager;
@@ -177,6 +180,7 @@ namespace MediaBrowser.Server.Implementations.Library
             _libraryMonitorFactory = libraryMonitorFactory;
             _libraryMonitorFactory = libraryMonitorFactory;
             _fileSystem = fileSystem;
             _fileSystem = fileSystem;
             _providerManagerFactory = providerManagerFactory;
             _providerManagerFactory = providerManagerFactory;
+            _userviewManager = userviewManager;
             ByReferenceItems = new ConcurrentDictionary<Guid, BaseItem>();
             ByReferenceItems = new ConcurrentDictionary<Guid, BaseItem>();
             _libraryItemsCache = new ConcurrentDictionary<Guid, BaseItem>();
             _libraryItemsCache = new ConcurrentDictionary<Guid, BaseItem>();
 
 
@@ -401,12 +405,12 @@ namespace MediaBrowser.Server.Implementations.Library
             {
             {
                 foreach (var path in item.GetDeletePaths().ToList())
                 foreach (var path in item.GetDeletePaths().ToList())
                 {
                 {
-					if (_fileSystem.DirectoryExists(path))
+                    if (_fileSystem.DirectoryExists(path))
                     {
                     {
                         _logger.Debug("Deleting path {0}", path);
                         _logger.Debug("Deleting path {0}", path);
                         _fileSystem.DeleteDirectory(path, true);
                         _fileSystem.DeleteDirectory(path, true);
                     }
                     }
-					else if (_fileSystem.FileExists(path))
+                    else if (_fileSystem.FileExists(path))
                     {
                     {
                         _logger.Debug("Deleting path {0}", path);
                         _logger.Debug("Deleting path {0}", path);
                         _fileSystem.DeleteFile(path);
                         _fileSystem.DeleteFile(path);
@@ -580,7 +584,7 @@ namespace MediaBrowser.Server.Implementations.Library
             };
             };
 
 
             // Return null if ignore rules deem that we should do so
             // Return null if ignore rules deem that we should do so
-            if (EntityResolutionIgnoreRules.Any(r => r.ShouldIgnore(args)))
+            if (IgnoreFile(args.FileInfo, args.Parent))
             {
             {
                 return null;
                 return null;
             }
             }
@@ -616,6 +620,11 @@ namespace MediaBrowser.Server.Implementations.Library
             return ResolveItem(args);
             return ResolveItem(args);
         }
         }
 
 
+        public bool IgnoreFile(FileSystemMetadata file, BaseItem parent)
+        {
+            return EntityResolutionIgnoreRules.Any(r => r.ShouldIgnore(file, parent));
+        }
+
         public IEnumerable<FileSystemMetadata> NormalizeRootPathList(IEnumerable<FileSystemMetadata> paths)
         public IEnumerable<FileSystemMetadata> NormalizeRootPathList(IEnumerable<FileSystemMetadata> paths)
         {
         {
             var originalList = paths.ToList();
             var originalList = paths.ToList();
@@ -651,7 +660,7 @@ namespace MediaBrowser.Server.Implementations.Library
 
 
         public IEnumerable<BaseItem> ResolvePaths(IEnumerable<FileSystemMetadata> files, IDirectoryService directoryService, Folder parent, string collectionType)
         public IEnumerable<BaseItem> ResolvePaths(IEnumerable<FileSystemMetadata> files, IDirectoryService directoryService, Folder parent, string collectionType)
         {
         {
-            var fileList = files.ToList();
+            var fileList = files.Where(i => !IgnoreFile(i, parent)).ToList();
 
 
             if (parent != null)
             if (parent != null)
             {
             {
@@ -702,7 +711,7 @@ namespace MediaBrowser.Server.Implementations.Library
         {
         {
             var rootFolderPath = ConfigurationManager.ApplicationPaths.RootFolderPath;
             var rootFolderPath = ConfigurationManager.ApplicationPaths.RootFolderPath;
 
 
-			_fileSystem.CreateDirectory(rootFolderPath);
+            _fileSystem.CreateDirectory(rootFolderPath);
 
 
             var rootFolder = GetItemById(GetNewItemId(rootFolderPath, typeof(AggregateFolder))) as AggregateFolder ?? (AggregateFolder)ResolvePath(_fileSystem.GetDirectoryInfo(rootFolderPath));
             var rootFolder = GetItemById(GetNewItemId(rootFolderPath, typeof(AggregateFolder))) as AggregateFolder ?? (AggregateFolder)ResolvePath(_fileSystem.GetDirectoryInfo(rootFolderPath));
 
 
@@ -732,6 +741,13 @@ namespace MediaBrowser.Server.Implementations.Library
                         folder = dbItem;
                         folder = dbItem;
                     }
                     }
 
 
+                    if (folder.ParentId != rootFolder.Id)
+                    {
+                        folder.ParentId = rootFolder.Id;
+                        var task = folder.UpdateToRepository(ItemUpdateType.MetadataImport, CancellationToken.None);
+                        Task.WaitAll(task);
+                    }
+
                     rootFolder.AddVirtualChild(folder);
                     rootFolder.AddVirtualChild(folder);
 
 
                     RegisterItem(folder);
                     RegisterItem(folder);
@@ -753,7 +769,7 @@ namespace MediaBrowser.Server.Implementations.Library
                     {
                     {
                         var userRootPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
                         var userRootPath = ConfigurationManager.ApplicationPaths.DefaultUserViewsPath;
 
 
-						_fileSystem.CreateDirectory(userRootPath);
+                        _fileSystem.CreateDirectory(userRootPath);
 
 
                         var tmpItem = GetItemById(GetNewItemId(userRootPath, typeof(UserRootFolder))) as UserRootFolder;
                         var tmpItem = GetItemById(GetNewItemId(userRootPath, typeof(UserRootFolder))) as UserRootFolder;
 
 
@@ -1005,9 +1021,9 @@ namespace MediaBrowser.Server.Implementations.Library
 
 
         private void SetPropertiesFromSongs(MusicArtist artist, IEnumerable<IHasMetadata> items)
         private void SetPropertiesFromSongs(MusicArtist artist, IEnumerable<IHasMetadata> items)
         {
         {
-            
+
         }
         }
-        
+
         /// <summary>
         /// <summary>
         /// Validate and refresh the People sub-set of the IBN.
         /// Validate and refresh the People sub-set of the IBN.
         /// The items are stored in the db but not loaded into memory until actually requested by an operation.
         /// The items are stored in the db but not loaded into memory until actually requested by an operation.
@@ -1018,7 +1034,7 @@ namespace MediaBrowser.Server.Implementations.Library
         public Task ValidatePeople(CancellationToken cancellationToken, IProgress<double> progress)
         public Task ValidatePeople(CancellationToken cancellationToken, IProgress<double> progress)
         {
         {
             // Ensure the location is available.
             // Ensure the location is available.
-			_fileSystem.CreateDirectory(ConfigurationManager.ApplicationPaths.PeoplePath);
+            _fileSystem.CreateDirectory(ConfigurationManager.ApplicationPaths.PeoplePath);
 
 
             return new PeopleValidator(this, _logger, ConfigurationManager, _fileSystem).ValidatePeople(cancellationToken, progress);
             return new PeopleValidator(this, _logger, ConfigurationManager, _fileSystem).ValidatePeople(cancellationToken, progress);
         }
         }
@@ -1265,6 +1281,11 @@ namespace MediaBrowser.Server.Implementations.Library
 
 
         public QueryResult<BaseItem> GetItems(InternalItemsQuery query)
         public QueryResult<BaseItem> GetItems(InternalItemsQuery query)
         {
         {
+            if (query.User != null)
+            {
+                AddUserToQuery(query, query.User);
+            }
+
             var result = ItemRepository.GetItemIdsList(query);
             var result = ItemRepository.GetItemIdsList(query);
 
 
             var items = result.Select(GetItemById).Where(i => i != null).ToArray();
             var items = result.Select(GetItemById).Where(i => i != null).ToArray();
@@ -1277,14 +1298,140 @@ namespace MediaBrowser.Server.Implementations.Library
 
 
         public QueryResult<BaseItem> QueryItems(InternalItemsQuery query)
         public QueryResult<BaseItem> QueryItems(InternalItemsQuery query)
         {
         {
+            if (query.User != null)
+            {
+                AddUserToQuery(query, query.User);
+            }
+
             return ItemRepository.GetItems(query);
             return ItemRepository.GetItems(query);
         }
         }
 
 
         public List<Guid> GetItemIds(InternalItemsQuery query)
         public List<Guid> GetItemIds(InternalItemsQuery query)
         {
         {
+            if (query.User != null)
+            {
+                AddUserToQuery(query, query.User);
+            }
+
             return ItemRepository.GetItemIdsList(query);
             return ItemRepository.GetItemIdsList(query);
         }
         }
 
 
+        public IEnumerable<BaseItem> GetItems(InternalItemsQuery query, IEnumerable<string> parentIds)
+        {
+            var parents = parentIds.Select(i => GetItemById(new Guid(i))).ToList();
+
+            SetTopParentIdsOrAncestors(query, parents);
+
+            return GetItemIds(query).Select(GetItemById);
+        }
+
+        public QueryResult<BaseItem> GetItemsResult(InternalItemsQuery query, IEnumerable<string> parentIds)
+        {
+            var parents = parentIds.Select(i => GetItemById(new Guid(i))).ToList();
+
+            SetTopParentIdsOrAncestors(query, parents);
+
+            return GetItems(query);
+        }
+
+        private void SetTopParentIdsOrAncestors(InternalItemsQuery query, List<BaseItem> parents)
+        {
+            if (parents.All(i =>
+            {
+                if ((i is ICollectionFolder) || (i is UserView))
+                {
+                    return true;
+                }
+
+                _logger.Debug("Query requires ancestor query due to type: " + i.GetType().Name);
+                return false;
+
+            }))
+            {
+                // Optimize by querying against top level views
+                query.TopParentIds = parents.SelectMany(i => GetTopParentsForQuery(i, query.User)).Select(i => i.Id.ToString("N")).ToArray();
+            }
+            else
+            {
+                // We need to be able to query from any arbitrary ancestor up the tree
+                query.AncestorIds = parents.SelectMany(i => i.GetIdsForAncestorQuery()).Select(i => i.ToString("N")).ToArray();
+            }
+        }
+
+        private void AddUserToQuery(InternalItemsQuery query, User user)
+        {
+            if (query.AncestorIds.Length == 0 && !query.ParentId.HasValue && query.ChannelIds.Length == 0 && query.TopParentIds.Length == 0)
+            {
+                var userViews = _userviewManager().GetUserViews(new UserViewQuery
+                {
+                    UserId = user.Id.ToString("N"),
+                    IncludeHidden = true
+
+                }, CancellationToken.None).Result.ToList();
+
+                query.TopParentIds = userViews.SelectMany(i => GetTopParentsForQuery(i, user)).Select(i => i.Id.ToString("N")).ToArray();
+            }
+        }
+
+        private IEnumerable<BaseItem> GetTopParentsForQuery(BaseItem item, User user)
+        {
+            var view = item as UserView;
+
+            if (view != null)
+            {
+                if (string.Equals(view.ViewType, CollectionType.LiveTv))
+                {
+                    return new[] { view };
+                }
+                if (string.Equals(view.ViewType, CollectionType.Channels))
+                {
+                    // TODO: Return channels
+                    return new[] { view };
+                }
+
+                // Translate view into folders
+                if (view.DisplayParentId != Guid.Empty)
+                {
+                    var displayParent = GetItemById(view.DisplayParentId);
+                    if (displayParent != null)
+                    {
+                        return GetTopParentsForQuery(displayParent, user);
+                    }
+                    return new BaseItem[] { };
+                }
+                if (view.ParentId != Guid.Empty)
+                {
+                    var displayParent = GetItemById(view.ParentId);
+                    if (displayParent != null)
+                    {
+                        return GetTopParentsForQuery(displayParent, user);
+                    }
+                    return new BaseItem[] { };
+                }
+
+                // Handle grouping
+                if (user != null && !string.IsNullOrWhiteSpace(view.ViewType) && UserView.IsEligibleForGrouping(view.ViewType))
+                {
+                    var collectionFolders = user.RootFolder.GetChildren(user, true).OfType<CollectionFolder>().Where(i => string.IsNullOrWhiteSpace(i.CollectionType) || string.Equals(i.CollectionType, view.ViewType, StringComparison.OrdinalIgnoreCase));
+                    return collectionFolders.SelectMany(i => GetTopParentsForQuery(i, user));
+                }
+                return new BaseItem[] { };
+            }
+
+            var collectionFolder = item as CollectionFolder;
+            if (collectionFolder != null)
+            {
+                return collectionFolder.GetPhysicalParents();
+            }
+
+            var topParent = item.GetTopParent();
+            if (topParent != null)
+            {
+                return new[] { topParent };
+            }
+            return new BaseItem[] { };
+        }
+
         /// <summary>
         /// <summary>
         /// Gets the intros.
         /// Gets the intros.
         /// </summary>
         /// </summary>
@@ -1577,9 +1724,9 @@ namespace MediaBrowser.Server.Implementations.Library
 
 
         public IEnumerable<Folder> GetCollectionFolders(BaseItem item)
         public IEnumerable<Folder> GetCollectionFolders(BaseItem item)
         {
         {
-            while (!(item.Parent is AggregateFolder) && item.Parent != null)
+            while (!(item.GetParent() is AggregateFolder) && item.GetParent() != null)
             {
             {
-                item = item.Parent;
+                item = item.GetParent();
             }
             }
 
 
             if (item == null)
             if (item == null)
@@ -1616,7 +1763,7 @@ namespace MediaBrowser.Server.Implementations.Library
                 return type;
                 return type;
             }
             }
 
 
-            return item.Parents
+            return item.GetParents()
                 .Select(GetConfiguredContentType)
                 .Select(GetConfiguredContentType)
                 .LastOrDefault(i => !string.IsNullOrWhiteSpace(i));
                 .LastOrDefault(i => !string.IsNullOrWhiteSpace(i));
         }
         }
@@ -1653,14 +1800,14 @@ namespace MediaBrowser.Server.Implementations.Library
 
 
         private string GetTopFolderContentType(BaseItem item)
         private string GetTopFolderContentType(BaseItem item)
         {
         {
-            while (!(item.Parent is AggregateFolder) && item.Parent != null)
+            if (item == null)
             {
             {
-                item = item.Parent;
+                return null;
             }
             }
 
 
-            if (item == null)
+            while (!(item.GetParent() is AggregateFolder) && item.GetParent() != null)
             {
             {
-                return null;
+                item = item.GetParent();
             }
             }
 
 
             return GetUserRootFolder().Children
             return GetUserRootFolder().Children
@@ -1679,7 +1826,7 @@ namespace MediaBrowser.Server.Implementations.Library
             string sortName,
             string sortName,
             CancellationToken cancellationToken)
             CancellationToken cancellationToken)
         {
         {
-            return GetNamedViewInternal(user, name, null, viewType, sortName, null, cancellationToken);
+            return GetNamedView(user, name, null, viewType, sortName, cancellationToken);
         }
         }
 
 
         public async Task<UserView> GetNamedView(string name,
         public async Task<UserView> GetNamedView(string name,
@@ -1697,10 +1844,9 @@ namespace MediaBrowser.Server.Implementations.Library
 
 
             var refresh = false;
             var refresh = false;
 
 
-            if (item == null ||
-                !string.Equals(item.Path, path, StringComparison.OrdinalIgnoreCase))
+            if (item == null || !string.Equals(item.Path, path, StringComparison.OrdinalIgnoreCase))
             {
             {
-				_fileSystem.CreateDirectory(path);
+                _fileSystem.CreateDirectory(path);
 
 
                 item = new UserView
                 item = new UserView
                 {
                 {
@@ -1717,12 +1863,6 @@ namespace MediaBrowser.Server.Implementations.Library
                 refresh = true;
                 refresh = true;
             }
             }
 
 
-            if (!string.Equals(viewType, item.ViewType, StringComparison.OrdinalIgnoreCase))
-            {
-                item.ViewType = viewType;
-                await item.UpdateToRepository(ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false);
-            }
-
             if (!refresh)
             if (!refresh)
             {
             {
                 refresh = (DateTime.UtcNow - item.DateLastRefreshed) >= _viewRefreshInterval;
                 refresh = (DateTime.UtcNow - item.DateLastRefreshed) >= _viewRefreshInterval;
@@ -1748,40 +1888,14 @@ namespace MediaBrowser.Server.Implementations.Library
             return item;
             return item;
         }
         }
 
 
-        public Task<UserView> GetNamedView(User user,
+        public async Task<UserView> GetNamedView(User user,
             string name,
             string name,
             string parentId,
             string parentId,
             string viewType,
             string viewType,
             string sortName,
             string sortName,
-            string uniqueId,
             CancellationToken cancellationToken)
             CancellationToken cancellationToken)
         {
         {
-            if (string.IsNullOrWhiteSpace(parentId))
-            {
-                throw new ArgumentNullException("parentId");
-            }
-
-            return GetNamedViewInternal(user, name, parentId, viewType, sortName, uniqueId, cancellationToken);
-        }
-
-        private async Task<UserView> GetNamedViewInternal(User user,
-            string name,
-            string parentId,
-            string viewType,
-            string sortName,
-            string uniqueId,
-            CancellationToken cancellationToken)
-        {
-            if (string.IsNullOrWhiteSpace(name))
-            {
-                throw new ArgumentNullException("name");
-            }
-
-            var idValues = "37_namedview_" + name + user.Id.ToString("N") + (parentId ?? string.Empty);
-            if (!string.IsNullOrWhiteSpace(uniqueId))
-            {
-                idValues += uniqueId;
-            }
+            var idValues = "38_namedview_" + name + user.Id.ToString("N") + (parentId ?? string.Empty) + (viewType ?? string.Empty);
 
 
             var id = GetNewItemId(idValues, typeof(UserView));
             var id = GetNewItemId(idValues, typeof(UserView));
 
 
@@ -1793,7 +1907,7 @@ namespace MediaBrowser.Server.Implementations.Library
 
 
             if (item == null)
             if (item == null)
             {
             {
-				_fileSystem.CreateDirectory(path);
+                _fileSystem.CreateDirectory(path);
 
 
                 item = new UserView
                 item = new UserView
                 {
                 {
@@ -1816,18 +1930,6 @@ namespace MediaBrowser.Server.Implementations.Library
                 isNew = true;
                 isNew = true;
             }
             }
 
 
-            if (!item.UserId.HasValue)
-            {
-                item.UserId = user.Id;
-                await item.UpdateToRepository(ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false);
-            }
-
-            if (!string.Equals(viewType, item.ViewType, StringComparison.OrdinalIgnoreCase))
-            {
-                item.ViewType = viewType;
-                await item.UpdateToRepository(ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false);
-            }
-
             var refresh = isNew || (DateTime.UtcNow - item.DateLastRefreshed) >= _viewRefreshInterval;
             var refresh = isNew || (DateTime.UtcNow - item.DateLastRefreshed) >= _viewRefreshInterval;
 
 
             if (!refresh && item.DisplayParentId != Guid.Empty)
             if (!refresh && item.DisplayParentId != Guid.Empty)
@@ -1851,7 +1953,6 @@ namespace MediaBrowser.Server.Implementations.Library
         public async Task<UserView> GetShadowView(BaseItem parent,
         public async Task<UserView> GetShadowView(BaseItem parent,
         string viewType,
         string viewType,
         string sortName,
         string sortName,
-        string uniqueId,
         CancellationToken cancellationToken)
         CancellationToken cancellationToken)
         {
         {
             if (parent == null)
             if (parent == null)
@@ -1862,11 +1963,7 @@ namespace MediaBrowser.Server.Implementations.Library
             var name = parent.Name;
             var name = parent.Name;
             var parentId = parent.Id;
             var parentId = parent.Id;
 
 
-            var idValues = "37_namedview_" + name + parentId + (viewType ?? string.Empty);
-            if (!string.IsNullOrWhiteSpace(uniqueId))
-            {
-                idValues += uniqueId;
-            }
+            var idValues = "38_namedview_" + name + parentId + (viewType ?? string.Empty);
 
 
             var id = GetNewItemId(idValues, typeof(UserView));
             var id = GetNewItemId(idValues, typeof(UserView));
 
 
@@ -1897,12 +1994,6 @@ namespace MediaBrowser.Server.Implementations.Library
                 isNew = true;
                 isNew = true;
             }
             }
 
 
-            if (!string.Equals(viewType, item.ViewType, StringComparison.OrdinalIgnoreCase))
-            {
-                item.ViewType = viewType;
-                await item.UpdateToRepository(ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false);
-            }
-
             var refresh = isNew || (DateTime.UtcNow - item.DateLastRefreshed) >= _viewRefreshInterval;
             var refresh = isNew || (DateTime.UtcNow - item.DateLastRefreshed) >= _viewRefreshInterval;
 
 
             if (!refresh && item.DisplayParentId != Guid.Empty)
             if (!refresh && item.DisplayParentId != Guid.Empty)
@@ -1922,7 +2013,7 @@ namespace MediaBrowser.Server.Implementations.Library
 
 
             return item;
             return item;
         }
         }
-        
+
         public async Task<UserView> GetNamedView(string name,
         public async Task<UserView> GetNamedView(string name,
             string parentId,
             string parentId,
             string viewType,
             string viewType,
@@ -1951,7 +2042,7 @@ namespace MediaBrowser.Server.Implementations.Library
 
 
             if (item == null)
             if (item == null)
             {
             {
-				_fileSystem.CreateDirectory(path);
+                _fileSystem.CreateDirectory(path);
 
 
                 item = new UserView
                 item = new UserView
                 {
                 {
@@ -2198,21 +2289,21 @@ namespace MediaBrowser.Server.Implementations.Library
             return ResolvePaths(files, directoryService, null, null)
             return ResolvePaths(files, directoryService, null, null)
                 .OfType<Video>()
                 .OfType<Video>()
                 .Select(video =>
                 .Select(video =>
-            {
-                // Try to retrieve it from the db. If we don't find it, use the resolved version
-                var dbItem = GetItemById(video.Id) as Video;
-
-                if (dbItem != null)
                 {
                 {
-                    video = dbItem;
-                }
+                    // Try to retrieve it from the db. If we don't find it, use the resolved version
+                    var dbItem = GetItemById(video.Id) as Video;
 
 
-                video.ExtraType = ExtraType.Trailer;
+                    if (dbItem != null)
+                    {
+                        video = dbItem;
+                    }
 
 
-                return video;
+                    video.ExtraType = ExtraType.Trailer;
 
 
-                // Sort them so that the list can be easily compared for changes
-            }).OrderBy(i => i.Path).ToList();
+                    return video;
+
+                    // Sort them so that the list can be easily compared for changes
+                }).OrderBy(i => i.Path).ToList();
         }
         }
 
 
         public IEnumerable<Video> FindExtras(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
         public IEnumerable<Video> FindExtras(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
@@ -2384,7 +2475,7 @@ namespace MediaBrowser.Server.Implementations.Library
             return ItemRepository.UpdatePeople(item.Id, people);
             return ItemRepository.UpdatePeople(item.Id, people);
         }
         }
 
 
-        private readonly SemaphoreSlim _dynamicImageResourcePool = new SemaphoreSlim(1,1);
+        private readonly SemaphoreSlim _dynamicImageResourcePool = new SemaphoreSlim(1, 1);
         public async Task<ItemImageInfo> ConvertImageToLocal(IHasImages item, ItemImageInfo image, int imageIndex)
         public async Task<ItemImageInfo> ConvertImageToLocal(IHasImages item, ItemImageInfo image, int imageIndex)
         {
         {
             _logger.Debug("ConvertImageToLocal item {0}", item.Id);
             _logger.Debug("ConvertImageToLocal item {0}", item.Id);
@@ -2403,4 +2494,4 @@ namespace MediaBrowser.Server.Implementations.Library
             return item.GetImageInfo(image.Type, imageIndex);
             return item.GetImageInfo(image.Type, imageIndex);
         }
         }
     }
     }
-}
+}

+ 3 - 5
MediaBrowser.Server.Implementations/Library/MusicManager.cs

@@ -80,15 +80,13 @@ namespace MediaBrowser.Server.Implementations.Library
         {
         {
             var genreList = genres.ToList();
             var genreList = genres.ToList();
 
 
-            var inputItems = _libraryManager.GetItems(new InternalItemsQuery
+            var inputItems = _libraryManager.GetItems(new InternalItemsQuery(user)
             {
             {
                 IncludeItemTypes = new[] { typeof(Audio).Name },
                 IncludeItemTypes = new[] { typeof(Audio).Name },
 
 
-                Genres = genreList.ToArray(),
+                Genres = genreList.ToArray()
 
 
-                User = user
-
-            }).Items;
+            }, new string[] { });
 
 
             var genresDictionary = genreList.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
             var genresDictionary = genreList.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
 
 

+ 2 - 2
MediaBrowser.Server.Implementations/Library/ResolverHelper.cs

@@ -41,7 +41,7 @@ namespace MediaBrowser.Server.Implementations.Library
             item.Id = libraryManager.GetNewItemId(item.Path, item.GetType());
             item.Id = libraryManager.GetNewItemId(item.Path, item.GetType());
 
 
             item.IsLocked = item.Path.IndexOf("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) != -1 ||
             item.IsLocked = item.Path.IndexOf("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) != -1 ||
-                item.Parents.Any(i => i.IsLocked);
+                item.GetParents().Any(i => i.IsLocked);
 
 
             // Make sure DateCreated and DateModified have values
             // Make sure DateCreated and DateModified have values
             var fileInfo = directoryService.GetFile(item.Path);
             var fileInfo = directoryService.GetFile(item.Path);
@@ -78,7 +78,7 @@ namespace MediaBrowser.Server.Implementations.Library
             EnsureName(item, args.FileInfo);
             EnsureName(item, args.FileInfo);
 
 
             item.IsLocked = item.Path.IndexOf("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) != -1 ||
             item.IsLocked = item.Path.IndexOf("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) != -1 ||
-                item.Parents.Any(i => i.IsLocked);
+                item.GetParents().Any(i => i.IsLocked);
 
 
             // Make sure DateCreated and DateModified have values
             // Make sure DateCreated and DateModified have values
             EnsureDates(fileSystem, item, args, true);
             EnsureDates(fileSystem, item, args, true);

+ 13 - 9
MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs

@@ -67,7 +67,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
             string collectionType,
             string collectionType,
             IDirectoryService directoryService)
             IDirectoryService directoryService)
         {
         {
-            if (IsInvalid(parent, collectionType, files))
+            if (IsInvalid(parent, collectionType))
             {
             {
                 return null;
                 return null;
             }
             }
@@ -95,7 +95,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
                     return ResolveVideos<Video>(parent, files, directoryService, false);
                     return ResolveVideos<Video>(parent, files, directoryService, false);
                 }
                 }
 
 
-                if (parent is Series || parent.Parents.OfType<Series>().Any())
+                if (parent is Series || parent.GetParents().OfType<Series>().Any())
                 {
                 {
                     return null;
                     return null;
                 }
                 }
@@ -185,7 +185,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
         {
         {
             var collectionType = args.GetCollectionType();
             var collectionType = args.GetCollectionType();
 
 
-            if (IsInvalid(args.Parent, collectionType, args.FileSystemChildren))
+            if (IsInvalid(args.Parent, collectionType))
             {
             {
                 return null;
                 return null;
             }
             }
@@ -193,14 +193,18 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
             // Find movies with their own folders
             // Find movies with their own folders
             if (args.IsDirectory)
             if (args.IsDirectory)
             {
             {
+                var files = args.FileSystemChildren
+                    .Where(i => !LibraryManager.IgnoreFile(i, args.Parent))
+                    .ToList();
+
                 if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
                 if (string.Equals(collectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase))
                 {
                 {
-                    return FindMovie<MusicVideo>(args.Path, args.Parent, args.FileSystemChildren.ToList(), args.DirectoryService, collectionType);
+                    return FindMovie<MusicVideo>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
                 }
                 }
 
 
                 if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase))
                 if (string.Equals(collectionType, CollectionType.HomeVideos, StringComparison.OrdinalIgnoreCase))
                 {
                 {
-                    return FindMovie<Video>(args.Path, args.Parent, args.FileSystemChildren.ToList(), args.DirectoryService, collectionType);
+                    return FindMovie<Video>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
                 }
                 }
 
 
                 if (string.IsNullOrEmpty(collectionType))
                 if (string.IsNullOrEmpty(collectionType))
@@ -208,7 +212,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
                     // Owned items should just use the plain video type
                     // Owned items should just use the plain video type
                     if (args.Parent == null)
                     if (args.Parent == null)
                     {
                     {
-                        return FindMovie<Video>(args.Path, args.Parent, args.FileSystemChildren.ToList(), args.DirectoryService, collectionType);
+                        return FindMovie<Video>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
                     }
                     }
 
 
                     if (args.HasParent<Series>())
                     if (args.HasParent<Series>())
@@ -216,12 +220,12 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
                         return null;
                         return null;
                     }
                     }
 
 
-                    return FindMovie<Movie>(args.Path, args.Parent, args.FileSystemChildren.ToList(), args.DirectoryService, collectionType);
+                    return FindMovie<Movie>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
                 }
                 }
 
 
                 if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
                 if (string.Equals(collectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
                 {
                 {
-                    return FindMovie<Movie>(args.Path, args.Parent, args.FileSystemChildren.ToList(), args.DirectoryService, collectionType);
+                    return FindMovie<Movie>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
                 }
                 }
 
 
                 return null;
                 return null;
@@ -494,7 +498,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
             };
             };
         }
         }
 
 
-        private bool IsInvalid(Folder parent, string collectionType, IEnumerable<FileSystemMetadata> files)
+        private bool IsInvalid(Folder parent, string collectionType)
         {
         {
             if (parent != null)
             if (parent != null)
             {
             {

+ 1 - 1
MediaBrowser.Server.Implementations/Library/Resolvers/TV/EpisodeResolver.cs

@@ -33,7 +33,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
             // Not officially supported but in some cases we can handle it.
             // Not officially supported but in some cases we can handle it.
             if (season == null)
             if (season == null)
             {
             {
-                season = parent.Parents.OfType<Season>().FirstOrDefault();
+                season = parent.GetParents().OfType<Season>().FirstOrDefault();
             }
             }
 
 
             // If the parent is a Season or Series, then this is an Episode if the VideoResolver returns something
             // If the parent is a Season or Series, then this is an Episode if the VideoResolver returns something

+ 5 - 25
MediaBrowser.Server.Implementations/Library/SearchEngine.cs

@@ -156,19 +156,18 @@ namespace MediaBrowser.Server.Implementations.Library
             }
             }
 
 
             AddIfMissing(excludeItemTypes, typeof(CollectionFolder).Name);
             AddIfMissing(excludeItemTypes, typeof(CollectionFolder).Name);
-            
-            var mediaItems = _libraryManager.GetItems(new InternalItemsQuery
+
+            var mediaItems = _libraryManager.GetItems(new InternalItemsQuery(user)
             {
             {
                 NameContains = searchTerm,
                 NameContains = searchTerm,
                 ExcludeItemTypes = excludeItemTypes.ToArray(),
                 ExcludeItemTypes = excludeItemTypes.ToArray(),
                 IncludeItemTypes = includeItemTypes.ToArray(),
                 IncludeItemTypes = includeItemTypes.ToArray(),
-                MaxParentalRating = user == null ? null : user.Policy.MaxParentalRating,
-                Limit = (query.Limit.HasValue ? (int?)(query.Limit.Value * 3) : null),
+                Limit = query.Limit,
 
 
-            }).Items;
+            }, new string[] { });
 
 
             // Add search hints based on item name
             // Add search hints based on item name
-            hints.AddRange(mediaItems.Where(i => IncludeInSearch(i) && IsVisible(i, user)).Select(item =>
+            hints.AddRange(mediaItems.Where(IncludeInSearch).Select(item =>
             {
             {
                 var index = GetIndex(item.Name, searchTerm, terms);
                 var index = GetIndex(item.Name, searchTerm, terms);
 
 
@@ -184,25 +183,6 @@ namespace MediaBrowser.Server.Implementations.Library
             return Task.FromResult(returnValue);
             return Task.FromResult(returnValue);
         }
         }
 
 
-        private bool IsVisible(BaseItem item, User user)
-        {
-            if (user == null)
-            {
-                return true;
-            }
-
-            if (item is IItemByName)
-            {
-                var dual = item as IHasDualAccess;
-                if (dual == null || dual.IsAccessedByName)
-                {
-                    return true;
-                }
-            }
-
-            return item.IsVisibleStandalone(user);
-        }
-
         private bool IncludeInSearch(BaseItem item)
         private bool IncludeInSearch(BaseItem item)
         {
         {
             var episode = item as Episode;
             var episode = item as Episode;

+ 1 - 2
MediaBrowser.Server.Implementations/Library/UserManager.cs

@@ -707,8 +707,7 @@ namespace MediaBrowser.Server.Implementations.Library
                 Id = Guid.NewGuid(),
                 Id = Guid.NewGuid(),
                 DateCreated = DateTime.UtcNow,
                 DateCreated = DateTime.UtcNow,
                 DateModified = DateTime.UtcNow,
                 DateModified = DateTime.UtcNow,
-                UsesIdForConfigurationPath = true,
-                EnableUserViews = true
+                UsesIdForConfigurationPath = true
             };
             };
         }
         }
 
 

+ 89 - 173
MediaBrowser.Server.Implementations/Library/UserViewManager.cs

@@ -16,6 +16,7 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
+using MediaBrowser.Controller.Entities.Audio;
 
 
 namespace MediaBrowser.Server.Implementations.Library
 namespace MediaBrowser.Server.Implementations.Library
 {
 {
@@ -48,103 +49,63 @@ namespace MediaBrowser.Server.Implementations.Library
                 .OfType<Folder>()
                 .OfType<Folder>()
                 .ToList();
                 .ToList();
 
 
-            var plainFolderIds = user.Configuration.PlainFolderViews.Select(i => new Guid(i)).ToList();
+            if (!query.IncludeHidden)
+            {
+                folders = folders.Where(i =>
+                {
+                    var hidden = i as IHiddenFromDisplay;
+                    return hidden == null || !hidden.IsHiddenFromUser(user);
+                }).ToList();
+            }
 
 
-            var standaloneFolders = folders
-                .Where(i => UserView.IsExcludedFromGrouping(i) || !user.IsFolderGrouped(i.Id))
-                .ToList();
+            var plainFolderIds = user.Configuration.PlainFolderViews.Select(i => new Guid(i)).ToList();
 
 
-            var foldersWithViewTypes = folders
-                .Except(standaloneFolders)
-                .OfType<ICollectionFolder>()
-                .ToList();
+            var groupedFolders = new List<ICollectionFolder>();
 
 
             var list = new List<Folder>();
             var list = new List<Folder>();
 
 
-            var enableUserViews = _config.Configuration.EnableUserViews || user.EnableUserViews;
-
-            if (enableUserViews)
+            foreach (var folder in folders)
             {
             {
-                foreach (var folder in standaloneFolders)
+                var collectionFolder = folder as ICollectionFolder;
+                var folderViewType = collectionFolder == null ? null : collectionFolder.CollectionType;
+
+                if (UserView.IsUserSpecific(folder))
                 {
                 {
-                    var collectionFolder = folder as ICollectionFolder;
-                    var folderViewType = collectionFolder == null ? null : collectionFolder.CollectionType;
+                    list.Add(await _libraryManager.GetNamedView(user, folder.Name, folder.Id.ToString("N"), folderViewType, null, cancellationToken).ConfigureAwait(false));
+                    continue;
+                }
 
 
-                    if (UserView.IsUserSpecific(folder))
-                    {
-                        list.Add(await GetUserView(folder.Id, folder.Name, folderViewType, true, string.Empty, user, cancellationToken).ConfigureAwait(false));
-                    }
-                    else if (plainFolderIds.Contains(folder.Id))
-                    {
-                        list.Add(await GetUserView(folder, folderViewType, false, string.Empty, cancellationToken).ConfigureAwait(false));
-                    }
-                    else if (!string.IsNullOrWhiteSpace(folderViewType))
-                    {
-                        list.Add(await GetUserView(folder, folderViewType, true, string.Empty, cancellationToken).ConfigureAwait(false));
-                    }
-                    else
-                    {
-                        list.Add(folder);
-                    }
+                if (plainFolderIds.Contains(folder.Id) && UserView.IsEligibleForEnhancedView(folderViewType))
+                {
+                    list.Add(folder);
+                    continue;
                 }
                 }
-            }
-            else
-            {
-                // TODO: Deprecate this whole block
-                foreach (var folder in standaloneFolders)
+                
+                if (collectionFolder != null && UserView.IsEligibleForGrouping(folder) && user.IsFolderGrouped(folder.Id))
                 {
                 {
-                    var collectionFolder = folder as ICollectionFolder;
-                    var folderViewType = collectionFolder == null ? null : collectionFolder.CollectionType;
-
-                    if (UserView.IsUserSpecific(folder))
-                    {
-                        list.Add(await GetUserView(folder.Id, folder.Name, folderViewType, true, string.Empty, user, cancellationToken).ConfigureAwait(false));
-                    }
-                    else if (plainFolderIds.Contains(folder.Id))
-                    {
-                        list.Add(await GetUserView(folder.Id, folder.Name, folderViewType, false, string.Empty, user, cancellationToken).ConfigureAwait(false));
-                    }
-                    else if (!string.IsNullOrWhiteSpace(folderViewType))
-                    {
-                        list.Add(await GetUserView(folder.Id, folder.Name, folderViewType, true, string.Empty, user, cancellationToken).ConfigureAwait(false));
-                    }
-                    else
-                    {
-                        list.Add(folder);
-                    }
+                    groupedFolders.Add(collectionFolder);
+                    continue;
                 }
                 }
-            }
-
-            var parents = foldersWithViewTypes.Where(i => string.Equals(i.GetViewType(user), CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) || string.IsNullOrWhiteSpace(i.GetViewType(user)))
-                .ToList();
-
-            if (parents.Count > 0)
-            {
-                list.Add(await GetUserView(parents, list, CollectionType.TvShows, string.Empty, user, enableUserViews, cancellationToken).ConfigureAwait(false));
-            }
-
-            parents = foldersWithViewTypes.Where(i => string.Equals(i.GetViewType(user), CollectionType.Music, StringComparison.OrdinalIgnoreCase) || string.IsNullOrWhiteSpace(i.GetViewType(user)))
-                .ToList();
 
 
-            if (parents.Count > 0)
-            {
-                list.Add(await GetUserView(parents, list, CollectionType.Music, string.Empty, user, enableUserViews, cancellationToken).ConfigureAwait(false));
+                if (query.PresetViews.Contains(folderViewType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
+                {
+                    list.Add(await GetUserView(folder, folderViewType, string.Empty, cancellationToken).ConfigureAwait(false));
+                }
+                else
+                {
+                    list.Add(folder);
+                }
             }
             }
 
 
-            parents = foldersWithViewTypes.Where(i => string.Equals(i.GetViewType(user), CollectionType.Movies, StringComparison.OrdinalIgnoreCase) || string.IsNullOrWhiteSpace(i.GetViewType(user)))
-               .ToList();
-
-            if (parents.Count > 0)
+            foreach (var viewType in new[] { CollectionType.Movies, CollectionType.TvShows })
             {
             {
-                list.Add(await GetUserView(parents, list, CollectionType.Movies, string.Empty, user, enableUserViews, cancellationToken).ConfigureAwait(false));
-            }
-
-            parents = foldersWithViewTypes.Where(i => string.Equals(i.GetViewType(user), CollectionType.Games, StringComparison.OrdinalIgnoreCase))
-               .ToList();
+                var parents = groupedFolders.Where(i => string.Equals(i.CollectionType, viewType, StringComparison.OrdinalIgnoreCase) || string.IsNullOrWhiteSpace(i.CollectionType))
+                    .ToList();
 
 
-            if (parents.Count > 0)
-            {
-                list.Add(await GetUserView(parents, list, CollectionType.Games, string.Empty, user, enableUserViews, cancellationToken).ConfigureAwait(false));
+                if (parents.Count > 0)
+                {
+                    list.Add(await GetUserView(parents, viewType, string.Empty, user, query.PresetViews, cancellationToken).ConfigureAwait(false));
+                }
             }
             }
 
 
             if (user.Configuration.DisplayFoldersView)
             if (user.Configuration.DisplayFoldersView)
@@ -189,6 +150,18 @@ namespace MediaBrowser.Server.Implementations.Library
                 {
                 {
                     var index = orders.IndexOf(i.Id.ToString("N"));
                     var index = orders.IndexOf(i.Id.ToString("N"));
 
 
+                    if (index == -1)
+                    {
+                        var view = i as UserView;
+                        if (view != null)
+                        {
+                            if (view.DisplayParentId != Guid.Empty)
+                            {
+                                index = orders.IndexOf(view.DisplayParentId.ToString("N"));
+                            }
+                        }
+                    }
+
                     return index == -1 ? int.MaxValue : index;
                     return index == -1 ? int.MaxValue : index;
                 })
                 })
                 .ThenBy(sorted.IndexOf)
                 .ThenBy(sorted.IndexOf)
@@ -209,32 +182,25 @@ namespace MediaBrowser.Server.Implementations.Library
             return GetUserSubView(name, parentId, type, sortName, cancellationToken);
             return GetUserSubView(name, parentId, type, sortName, cancellationToken);
         }
         }
 
 
-        private async Task<UserView> GetUserView(List<ICollectionFolder> parents, List<Folder> currentViews, string viewType, string sortName, User user, bool enableUserViews, CancellationToken cancellationToken)
+        private async Task<Folder> GetUserView(List<ICollectionFolder> parents, string viewType, string sortName, User user, string[] presetViews, CancellationToken cancellationToken)
         {
         {
-            if (parents.Count == 1 && parents.All(i => string.Equals((enableUserViews ? i.GetViewType(user) : i.CollectionType), viewType, StringComparison.OrdinalIgnoreCase)))
+            if (parents.Count == 1 && parents.All(i => string.Equals(i.CollectionType, viewType, StringComparison.OrdinalIgnoreCase)))
             {
             {
-                var parentId = parents[0].Id;
-
-                var enableRichView = !user.Configuration.PlainFolderViews.Contains(parentId.ToString("N"), StringComparer.OrdinalIgnoreCase);
+                if (!presetViews.Contains(viewType, StringComparer.OrdinalIgnoreCase))
+                {
+                    return (Folder)parents[0];
+                }
 
 
-                return await GetUserView((Folder)parents[0], viewType, enableRichView, string.Empty, cancellationToken).ConfigureAwait(false);
+                return await GetUserView((Folder)parents[0], viewType, string.Empty, cancellationToken).ConfigureAwait(false);
             }
             }
 
 
             var name = _localizationManager.GetLocalizedString("ViewType" + viewType);
             var name = _localizationManager.GetLocalizedString("ViewType" + viewType);
             return await _libraryManager.GetNamedView(user, name, viewType, sortName, cancellationToken).ConfigureAwait(false);
             return await _libraryManager.GetNamedView(user, name, viewType, sortName, cancellationToken).ConfigureAwait(false);
         }
         }
 
 
-        public Task<UserView> GetUserView(Guid parentId, string name, string viewType, bool enableRichView, string sortName, User user, CancellationToken cancellationToken)
-        {
-            viewType = enableRichView ? viewType : null;
-            return _libraryManager.GetNamedView(user, name, parentId.ToString("N"), viewType, sortName, null, cancellationToken);
-        }
-
-        public Task<UserView> GetUserView(Folder parent, string viewType, bool enableRichView, string sortName, CancellationToken cancellationToken)
+        public Task<UserView> GetUserView(Folder parent, string viewType, string sortName, CancellationToken cancellationToken)
         {
         {
-            viewType = enableRichView ? viewType : null;
-
-            return _libraryManager.GetShadowView(parent, viewType, sortName, null, cancellationToken);
+            return _libraryManager.GetShadowView(parent, viewType, sortName, cancellationToken);
         }
         }
 
 
         public List<Tuple<BaseItem, List<BaseItem>>> GetLatestItems(LatestItemsQuery request)
         public List<Tuple<BaseItem, List<BaseItem>>> GetLatestItems(LatestItemsQuery request)
@@ -245,16 +211,8 @@ namespace MediaBrowser.Server.Implementations.Library
 
 
             var currentUser = user;
             var currentUser = user;
 
 
-            Func<BaseItem, bool> filter = i =>
+            var libraryItems = GetItemsForLatestItems(user, request.ParentId, includeTypes, request.Limit ?? 10).Where(i =>
             {
             {
-                if (includeTypes.Length > 0)
-                {
-                    if (!includeTypes.Contains(i.GetType().Name, StringComparer.OrdinalIgnoreCase))
-                    {
-                        return false;
-                    }
-                }
-
                 if (request.IsPlayed.HasValue)
                 if (request.IsPlayed.HasValue)
                 {
                 {
                     var val = request.IsPlayed.Value;
                     var val = request.IsPlayed.Value;
@@ -264,29 +222,12 @@ namespace MediaBrowser.Server.Implementations.Library
                     }
                     }
                 }
                 }
 
 
-                return i.LocationType != LocationType.Virtual && !i.IsFolder;
-            };
-
-            // Avoid implicitly captured closure
-            var libraryItems = string.IsNullOrEmpty(request.ParentId) && user != null ?
-                GetItemsConfiguredForLatest(user, filter) :
-                GetAllLibraryItems(request.UserId, _userManager, _libraryManager, request.ParentId, filter);
-
-            libraryItems = libraryItems.OrderByDescending(i => i.DateCreated);
-
-            if (request.IsPlayed.HasValue)
-            {
-                var takeLimit = (request.Limit ?? 20) * 20;
-                libraryItems = libraryItems.Take(takeLimit);
-            }
-
-            // Avoid implicitly captured closure
-            var items = libraryItems
-                .ToList();
+                return true;
+            });
 
 
             var list = new List<Tuple<BaseItem, List<BaseItem>>>();
             var list = new List<Tuple<BaseItem, List<BaseItem>>>();
 
 
-            foreach (var item in items)
+            foreach (var item in libraryItems)
             {
             {
                 // Only grab the index container for media
                 // Only grab the index container for media
                 var container = item.IsFolder || !request.GroupItems ? null : item.LatestItemsIndexContainer;
                 var container = item.IsFolder || !request.GroupItems ? null : item.LatestItemsIndexContainer;
@@ -318,59 +259,34 @@ namespace MediaBrowser.Server.Implementations.Library
             return list;
             return list;
         }
         }
 
 
-        protected IList<BaseItem> GetAllLibraryItems(string userId, IUserManager userManager, ILibraryManager libraryManager, string parentId, Func<BaseItem, bool> filter)
+        private IEnumerable<BaseItem> GetItemsForLatestItems(User user, string parentId, string[] includeItemTypes, int limit)
         {
         {
-            if (!string.IsNullOrEmpty(parentId))
-            {
-                var folder = (Folder)libraryManager.GetItemById(new Guid(parentId));
-
-                if (!string.IsNullOrWhiteSpace(userId))
-                {
-                    var user = userManager.GetUserById(userId);
-
-                    if (user == null)
-                    {
-                        throw new ArgumentException("User not found");
-                    }
-
-                    return folder
-                        .GetRecursiveChildren(user, filter)
-                        .ToList();
-                }
+            var parentIds = string.IsNullOrEmpty(parentId)
+              ? new string[] { }
+              : new[] { parentId };
 
 
-                return folder
-                    .GetRecursiveChildren(filter);
-            }
-            if (!string.IsNullOrWhiteSpace(userId))
+            if (parentIds.Length == 0)
             {
             {
-                var user = userManager.GetUserById(userId);
-
-                if (user == null)
-                {
-                    throw new ArgumentException("User not found");
-                }
-
-                return user
-                    .RootFolder
-                    .GetRecursiveChildren(user, filter)
-                    .ToList();
+                parentIds = user.RootFolder.GetChildren(user, true)
+                    .OfType<Folder>()
+                    .Select(i => i.Id.ToString("N"))
+                    .Where(i => !user.Configuration.LatestItemsExcludes.Contains(i))
+                    .ToArray();
             }
             }
 
 
-            return libraryManager
-                .RootFolder
-                .GetRecursiveChildren(filter);
-        }
-
-        private IEnumerable<BaseItem> GetItemsConfiguredForLatest(User user, Func<BaseItem, bool> filter)
-        {
-            // Avoid implicitly captured closure
-            var currentUser = user;
+            var excludeItemTypes = includeItemTypes.Length == 0 ? new[] { "ChannelItem", "LiveTvItem", typeof(Person).Name, typeof(Studio).Name, typeof(Year).Name, typeof(GameGenre).Name, typeof(MusicGenre).Name, typeof(Genre).Name } : new string[] { };
 
 
-            return user.RootFolder.GetChildren(user, true)
-                .OfType<Folder>()
-                .Where(i => !user.Configuration.LatestItemsExcludes.Contains(i.Id.ToString("N")))
-                .SelectMany(i => i.GetRecursiveChildren(currentUser, filter))
-                .DistinctBy(i => i.Id);
+            return _libraryManager.GetItems(new InternalItemsQuery(user)
+            {
+                IncludeItemTypes = includeItemTypes,
+                SortOrder = SortOrder.Descending,
+                SortBy = new[] { ItemSortBy.DateCreated },
+                IsFolder = includeItemTypes.Length == 0 ? false : (bool?)null,
+                ExcludeItemTypes = excludeItemTypes,
+                ExcludeLocationTypes = new[] { LocationType.Virtual },
+                Limit = limit * 20
+
+            }, parentIds);
         }
         }
     }
     }
 }
 }

+ 27 - 29
MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs

@@ -560,6 +560,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             }
             }
             item.ExternalId = channelInfo.Id;
             item.ExternalId = channelInfo.Id;
 
 
+            if (!item.ParentId.Equals(parentFolderId))
+            {
+                isNew = true;
+            }
+            item.ParentId = parentFolderId;
+
             item.ChannelType = channelInfo.ChannelType;
             item.ChannelType = channelInfo.ChannelType;
             item.ServiceName = serviceName;
             item.ServiceName = serviceName;
             item.Number = channelInfo.Number;
             item.Number = channelInfo.Number;
@@ -622,6 +628,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 };
                 };
             }
             }
 
 
+            if (!item.ParentId.Equals(channel.Id))
+            {
+                forceUpdate = true;
+            }
+            item.ParentId = channel.Id;
+
             //item.ChannelType = channelType;
             //item.ChannelType = channelType;
             if (!string.Equals(item.ServiceName, serviceName, StringComparison.Ordinal))
             if (!string.Equals(item.ServiceName, serviceName, StringComparison.Ordinal))
             {
             {
@@ -774,6 +786,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             }
             }
             recording.IsSeries = info.IsSeries;
             recording.IsSeries = info.IsSeries;
 
 
+            if (!item.ParentId.Equals(parentFolderId))
+            {
+                dataChanged = true;
+            }
+            item.ParentId = parentFolderId;
+
             if (!item.HasImage(ImageType.Primary))
             if (!item.HasImage(ImageType.Primary))
             {
             {
                 if (!string.IsNullOrWhiteSpace(info.ImagePath))
                 if (!string.IsNullOrWhiteSpace(info.ImagePath))
@@ -851,7 +869,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
         {
         {
             var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId);
             var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId);
 
 
-            var internalQuery = new InternalItemsQuery
+            var internalQuery = new InternalItemsQuery(user)
             {
             {
                 IncludeItemTypes = new[] { typeof(LiveTvProgram).Name },
                 IncludeItemTypes = new[] { typeof(LiveTvProgram).Name },
                 MinEndDate = query.MinEndDate,
                 MinEndDate = query.MinEndDate,
@@ -869,16 +887,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 SortOrder = query.SortOrder ?? SortOrder.Ascending
                 SortOrder = query.SortOrder ?? SortOrder.Ascending
             };
             };
 
 
-            if (user != null)
-            {
-                internalQuery.MaxParentalRating = user.Policy.MaxParentalRating;
-
-                if (user.Policy.BlockUnratedItems.Contains(UnratedItem.LiveTvProgram))
-                {
-                    internalQuery.HasParentalRating = true;
-                }
-            }
-
             if (query.HasAired.HasValue)
             if (query.HasAired.HasValue)
             {
             {
                 if (query.HasAired.Value)
                 if (query.HasAired.Value)
@@ -913,7 +921,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
         {
         {
             var user = _userManager.GetUserById(query.UserId);
             var user = _userManager.GetUserById(query.UserId);
 
 
-            var internalQuery = new InternalItemsQuery
+            var internalQuery = new InternalItemsQuery(user)
             {
             {
                 IncludeItemTypes = new[] { typeof(LiveTvProgram).Name },
                 IncludeItemTypes = new[] { typeof(LiveTvProgram).Name },
                 IsAiring = query.IsAiring,
                 IsAiring = query.IsAiring,
@@ -922,16 +930,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 IsKids = query.IsKids
                 IsKids = query.IsKids
             };
             };
 
 
-            if (user != null)
-            {
-                internalQuery.MaxParentalRating = user.Policy.MaxParentalRating;
-
-                if (user.Policy.BlockUnratedItems.Contains(UnratedItem.LiveTvProgram))
-                {
-                    internalQuery.HasParentalRating = true;
-                }
-            }
-
             if (query.HasAired.HasValue)
             if (query.HasAired.HasValue)
             {
             {
                 if (query.HasAired.Value)
                 if (query.HasAired.Value)
@@ -1399,7 +1397,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
 
             await RefreshRecordings(cancellationToken).ConfigureAwait(false);
             await RefreshRecordings(cancellationToken).ConfigureAwait(false);
 
 
-            var internalQuery = new InternalItemsQuery
+            var internalQuery = new InternalItemsQuery(user)
             {
             {
                 IncludeItemTypes = new[] { typeof(LiveTvVideoRecording).Name, typeof(LiveTvAudioRecording).Name }
                 IncludeItemTypes = new[] { typeof(LiveTvVideoRecording).Name, typeof(LiveTvAudioRecording).Name }
             };
             };
@@ -1409,8 +1407,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 internalQuery.ChannelIds = new[] { query.ChannelId };
                 internalQuery.ChannelIds = new[] { query.ChannelId };
             }
             }
 
 
-            var queryResult = _libraryManager.GetItems(internalQuery);
-            IEnumerable<ILiveTvRecording> recordings = queryResult.Items.Cast<ILiveTvRecording>();
+            var queryResult = _libraryManager.GetItems(internalQuery, new string[] { });
+            IEnumerable<ILiveTvRecording> recordings = queryResult.Cast<ILiveTvRecording>();
 
 
             if (!string.IsNullOrEmpty(query.Id))
             if (!string.IsNullOrEmpty(query.Id))
             {
             {
@@ -1812,7 +1810,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
 
             var now = DateTime.UtcNow;
             var now = DateTime.UtcNow;
 
 
-            var programs = _libraryManager.GetItems(new InternalItemsQuery
+            var programs = _libraryManager.GetItems(new InternalItemsQuery(user)
             {
             {
                 IncludeItemTypes = new[] { typeof(LiveTvProgram).Name },
                 IncludeItemTypes = new[] { typeof(LiveTvProgram).Name },
                 ChannelIds = new[] { id },
                 ChannelIds = new[] { id },
@@ -1821,7 +1819,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 Limit = 1,
                 Limit = 1,
                 SortBy = new[] { "StartDate" }
                 SortBy = new[] { "StartDate" }
 
 
-            }).Items.Cast<LiveTvProgram>();
+            }, new string[] { }).Cast<LiveTvProgram>();
 
 
             var currentProgram = programs.FirstOrDefault();
             var currentProgram = programs.FirstOrDefault();
 
 
@@ -1836,7 +1834,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
 
             var now = DateTime.UtcNow;
             var now = DateTime.UtcNow;
 
 
-            var programs = _libraryManager.GetItems(new InternalItemsQuery
+            var programs = _libraryManager.GetItems(new InternalItemsQuery(user)
             {
             {
                 IncludeItemTypes = new[] { typeof(LiveTvProgram).Name },
                 IncludeItemTypes = new[] { typeof(LiveTvProgram).Name },
                 ChannelIds = new[] { channel.Id.ToString("N") },
                 ChannelIds = new[] { channel.Id.ToString("N") },
@@ -1845,7 +1843,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 Limit = 1,
                 Limit = 1,
                 SortBy = new[] { "StartDate" }
                 SortBy = new[] { "StartDate" }
 
 
-            }).Items.Cast<LiveTvProgram>();
+            }, new string[] { }).Cast<LiveTvProgram>();
 
 
             var currentProgram = programs.FirstOrDefault();
             var currentProgram = programs.FirstOrDefault();
 
 

+ 1 - 2
MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs

@@ -124,8 +124,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
                 {
                 {
                     if (extractImages)
                     if (extractImages)
                     {
                     {
-                        if (video.VideoType == VideoType.HdDvd || video.VideoType == VideoType.Iso ||
-                             video.VideoType == VideoType.BluRay)
+                        if (video.VideoType == VideoType.HdDvd || video.VideoType == VideoType.Iso || video.VideoType == VideoType.BluRay)
                         {
                         {
                             continue;
                             continue;
                         }
                         }

+ 26 - 9
MediaBrowser.Server.Implementations/Persistence/CleanDatabaseScheduledTask.cs

@@ -12,6 +12,7 @@ using System.Collections.Generic;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using CommonIO;
 using CommonIO;
+using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Audio;
 
 
 namespace MediaBrowser.Server.Implementations.Persistence
 namespace MediaBrowser.Server.Implementations.Persistence
@@ -24,6 +25,8 @@ namespace MediaBrowser.Server.Implementations.Persistence
         private readonly IServerConfigurationManager _config;
         private readonly IServerConfigurationManager _config;
         private readonly IFileSystem _fileSystem;
         private readonly IFileSystem _fileSystem;
 
 
+        public const int MigrationVersion = 7;
+
         public CleanDatabaseScheduledTask(ILibraryManager libraryManager, IItemRepository itemRepo, ILogger logger, IServerConfigurationManager config, IFileSystem fileSystem)
         public CleanDatabaseScheduledTask(ILibraryManager libraryManager, IItemRepository itemRepo, ILogger logger, IServerConfigurationManager config, IFileSystem fileSystem)
         {
         {
             _libraryManager = libraryManager;
             _libraryManager = libraryManager;
@@ -64,16 +67,15 @@ namespace MediaBrowser.Server.Implementations.Persistence
             innerProgress.RegisterAction(p => progress.Report(45 + (.55 * p)));
             innerProgress.RegisterAction(p => progress.Report(45 + (.55 * p)));
             await CleanDeletedItems(cancellationToken, innerProgress).ConfigureAwait(false);
             await CleanDeletedItems(cancellationToken, innerProgress).ConfigureAwait(false);
             progress.Report(100);
             progress.Report(100);
+
+            await _itemRepo.UpdateInheritedValues(cancellationToken).ConfigureAwait(false);
         }
         }
 
 
         private async Task UpdateToLatestSchema(CancellationToken cancellationToken, IProgress<double> progress)
         private async Task UpdateToLatestSchema(CancellationToken cancellationToken, IProgress<double> progress)
         {
         {
             var itemIds = _libraryManager.GetItemIds(new InternalItemsQuery
             var itemIds = _libraryManager.GetItemIds(new InternalItemsQuery
             {
             {
-                IsCurrentSchema = false,
-
-                // These are constantly getting regenerated so don't bother with them here
-                ExcludeItemTypes = new[] { typeof(LiveTvProgram).Name }
+                IsCurrentSchema = false
             });
             });
 
 
             var numComplete = 0;
             var numComplete = 0;
@@ -115,9 +117,9 @@ namespace MediaBrowser.Server.Implementations.Persistence
                 progress.Report(percent * 100);
                 progress.Report(percent * 100);
             }
             }
 
 
-            if (!_config.Configuration.DisableStartupScan)
+            if (_config.Configuration.MigrationVersion < MigrationVersion)
             {
             {
-                _config.Configuration.DisableStartupScan = true;
+                _config.Configuration.MigrationVersion = MigrationVersion;
                 _config.SaveConfiguration();
                 _config.SaveConfiguration();
             }
             }
 
 
@@ -165,12 +167,22 @@ namespace MediaBrowser.Server.Implementations.Persistence
         {
         {
             var result = _itemRepo.GetItemIdsWithPath(new InternalItemsQuery
             var result = _itemRepo.GetItemIdsWithPath(new InternalItemsQuery
             {
             {
-                IsOffline = false,
                 LocationType = LocationType.FileSystem,
                 LocationType = LocationType.FileSystem,
                 //Limit = limit,
                 //Limit = limit,
 
 
                 // These have their own cleanup routines
                 // These have their own cleanup routines
-                ExcludeItemTypes = new[] { typeof(Person).Name, typeof(Genre).Name, typeof(MusicGenre).Name, typeof(GameGenre).Name, typeof(Studio).Name, typeof(Year).Name }
+                ExcludeItemTypes = new[]
+                {
+                    typeof(Person).Name, 
+                    typeof(Genre).Name, 
+                    typeof(MusicGenre).Name, 
+                    typeof(GameGenre).Name, 
+                    typeof(Studio).Name, 
+                    typeof(Year).Name, 
+                    typeof(Channel).Name, 
+                    typeof(AggregateFolder).Name, 
+                    typeof(CollectionFolder).Name
+                }
             });
             });
 
 
             var numComplete = 0;
             var numComplete = 0;
@@ -191,6 +203,11 @@ namespace MediaBrowser.Server.Implementations.Persistence
 
 
                     var libraryItem = _libraryManager.GetItemById(item.Item1);
                     var libraryItem = _libraryManager.GetItemById(item.Item1);
 
 
+                    if (libraryItem.IsTopParent)
+                    {
+                        continue;
+                    }
+
                     if (Folder.IsPathOffline(path))
                     if (Folder.IsPathOffline(path))
                     {
                     {
                         libraryItem.IsOffline = true;
                         libraryItem.IsOffline = true;
@@ -229,4 +246,4 @@ namespace MediaBrowser.Server.Implementations.Persistence
             };
             };
         }
         }
     }
     }
-}
+}

+ 32 - 0
MediaBrowser.Server.Implementations/Persistence/MediaStreamColumns.cs

@@ -27,6 +27,38 @@ namespace MediaBrowser.Server.Implementations.Persistence
             AddIsCabacColumn();
             AddIsCabacColumn();
             AddKeyFramesColumn();
             AddKeyFramesColumn();
             AddRefFramesCommand();
             AddRefFramesCommand();
+            AddCodecTagColumn();
+        }
+
+        private void AddCodecTagColumn()
+        {
+            using (var cmd = _connection.CreateCommand())
+            {
+                cmd.CommandText = "PRAGMA table_info(mediastreams)";
+
+                using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
+                {
+                    while (reader.Read())
+                    {
+                        if (!reader.IsDBNull(1))
+                        {
+                            var name = reader.GetString(1);
+
+                            if (string.Equals(name, "CodecTag", StringComparison.OrdinalIgnoreCase))
+                            {
+                                return;
+                            }
+                        }
+                    }
+                }
+            }
+
+            var builder = new StringBuilder();
+
+            builder.AppendLine("alter table mediastreams");
+            builder.AppendLine("add column CodecTag TEXT");
+
+            _connection.RunQueries(new[] { builder.ToString() }, _logger);
         }
         }
 
 
         private void AddPixelFormatColumnCommand()
         private void AddPixelFormatColumnCommand()

+ 481 - 22
MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs

@@ -19,6 +19,7 @@ using System.Runtime.Serialization;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Channels;
+using MediaBrowser.Model.LiveTv;
 
 
 namespace MediaBrowser.Server.Implementations.Persistence
 namespace MediaBrowser.Server.Implementations.Persistence
 {
 {
@@ -76,7 +77,12 @@ namespace MediaBrowser.Server.Implementations.Persistence
         private IDbCommand _deleteStreamsCommand;
         private IDbCommand _deleteStreamsCommand;
         private IDbCommand _saveStreamCommand;
         private IDbCommand _saveStreamCommand;
 
 
-        private const int LatestSchemaVersion = 13;
+        private IDbCommand _deleteAncestorsCommand;
+        private IDbCommand _saveAncestorCommand;
+
+        private IDbCommand _updateInheritedRatingCommand;
+        
+        private const int LatestSchemaVersion = 40;
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="SqliteItemRepository"/> class.
         /// Initializes a new instance of the <see cref="SqliteItemRepository"/> class.
@@ -121,17 +127,24 @@ namespace MediaBrowser.Server.Implementations.Persistence
             _connection = await SqliteExtensions.ConnectToDb(dbFile, _logger).ConfigureAwait(false);
             _connection = await SqliteExtensions.ConnectToDb(dbFile, _logger).ConfigureAwait(false);
 
 
             var createMediaStreamsTableCommand
             var createMediaStreamsTableCommand
-               = "create table if not exists mediastreams (ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, BitDepth INT NULL, IsAnamorphic BIT NULL, RefFrames INT NULL, IsCabac BIT NULL, KeyFrames TEXT NULL, PRIMARY KEY (ItemId, StreamIndex))";
+               = "create table if not exists mediastreams (ItemId GUID, StreamIndex INT, StreamType TEXT, Codec TEXT, Language TEXT, ChannelLayout TEXT, Profile TEXT, AspectRatio TEXT, Path TEXT, IsInterlaced BIT, BitRate INT NULL, Channels INT NULL, SampleRate INT NULL, IsDefault BIT, IsForced BIT, IsExternal BIT, Height INT NULL, Width INT NULL, AverageFrameRate FLOAT NULL, RealFrameRate FLOAT NULL, Level FLOAT NULL, PixelFormat TEXT, BitDepth INT NULL, IsAnamorphic BIT NULL, RefFrames INT NULL, IsCabac BIT NULL, CodecTag TEXT NULL, PRIMARY KEY (ItemId, StreamIndex))";
 
 
             string[] queries = {
             string[] queries = {
 
 
-                                "create table if not exists TypedBaseItems (guid GUID primary key, type TEXT, data BLOB)",
+                                "create table if not exists TypedBaseItems (guid GUID primary key, type TEXT, data BLOB, ParentId GUID)",
                                 "create index if not exists idx_TypedBaseItems on TypedBaseItems(guid)",
                                 "create index if not exists idx_TypedBaseItems on TypedBaseItems(guid)",
+                                "create index if not exists idx_ParentIdTypedBaseItems on TypedBaseItems(ParentId)",
 
 
+                                "create table if not exists AncestorIds (ItemId GUID, AncestorId GUID, AncestorIdText TEXT, PRIMARY KEY (ItemId, AncestorId))",
+                                "create index if not exists idx_AncestorIds1 on AncestorIds(AncestorId)",
+                                "create index if not exists idx_AncestorIds2 on AncestorIds(AncestorIdText)",
+                                
                                 "create table if not exists ChildrenIds (ParentId GUID, ItemId GUID, PRIMARY KEY (ParentId, ItemId))",
                                 "create table if not exists ChildrenIds (ParentId GUID, ItemId GUID, PRIMARY KEY (ParentId, ItemId))",
                                 "create index if not exists idx_ChildrenIds on ChildrenIds(ParentId,ItemId)",
                                 "create index if not exists idx_ChildrenIds on ChildrenIds(ParentId,ItemId)",
 
 
                                 "create table if not exists People (ItemId GUID, Name TEXT NOT NULL, Role TEXT, PersonType TEXT, SortOrder int, ListOrder int)",
                                 "create table if not exists People (ItemId GUID, Name TEXT NOT NULL, Role TEXT, PersonType TEXT, SortOrder int, ListOrder int)",
+                                "create index if not exists idxPeopleItemId on People(ItemId)",
+                                "create index if not exists idxPeopleName on People(Name)",
 
 
                                 "create table if not exists "+ChaptersTableName+" (ItemId GUID, ChapterIndex INT, StartPositionTicks BIGINT, Name TEXT, ImagePath TEXT, PRIMARY KEY (ItemId, ChapterIndex))",
                                 "create table if not exists "+ChaptersTableName+" (ItemId GUID, ChapterIndex INT, StartPositionTicks BIGINT, Name TEXT, ImagePath TEXT, PRIMARY KEY (ItemId, ChapterIndex))",
                                 "create index if not exists idx_"+ChaptersTableName+" on "+ChaptersTableName+"(ItemId, ChapterIndex)",
                                 "create index if not exists idx_"+ChaptersTableName+" on "+ChaptersTableName+"(ItemId, ChapterIndex)",
@@ -147,6 +160,8 @@ namespace MediaBrowser.Server.Implementations.Persistence
 
 
             _connection.RunQueries(queries, _logger);
             _connection.RunQueries(queries, _logger);
 
 
+            _connection.AddColumn(_logger, "AncestorIds", "AncestorIdText", "Text");
+            
             _connection.AddColumn(_logger, "TypedBaseItems", "Path", "Text");
             _connection.AddColumn(_logger, "TypedBaseItems", "Path", "Text");
             _connection.AddColumn(_logger, "TypedBaseItems", "StartDate", "DATETIME");
             _connection.AddColumn(_logger, "TypedBaseItems", "StartDate", "DATETIME");
             _connection.AddColumn(_logger, "TypedBaseItems", "EndDate", "DATETIME");
             _connection.AddColumn(_logger, "TypedBaseItems", "EndDate", "DATETIME");
@@ -198,6 +213,18 @@ namespace MediaBrowser.Server.Implementations.Persistence
             _connection.AddColumn(_logger, "TypedBaseItems", "ExternalEtag", "Text");
             _connection.AddColumn(_logger, "TypedBaseItems", "ExternalEtag", "Text");
             _connection.AddColumn(_logger, "TypedBaseItems", "DateLastRefreshed", "DATETIME");
             _connection.AddColumn(_logger, "TypedBaseItems", "DateLastRefreshed", "DATETIME");
 
 
+            _connection.AddColumn(_logger, "TypedBaseItems", "DateLastSaved", "DATETIME");
+            _connection.AddColumn(_logger, "TypedBaseItems", "IsInMixedFolder", "BIT");
+            _connection.AddColumn(_logger, "TypedBaseItems", "LockedFields", "Text");
+            _connection.AddColumn(_logger, "TypedBaseItems", "Studios", "Text");
+            _connection.AddColumn(_logger, "TypedBaseItems", "Audio", "Text");
+            _connection.AddColumn(_logger, "TypedBaseItems", "ExternalServiceId", "Text");
+            _connection.AddColumn(_logger, "TypedBaseItems", "Tags", "Text");
+            _connection.AddColumn(_logger, "TypedBaseItems", "IsFolder", "BIT");
+            _connection.AddColumn(_logger, "TypedBaseItems", "InheritedParentalRatingValue", "INT");
+            _connection.AddColumn(_logger, "TypedBaseItems", "UnratedType", "Text");
+            _connection.AddColumn(_logger, "TypedBaseItems", "TopParentId", "Text");
+
             PrepareStatements();
             PrepareStatements();
 
 
             new MediaStreamColumns(_connection, _logger).AddColumns();
             new MediaStreamColumns(_connection, _logger).AddColumns();
@@ -307,7 +334,27 @@ namespace MediaBrowser.Server.Implementations.Persistence
             "PreferredMetadataCountryCode",
             "PreferredMetadataCountryCode",
             "IsHD",
             "IsHD",
             "ExternalEtag",
             "ExternalEtag",
-            "DateLastRefreshed"
+            "DateLastRefreshed",
+            "Name",
+            "Path",
+            "PremiereDate",
+            "Overview",
+            "ParentIndexNumber",
+            "ProductionYear",
+            "OfficialRating",
+            "OfficialRatingDescription",
+            "HomePageUrl",
+            "DisplayMediaType",
+            "ForcedSortName",
+            "RunTimeTicks",
+            "VoteCount",
+            "DateCreated",
+            "DateModified",
+            "guid",
+            "Genres",
+            "ParentId",
+            "Audio",
+            "ExternalServiceId"
         };
         };
 
 
         private readonly string[] _mediaStreamSaveColumns =
         private readonly string[] _mediaStreamSaveColumns =
@@ -338,7 +385,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
             "IsAnamorphic",
             "IsAnamorphic",
             "RefFrames",
             "RefFrames",
             "IsCabac",
             "IsCabac",
-            "KeyFrames"
+            "CodecTag"
         };
         };
 
 
         /// <summary>
         /// <summary>
@@ -378,6 +425,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
                 "ParentId",
                 "ParentId",
                 "Genres",
                 "Genres",
                 "ParentalRatingValue",
                 "ParentalRatingValue",
+                "InheritedParentalRatingValue",
                 "SchemaVersion",
                 "SchemaVersion",
                 "SortName",
                 "SortName",
                 "RunTimeTicks",
                 "RunTimeTicks",
@@ -394,7 +442,17 @@ namespace MediaBrowser.Server.Implementations.Persistence
                 "PreferredMetadataCountryCode",
                 "PreferredMetadataCountryCode",
                 "IsHD",
                 "IsHD",
                 "ExternalEtag",
                 "ExternalEtag",
-                "DateLastRefreshed"
+                "DateLastRefreshed",
+                "DateLastSaved",
+                "IsInMixedFolder",
+                "LockedFields",
+                "Studios",
+                "Audio",
+                "ExternalServiceId",
+                "Tags",
+                "IsFolder",
+                "UnratedType",
+                "TopParentId"
             };
             };
             _saveItemCommand = _connection.CreateCommand();
             _saveItemCommand = _connection.CreateCommand();
             _saveItemCommand.CommandText = "replace into TypedBaseItems (" + string.Join(",", saveColumns.ToArray()) + ") values (";
             _saveItemCommand.CommandText = "replace into TypedBaseItems (" + string.Join(",", saveColumns.ToArray()) + ") values (";
@@ -438,6 +496,17 @@ namespace MediaBrowser.Server.Implementations.Persistence
             _savePersonCommand.Parameters.Add(_savePersonCommand, "@SortOrder");
             _savePersonCommand.Parameters.Add(_savePersonCommand, "@SortOrder");
             _savePersonCommand.Parameters.Add(_savePersonCommand, "@ListOrder");
             _savePersonCommand.Parameters.Add(_savePersonCommand, "@ListOrder");
 
 
+            // Ancestors
+            _deleteAncestorsCommand = _connection.CreateCommand();
+            _deleteAncestorsCommand.CommandText = "delete from AncestorIds where ItemId=@Id";
+            _deleteAncestorsCommand.Parameters.Add(_deleteAncestorsCommand, "@Id");
+
+            _saveAncestorCommand = _connection.CreateCommand();
+            _saveAncestorCommand.CommandText = "insert into AncestorIds (ItemId, AncestorId, AncestorIdText) values (@ItemId, @AncestorId, @AncestorIdText)";
+            _saveAncestorCommand.Parameters.Add(_saveAncestorCommand, "@ItemId");
+            _saveAncestorCommand.Parameters.Add(_saveAncestorCommand, "@AncestorId");
+            _saveAncestorCommand.Parameters.Add(_saveAncestorCommand, "@AncestorIdText");
+
             // Chapters
             // Chapters
             _deleteChaptersCommand = _connection.CreateCommand();
             _deleteChaptersCommand = _connection.CreateCommand();
             _deleteChaptersCommand.CommandText = "delete from " + ChaptersTableName + " where ItemId=@ItemId";
             _deleteChaptersCommand.CommandText = "delete from " + ChaptersTableName + " where ItemId=@ItemId";
@@ -467,6 +536,11 @@ namespace MediaBrowser.Server.Implementations.Persistence
             {
             {
                 _saveStreamCommand.Parameters.Add(_saveStreamCommand, "@" + col);
                 _saveStreamCommand.Parameters.Add(_saveStreamCommand, "@" + col);
             }
             }
+
+            _updateInheritedRatingCommand = _connection.CreateCommand();
+            _updateInheritedRatingCommand.CommandText = "Update TypedBaseItems set InheritedParentalRatingValue=@InheritedParentalRatingValue where Guid=@Guid";
+            _updateInheritedRatingCommand.Parameters.Add(_updateInheritedRatingCommand, "@InheritedParentalRatingValue");
+            _updateInheritedRatingCommand.Parameters.Add(_updateInheritedRatingCommand, "@Guid");
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -592,7 +666,8 @@ namespace MediaBrowser.Server.Implementations.Persistence
                     }
                     }
 
 
                     _saveItemCommand.GetParameter(index++).Value = string.Join("|", item.Genres.ToArray());
                     _saveItemCommand.GetParameter(index++).Value = string.Join("|", item.Genres.ToArray());
-                    _saveItemCommand.GetParameter(index++).Value = item.GetParentalRatingValue();
+                    _saveItemCommand.GetParameter(index++).Value = item.GetParentalRatingValue() ?? 0;
+                    _saveItemCommand.GetParameter(index++).Value = item.GetInheritedParentalRatingValue() ?? 0;
 
 
                     _saveItemCommand.GetParameter(index++).Value = LatestSchemaVersion;
                     _saveItemCommand.GetParameter(index++).Value = LatestSchemaVersion;
                     _saveItemCommand.GetParameter(index++).Value = item.SortName;
                     _saveItemCommand.GetParameter(index++).Value = item.SortName;
@@ -623,9 +698,53 @@ namespace MediaBrowser.Server.Implementations.Persistence
                         _saveItemCommand.GetParameter(index++).Value = item.DateLastRefreshed;
                         _saveItemCommand.GetParameter(index++).Value = item.DateLastRefreshed;
                     }
                     }
 
 
+                    _saveItemCommand.GetParameter(index++).Value = item.DateLastSaved;
+                    _saveItemCommand.GetParameter(index++).Value = item.IsInMixedFolder;
+                    _saveItemCommand.GetParameter(index++).Value = string.Join("|", item.LockedFields.Select(i => i.ToString()).ToArray());
+                    _saveItemCommand.GetParameter(index++).Value = string.Join("|", item.Studios.ToArray());
+
+                    if (item.Audio.HasValue)
+                    {
+                        _saveItemCommand.GetParameter(index++).Value = item.Audio.Value.ToString();
+                    }
+                    else
+                    {
+                        _saveItemCommand.GetParameter(index++).Value = null;
+                    }
+
+                    var tvItem = item as ILiveTvItem;
+                    if (tvItem != null)
+                    {
+                        _saveItemCommand.GetParameter(index++).Value = tvItem.ServiceName;
+                    }
+                    else
+                    {
+                        _saveItemCommand.GetParameter(index++).Value = null;
+                    }
+
+                    _saveItemCommand.GetParameter(index++).Value = string.Join("|", item.Tags.ToArray());
+                    _saveItemCommand.GetParameter(index++).Value = item.IsFolder;
+
+                    _saveItemCommand.GetParameter(index++).Value = item.GetBlockUnratedType().ToString();
+
+                    var topParent = item.GetTopParent();
+                    if (topParent != null)
+                    {
+                        _saveItemCommand.GetParameter(index++).Value = topParent.Id.ToString("N");
+                    }
+                    else
+                    {
+                        _saveItemCommand.GetParameter(index++).Value = null;
+                    }
+
                     _saveItemCommand.Transaction = transaction;
                     _saveItemCommand.Transaction = transaction;
 
 
                     _saveItemCommand.ExecuteNonQuery();
                     _saveItemCommand.ExecuteNonQuery();
+
+                    if (item.SupportsAncestors)
+                    {
+                        UpdateAncestors(item.Id, item.GetAncestorIds().Distinct().ToList(), transaction);
+                    }
                 }
                 }
 
 
                 transaction.Commit();
                 transaction.Commit();
@@ -706,22 +825,32 @@ namespace MediaBrowser.Server.Implementations.Persistence
                 return null;
                 return null;
             }
             }
 
 
-            BaseItem item;
+            BaseItem item = null;
 
 
             using (var stream = reader.GetMemoryStream(1))
             using (var stream = reader.GetMemoryStream(1))
             {
             {
                 try
                 try
                 {
                 {
                     item = _jsonSerializer.DeserializeFromStream(stream, type) as BaseItem;
                     item = _jsonSerializer.DeserializeFromStream(stream, type) as BaseItem;
+                }
+                catch (SerializationException ex)
+                {
+                    _logger.ErrorException("Error deserializing item", ex);
+                }
 
 
-                    if (item == null)
+                if (item == null)
+                {
+                    try
+                    {
+                        item = Activator.CreateInstance(type) as BaseItem;
+                    }
+                    catch
                     {
                     {
-                        return null;
                     }
                     }
                 }
                 }
-                catch (SerializationException ex)
+
+                if (item == null)
                 {
                 {
-                    _logger.ErrorException("Error deserializing item", ex);
                     return null;
                     return null;
                 }
                 }
             }
             }
@@ -844,6 +973,107 @@ namespace MediaBrowser.Server.Implementations.Persistence
                 item.DateLastRefreshed = reader.GetDateTime(23).ToUniversalTime();
                 item.DateLastRefreshed = reader.GetDateTime(23).ToUniversalTime();
             }
             }
 
 
+            if (!reader.IsDBNull(24))
+            {
+                item.Name = reader.GetString(24);
+            }
+
+            if (!reader.IsDBNull(25))
+            {
+                item.Path = reader.GetString(25);
+            }
+
+            if (!reader.IsDBNull(26))
+            {
+                item.PremiereDate = reader.GetDateTime(26).ToUniversalTime();
+            }
+
+            if (!reader.IsDBNull(27))
+            {
+                item.Overview = reader.GetString(27);
+            }
+
+            if (!reader.IsDBNull(28))
+            {
+                item.ParentIndexNumber = reader.GetInt32(28);
+            }
+
+            if (!reader.IsDBNull(29))
+            {
+                item.ProductionYear = reader.GetInt32(29);
+            }
+
+            if (!reader.IsDBNull(30))
+            {
+                item.OfficialRating = reader.GetString(30);
+            }
+
+            if (!reader.IsDBNull(31))
+            {
+                item.OfficialRating = reader.GetString(31);
+            }
+
+            if (!reader.IsDBNull(32))
+            {
+                item.HomePageUrl = reader.GetString(32);
+            }
+
+            if (!reader.IsDBNull(33))
+            {
+                item.DisplayMediaType = reader.GetString(33);
+            }
+
+            if (!reader.IsDBNull(34))
+            {
+                item.ForcedSortName = reader.GetString(34);
+            }
+
+            if (!reader.IsDBNull(35))
+            {
+                item.RunTimeTicks = reader.GetInt64(35);
+            }
+
+            if (!reader.IsDBNull(36))
+            {
+                item.VoteCount = reader.GetInt32(36);
+            }
+
+            if (!reader.IsDBNull(37))
+            {
+                item.DateCreated = reader.GetDateTime(37).ToUniversalTime();
+            }
+
+            if (!reader.IsDBNull(38))
+            {
+                item.DateModified = reader.GetDateTime(38).ToUniversalTime();
+            }
+
+            item.Id = reader.GetGuid(39);
+
+            if (!reader.IsDBNull(40))
+            {
+                item.Genres = reader.GetString(40).Split('|').Where(i => !string.IsNullOrWhiteSpace(i)).ToList();
+            }
+
+            if (!reader.IsDBNull(41))
+            {
+                item.ParentId = reader.GetGuid(41);
+            }
+
+            if (!reader.IsDBNull(42))
+            {
+                item.Audio = (ProgramAudio)Enum.Parse(typeof(ProgramAudio), reader.GetString(42), true);
+            }
+
+            if (!reader.IsDBNull(43))
+            {
+                var tvItem = item as ILiveTvItem;
+                if (tvItem != null)
+                {
+                    tvItem.ServiceName = reader.GetString(43);
+                }
+            }
+
             return item;
             return item;
         }
         }
 
 
@@ -1168,6 +1398,8 @@ namespace MediaBrowser.Server.Implementations.Persistence
 
 
                 cmd.Parameters.Add(cmd, "@ParentId", DbType.Guid).Value = parentId;
                 cmd.Parameters.Add(cmd, "@ParentId", DbType.Guid).Value = parentId;
 
 
+                //_logger.Debug(cmd.CommandText);
+
                 using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
                 using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
                 {
                 {
                     while (reader.Read())
                     while (reader.Read())
@@ -1213,6 +1445,50 @@ namespace MediaBrowser.Server.Implementations.Persistence
             }
             }
         }
         }
 
 
+        public IEnumerable<BaseItem> GetItemList(InternalItemsQuery query)
+        {
+            if (query == null)
+            {
+                throw new ArgumentNullException("query");
+            }
+
+            CheckDisposed();
+
+            using (var cmd = _connection.CreateCommand())
+            {
+                cmd.CommandText = "select " + string.Join(",", _retriveItemColumns) + " from TypedBaseItems";
+
+                var whereClauses = GetWhereClauses(query, cmd, true);
+
+                var whereText = whereClauses.Count == 0 ?
+                    string.Empty :
+                    " where " + string.Join(" AND ", whereClauses.ToArray());
+
+                cmd.CommandText += whereText;
+
+                cmd.CommandText += GetOrderByText(query);
+
+                if (query.Limit.HasValue)
+                {
+                    cmd.CommandText += " LIMIT " + query.Limit.Value.ToString(CultureInfo.InvariantCulture);
+                }
+
+                //_logger.Debug(cmd.CommandText);
+
+                using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
+                {
+                    while (reader.Read())
+                    {
+                        var item = GetItem(reader);
+                        if (item != null)
+                        {
+                            yield return item;
+                        }
+                    }
+                }
+            }
+        }
+
         public QueryResult<BaseItem> GetItems(InternalItemsQuery query)
         public QueryResult<BaseItem> GetItems(InternalItemsQuery query)
         {
         {
             if (query == null)
             if (query == null)
@@ -1293,6 +1569,12 @@ namespace MediaBrowser.Server.Implementations.Persistence
 
 
         private string MapOrderByField(string name)
         private string MapOrderByField(string name)
         {
         {
+            if (string.Equals(name, "airtime", StringComparison.OrdinalIgnoreCase))
+            {
+                // TODO
+                return "SortName";
+            }
+
             return name;
             return name;
         }
         }
 
 
@@ -1328,7 +1610,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
 
 
                 _logger.Debug(cmd.CommandText);
                 _logger.Debug(cmd.CommandText);
 
 
-                using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
+                using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
                 {
                 {
                     while (reader.Read())
                     while (reader.Read())
                     {
                     {
@@ -1512,6 +1794,11 @@ namespace MediaBrowser.Server.Implementations.Persistence
                 whereClauses.Add("IsSports=@IsSports");
                 whereClauses.Add("IsSports=@IsSports");
                 cmd.Parameters.Add(cmd, "@IsSports", DbType.Boolean).Value = query.IsSports;
                 cmd.Parameters.Add(cmd, "@IsSports", DbType.Boolean).Value = query.IsSports;
             }
             }
+            if (query.IsFolder.HasValue)
+            {
+                whereClauses.Add("IsFolder=@IsFolder");
+                cmd.Parameters.Add(cmd, "@IsFolder", DbType.Boolean).Value = query.IsFolder;
+            }
 
 
             var includeTypes = query.IncludeItemTypes.SelectMany(MapIncludeItemTypes).ToArray();
             var includeTypes = query.IncludeItemTypes.SelectMany(MapIncludeItemTypes).ToArray();
             if (includeTypes.Length == 1)
             if (includeTypes.Length == 1)
@@ -1572,6 +1859,12 @@ namespace MediaBrowser.Server.Implementations.Persistence
                 cmd.Parameters.Add(cmd, "@MinStartDate", DbType.Date).Value = query.MinStartDate.Value;
                 cmd.Parameters.Add(cmd, "@MinStartDate", DbType.Date).Value = query.MinStartDate.Value;
             }
             }
 
 
+            if (query.MinPremiereDate.HasValue)
+            {
+                whereClauses.Add("PremiereDate>=@MinPremiereDate");
+                cmd.Parameters.Add(cmd, "@MinPremiereDate", DbType.Date).Value = query.MinPremiereDate.Value;
+            }
+
             if (query.MaxStartDate.HasValue)
             if (query.MaxStartDate.HasValue)
             {
             {
                 whereClauses.Add("StartDate<=@MaxStartDate");
                 whereClauses.Add("StartDate<=@MaxStartDate");
@@ -1623,7 +1916,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
 
 
             if (query.MaxParentalRating.HasValue)
             if (query.MaxParentalRating.HasValue)
             {
             {
-                whereClauses.Add("(ParentalRatingValue is NULL OR ParentalRatingValue<=@MaxParentalRating)");
+                whereClauses.Add("InheritedParentalRatingValue<=@MaxParentalRating");
                 cmd.Parameters.Add(cmd, "@MaxParentalRating", DbType.Int32).Value = query.MaxParentalRating.Value;
                 cmd.Parameters.Add(cmd, "@MaxParentalRating", DbType.Int32).Value = query.MaxParentalRating.Value;
             }
             }
 
 
@@ -1631,11 +1924,11 @@ namespace MediaBrowser.Server.Implementations.Persistence
             {
             {
                 if (query.HasParentalRating.Value)
                 if (query.HasParentalRating.Value)
                 {
                 {
-                    whereClauses.Add("ParentalRatingValue NOT NULL");
+                    whereClauses.Add("InheritedParentalRatingValue > 0");
                 }
                 }
                 else
                 else
                 {
                 {
-                    whereClauses.Add("ParentalRatingValue IS NULL");
+                    whereClauses.Add("InheritedParentalRatingValue = 0");
                 }
                 }
             }
             }
 
 
@@ -1646,7 +1939,60 @@ namespace MediaBrowser.Server.Implementations.Persistence
                     whereClauses.Add("ParentId NOT NULL AND ParentId NOT IN (select guid from TypedBaseItems)");
                     whereClauses.Add("ParentId NOT NULL AND ParentId NOT IN (select guid from TypedBaseItems)");
                 }
                 }
             }
             }
+            if (query.ExcludeLocationTypes.Length == 1)
+            {
+                whereClauses.Add("LocationType<>@LocationType");
+                cmd.Parameters.Add(cmd, "@LocationType", DbType.String).Value = query.ExcludeLocationTypes[0].ToString();
+            }
+            if (query.ExcludeLocationTypes.Length > 1)
+            {
+                var val = string.Join(",", query.ExcludeLocationTypes.Select(i => "'" + i + "'").ToArray());
+
+                whereClauses.Add("LocationType not in (" + val + ")");
+            }
 
 
+            if (query.TopParentIds.Length == 1)
+            {
+                whereClauses.Add("(TopParentId=@TopParentId)");
+                cmd.Parameters.Add(cmd, "@TopParentId", DbType.String).Value = query.TopParentIds[0];
+            }
+            if (query.TopParentIds.Length > 1)
+            {
+                var val = string.Join(",", query.TopParentIds.Select(i => "'" + i + "'").ToArray());
+
+                whereClauses.Add("(TopParentId in (" + val + "))");
+            }
+
+            if (query.AncestorIds.Length == 1)
+            {
+                whereClauses.Add("Guid in (select itemId from AncestorIds where AncestorId=@AncestorId)");
+                cmd.Parameters.Add(cmd, "@AncestorId", DbType.Guid).Value = new Guid(query.AncestorIds[0]);
+            }
+            if (query.AncestorIds.Length > 1)
+            {
+                var inClause = string.Join(",", query.AncestorIds.Select(i => "'" + new Guid(i).ToString("N") + "'").ToArray());
+                whereClauses.Add(string.Format("Guid in (select itemId from AncestorIds where AncestorIdText in ({0}))", inClause));
+            }
+
+            if (query.BlockUnratedItems.Length == 1)
+            {
+                whereClauses.Add("(InheritedParentalRatingValue > 0 or UnratedType <> @UnratedType)");
+                cmd.Parameters.Add(cmd, "@UnratedType", DbType.String).Value = query.BlockUnratedItems[0].ToString();
+            }
+            if (query.BlockUnratedItems.Length > 1)
+            {
+                var inClause = string.Join(",", query.BlockUnratedItems.Select(i => "'" + i.ToString() + "'").ToArray());
+                whereClauses.Add(string.Format("(InheritedParentalRatingValue > 0 or UnratedType not in ({0}))", inClause));
+            }
+
+            //var excludeTagIndex = 0;
+            //foreach (var excludeTag in query.ExcludeTags)
+            //{
+            //    whereClauses.Add("Tags not like @excludeTag" + excludeTagIndex);
+            //    cmd.Parameters.Add(cmd, "@excludeTag" + excludeTagIndex, DbType.String).Value = "%" + excludeTag + "%";
+            //    excludeTagIndex++;
+            //}
+            
             if (addPaging)
             if (addPaging)
             {
             {
                 if (query.StartIndex.HasValue && query.StartIndex.Value > 0)
                 if (query.StartIndex.HasValue && query.StartIndex.Value > 0)
@@ -1703,6 +2049,83 @@ namespace MediaBrowser.Server.Implementations.Persistence
             typeof(AggregateFolder)
             typeof(AggregateFolder)
         };
         };
 
 
+        public async Task UpdateInheritedValues(CancellationToken cancellationToken)
+        {
+            var newValues = new List<Tuple<Guid, int>>();
+
+            using (var cmd = _connection.CreateCommand())
+            {
+                cmd.CommandText = "select Guid,InheritedParentalRatingValue,(select Max(ParentalRatingValue, (select COALESCE(MAX(ParentalRatingValue),0) from TypedBaseItems where guid in (Select AncestorId from AncestorIds where ItemId=Outer.guid)))) as NewInheritedParentalRatingValue from typedbaseitems as Outer where InheritedParentalRatingValue <> NewInheritedParentalRatingValue";
+
+                using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
+                {
+                    while (reader.Read())
+                    {
+                        var id = reader.GetGuid(0);
+                        var newValue = reader.GetInt32(2);
+
+                        newValues.Add(new Tuple<Guid, int>(id, newValue));
+                    }
+                }
+            }
+
+            if (newValues.Count == 0)
+            {
+                return;
+            }
+            
+            await _writeLock.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+            IDbTransaction transaction = null;
+
+            try
+            {
+                transaction = _connection.BeginTransaction();
+
+                foreach (var item in newValues)
+                {
+                    _updateInheritedRatingCommand.GetParameter(0).Value = item.Item1;
+                    _updateInheritedRatingCommand.GetParameter(1).Value = item.Item2;
+
+                    _updateInheritedRatingCommand.Transaction = transaction;
+                    _updateInheritedRatingCommand.ExecuteNonQuery();
+
+                    _updateInheritedRatingCommand.ExecuteNonQuery();
+                }
+
+                transaction.Commit();
+            }
+            catch (OperationCanceledException)
+            {
+                if (transaction != null)
+                {
+                    transaction.Rollback();
+                }
+
+                throw;
+            }
+            catch (Exception e)
+            {
+                _logger.ErrorException("Error running query:", e);
+
+                if (transaction != null)
+                {
+                    transaction.Rollback();
+                }
+
+                throw;
+            }
+            finally
+            {
+                if (transaction != null)
+                {
+                    transaction.Dispose();
+                }
+
+                _writeLock.Release();
+            }
+        }
+
         private static Dictionary<string, string[]> GetTypeMapDictionary()
         private static Dictionary<string, string[]> GetTypeMapDictionary()
         {
         {
             var dict = new Dictionary<string, string[]>();
             var dict = new Dictionary<string, string[]>();
@@ -1712,6 +2135,8 @@ namespace MediaBrowser.Server.Implementations.Persistence
                 dict[t.Name] = new[] { t.FullName };
                 dict[t.Name] = new[] { t.FullName };
             }
             }
 
 
+            dict["ChannelItem"] = new[] { typeof(ChannelVideoItem).FullName, typeof(ChannelAudioItem).FullName, typeof(ChannelFolderItem).FullName };
+            dict["LiveTvItem"] = new[] { typeof(LiveTvAudioRecording).FullName, typeof(LiveTvVideoRecording).FullName, typeof(LiveTvChannel).FullName, typeof(LiveTvProgram).FullName };
             dict["Recording"] = new[] { typeof(LiveTvAudioRecording).FullName, typeof(LiveTvVideoRecording).FullName };
             dict["Recording"] = new[] { typeof(LiveTvAudioRecording).FullName, typeof(LiveTvVideoRecording).FullName };
             dict["Program"] = new[] { typeof(LiveTvProgram).FullName };
             dict["Program"] = new[] { typeof(LiveTvProgram).FullName };
             dict["TvChannel"] = new[] { typeof(LiveTvChannel).FullName };
             dict["TvChannel"] = new[] { typeof(LiveTvChannel).FullName };
@@ -1770,11 +2195,16 @@ namespace MediaBrowser.Server.Implementations.Persistence
                 _deleteStreamsCommand.Transaction = transaction;
                 _deleteStreamsCommand.Transaction = transaction;
                 _deleteStreamsCommand.ExecuteNonQuery();
                 _deleteStreamsCommand.ExecuteNonQuery();
 
 
+                // Delete ancestors
+                _deleteAncestorsCommand.GetParameter(0).Value = id;
+                _deleteAncestorsCommand.Transaction = transaction;
+                _deleteAncestorsCommand.ExecuteNonQuery();
+
                 // Delete the item
                 // Delete the item
                 _deleteItemCommand.GetParameter(0).Value = id;
                 _deleteItemCommand.GetParameter(0).Value = id;
                 _deleteItemCommand.Transaction = transaction;
                 _deleteItemCommand.Transaction = transaction;
                 _deleteItemCommand.ExecuteNonQuery();
                 _deleteItemCommand.ExecuteNonQuery();
-                
+
                 transaction.Commit();
                 transaction.Commit();
             }
             }
             catch (OperationCanceledException)
             catch (OperationCanceledException)
@@ -2003,6 +2433,38 @@ namespace MediaBrowser.Server.Implementations.Persistence
             return whereClauses;
             return whereClauses;
         }
         }
 
 
+        private void UpdateAncestors(Guid itemId, List<Guid> ancestorIds, IDbTransaction transaction)
+        {
+            if (itemId == Guid.Empty)
+            {
+                throw new ArgumentNullException("itemId");
+            }
+
+            if (ancestorIds == null)
+            {
+                throw new ArgumentNullException("ancestorIds");
+            }
+
+            CheckDisposed();
+
+            // First delete 
+            _deleteAncestorsCommand.GetParameter(0).Value = itemId;
+            _deleteAncestorsCommand.Transaction = transaction;
+
+            _deleteAncestorsCommand.ExecuteNonQuery();
+
+            foreach (var ancestorId in ancestorIds)
+            {
+                _saveAncestorCommand.GetParameter(0).Value = itemId;
+                _saveAncestorCommand.GetParameter(1).Value = ancestorId;
+                _saveAncestorCommand.GetParameter(2).Value = ancestorId.ToString("N");
+
+                _saveAncestorCommand.Transaction = transaction;
+
+                _saveAncestorCommand.ExecuteNonQuery();
+            }
+        }
+
         public async Task UpdatePeople(Guid itemId, List<PersonInfo> people)
         public async Task UpdatePeople(Guid itemId, List<PersonInfo> people)
         {
         {
             if (itemId == Guid.Empty)
             if (itemId == Guid.Empty)
@@ -2220,7 +2682,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
                     _saveStreamCommand.GetParameter(index++).Value = stream.RefFrames;
                     _saveStreamCommand.GetParameter(index++).Value = stream.RefFrames;
                     _saveStreamCommand.GetParameter(index++).Value = stream.IsCabac;
                     _saveStreamCommand.GetParameter(index++).Value = stream.IsCabac;
 
 
-                    _saveStreamCommand.GetParameter(index++).Value = null;
+                    _saveStreamCommand.GetParameter(index++).Value = stream.CodecTag;
 
 
                     _saveStreamCommand.Transaction = transaction;
                     _saveStreamCommand.Transaction = transaction;
                     _saveStreamCommand.ExecuteNonQuery();
                     _saveStreamCommand.ExecuteNonQuery();
@@ -2376,10 +2838,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
 
 
             if (!reader.IsDBNull(26))
             if (!reader.IsDBNull(26))
             {
             {
-                var frames = reader.GetString(26);
-                if (!string.IsNullOrWhiteSpace(frames))
-                {
-                }
+                item.CodecTag = reader.GetString(26);
             }
             }
 
 
             return item;
             return item;

+ 0 - 5
MediaBrowser.Server.Implementations/Playlists/ManualPlaylistsFolder.cs

@@ -34,11 +34,6 @@ namespace MediaBrowser.Server.Implementations.Playlists
             }
             }
         }
         }
 
 
-        public override bool IsHiddenFromUser(User user)
-        {
-            return false;
-        }
-
         public override string CollectionType
         public override string CollectionType
         {
         {
             get { return Model.Entities.CollectionType.Playlists; }
             get { return Model.Entities.CollectionType.Playlists; }

+ 1 - 1
MediaBrowser.Server.Implementations/Playlists/PlaylistImageProvider.cs

@@ -47,7 +47,7 @@ namespace MediaBrowser.Server.Implementations.Playlists
                         return subItem;
                         return subItem;
                     }
                     }
 
 
-                    var parent = subItem.Parent;
+                    var parent = subItem.GetParent();
 
 
                     if (parent != null && parent.HasImage(ImageType.Primary))
                     if (parent != null && parent.HasImage(ImageType.Primary))
                     {
                     {

+ 1 - 0
MediaBrowser.Server.Implementations/Playlists/PlaylistManager.cs

@@ -264,6 +264,7 @@ namespace MediaBrowser.Server.Implementations.Playlists
         public Folder GetPlaylistsFolder(string userId)
         public Folder GetPlaylistsFolder(string userId)
         {
         {
             return _libraryManager.RootFolder.Children.OfType<PlaylistsFolder>()
             return _libraryManager.RootFolder.Children.OfType<PlaylistsFolder>()
+                .FirstOrDefault() ?? _libraryManager.GetUserRootFolder().Children.OfType<PlaylistsFolder>()
                 .FirstOrDefault();
                 .FirstOrDefault();
         }
         }
     }
     }

部分文件因为文件数量过多而无法显示