Pārlūkot izejas kodu

Merge branch 'beta'

Luke Pulverenti 9 gadi atpakaļ
vecāks
revīzija
b253d26aba
44 mainītis faili ar 528 papildinājumiem un 372 dzēšanām
  1. 2 1
      MediaBrowser.Api/FilterService.cs
  2. 4 1
      MediaBrowser.Api/GamesService.cs
  3. 10 5
      MediaBrowser.Api/StartupWizardService.cs
  4. 1 12
      MediaBrowser.Api/TvShowsService.cs
  5. 8 1
      MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs
  6. 3 9
      MediaBrowser.Api/UserLibrary/ItemsService.cs
  7. 3 3
      MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj
  8. 2 2
      MediaBrowser.Common.Implementations/packages.config
  9. 34 3
      MediaBrowser.Controller/Entities/AggregateFolder.cs
  10. 11 26
      MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
  11. 35 25
      MediaBrowser.Controller/Entities/CollectionFolder.cs
  12. 43 50
      MediaBrowser.Controller/Entities/Folder.cs
  13. 0 6
      MediaBrowser.Controller/Entities/IItemByName.cs
  14. 1 2
      MediaBrowser.Controller/Entities/InternalItemsQuery.cs
  15. 17 17
      MediaBrowser.Controller/Entities/TV/Season.cs
  16. 12 15
      MediaBrowser.Controller/Entities/TV/Series.cs
  17. 1 8
      MediaBrowser.Controller/Entities/User.cs
  18. 0 9
      MediaBrowser.Controller/Entities/UserRootFolder.cs
  19. 7 4
      MediaBrowser.Controller/Entities/UserView.cs
  20. 32 40
      MediaBrowser.Controller/Entities/UserViewBuilder.cs
  21. 8 4
      MediaBrowser.Controller/Playlists/Playlist.cs
  22. 6 27
      MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs
  23. 3 0
      MediaBrowser.Model/Configuration/ServerConfiguration.cs
  24. 11 7
      MediaBrowser.Model/Entities/MediaStream.cs
  25. 5 0
      MediaBrowser.Model/Extensions/StringHelper.cs
  26. 3 0
      MediaBrowser.Model/LiveTv/LiveTvOptions.cs
  27. 17 0
      MediaBrowser.Model/MediaInfo/AudioCodec.cs
  28. 4 0
      MediaBrowser.Model/Querying/ItemQuery.cs
  29. 9 14
      MediaBrowser.Providers/Manager/MetadataService.cs
  30. 5 7
      MediaBrowser.Providers/TV/DummySeasonProvider.cs
  31. 1 1
      MediaBrowser.Providers/TV/MissingEpisodeProvider.cs
  32. 5 5
      MediaBrowser.Providers/TV/SeasonMetadataService.cs
  33. 6 2
      MediaBrowser.Server.Implementations/Library/LibraryManager.cs
  34. 16 4
      MediaBrowser.Server.Implementations/Library/MusicManager.cs
  35. 24 6
      MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
  36. 23 5
      MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
  37. 5 5
      MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
  38. 139 14
      MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs
  39. 2 2
      MediaBrowser.Server.Implementations/packages.config
  40. 3 3
      MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj
  41. 4 4
      Nuget/MediaBrowser.Common.Internal.nuspec
  42. 1 1
      Nuget/MediaBrowser.Common.nuspec
  43. 0 20
      Nuget/MediaBrowser.Model.Signed.nuspec
  44. 2 2
      Nuget/MediaBrowser.Server.Core.nuspec

+ 2 - 1
MediaBrowser.Api/FilterService.cs

@@ -103,7 +103,8 @@ namespace MediaBrowser.Api
                 User = user,
                 User = user,
                 MediaTypes = request.GetMediaTypes(),
                 MediaTypes = request.GetMediaTypes(),
                 IncludeItemTypes = request.GetIncludeItemTypes(),
                 IncludeItemTypes = request.GetIncludeItemTypes(),
-                Recursive = true
+                Recursive = true,
+                EnableTotalRecordCount = false
             };
             };
 
 
             return query;
             return query;

+ 4 - 1
MediaBrowser.Api/GamesService.cs

@@ -162,7 +162,10 @@ namespace MediaBrowser.Api
 
 
             var items = user == null ? 
             var items = user == null ? 
                 system.GetRecursiveChildren(i => i is Game) :
                 system.GetRecursiveChildren(i => i is Game) :
-                system.GetRecursiveChildren(user, i => i is Game);
+                system.GetRecursiveChildren(user, new InternalItemsQuery(user)
+                {
+                    IncludeItemTypes = new[] { typeof(Game).Name }
+                });
 
 
             var games = items.Cast<Game>().ToList();
             var games = items.Cast<Game>().ToList();
 
 

+ 10 - 5
MediaBrowser.Api/StartupWizardService.cs

@@ -65,11 +65,7 @@ namespace MediaBrowser.Api
         public void Post(ReportStartupWizardComplete request)
         public void Post(ReportStartupWizardComplete request)
         {
         {
             _config.Configuration.IsStartupWizardCompleted = true;
             _config.Configuration.IsStartupWizardCompleted = true;
-            _config.Configuration.EnableLocalizedGuids = true;
-            _config.Configuration.EnableCustomPathSubFolders = true;
-            _config.Configuration.EnableDateLastRefresh = true;
-            _config.Configuration.EnableStandaloneMusicKeys = true;
-            _config.Configuration.EnableCaseSensitiveItemIds = true;
+            SetWizardFinishValues(_config.Configuration);
             _config.SaveConfiguration();
             _config.SaveConfiguration();
         }
         }
 
 
@@ -111,6 +107,15 @@ namespace MediaBrowser.Api
             return result;
             return result;
         }
         }
 
 
+        private void SetWizardFinishValues(ServerConfiguration config)
+        {
+            config.EnableLocalizedGuids = true;
+            config.EnableCustomPathSubFolders = true;
+            config.EnableDateLastRefresh = true;
+            config.EnableStandaloneMusicKeys = true;
+            config.EnableCaseSensitiveItemIds = true;
+        }
+
         public void Post(UpdateStartupConfiguration request)
         public void Post(UpdateStartupConfiguration request)
         {
         {
             _config.Configuration.UICulture = request.UICulture;
             _config.Configuration.UICulture = request.UICulture;

+ 1 - 12
MediaBrowser.Api/TvShowsService.cs

@@ -415,21 +415,10 @@ namespace MediaBrowser.Api
 
 
         private IEnumerable<Season> FilterVirtualSeasons(GetSeasons request, IEnumerable<Season> items)
         private IEnumerable<Season> FilterVirtualSeasons(GetSeasons request, IEnumerable<Season> items)
         {
         {
-            if (request.IsMissing.HasValue && request.IsVirtualUnaired.HasValue)
-            {
-                var isMissing = request.IsMissing.Value;
-                var isVirtualUnaired = request.IsVirtualUnaired.Value;
-
-                if (!isMissing && !isVirtualUnaired)
-                {
-                    return items.Where(i => !i.IsMissingOrVirtualUnaired);
-                }
-            }
-
             if (request.IsMissing.HasValue)
             if (request.IsMissing.HasValue)
             {
             {
                 var val = request.IsMissing.Value;
                 var val = request.IsMissing.Value;
-                items = items.Where(i => (i.IsMissingSeason ?? false) == val);
+                items = items.Where(i => (i.IsMissingSeason) == val);
             }
             }
 
 
             if (request.IsVirtualUnaired.HasValue)
             if (request.IsVirtualUnaired.HasValue)

+ 8 - 1
MediaBrowser.Api/UserLibrary/BaseItemsByNameService.cs

@@ -121,6 +121,13 @@ namespace MediaBrowser.Api.UserLibrary
             var includeItemTypes = request.GetIncludeItemTypes();
             var includeItemTypes = request.GetIncludeItemTypes();
             var mediaTypes = request.GetMediaTypes();
             var mediaTypes = request.GetMediaTypes();
 
 
+            var query = new InternalItemsQuery(user)
+            {
+                ExcludeItemTypes = excludeItemTypes,
+                IncludeItemTypes = includeItemTypes,
+                MediaTypes = mediaTypes
+            };
+
             Func<BaseItem, bool> filter = i => FilterItem(request, i, excludeItemTypes, includeItemTypes, mediaTypes);
             Func<BaseItem, bool> filter = i => FilterItem(request, i, excludeItemTypes, includeItemTypes, mediaTypes);
 
 
             if (parentItem.IsFolder)
             if (parentItem.IsFolder)
@@ -130,7 +137,7 @@ namespace MediaBrowser.Api.UserLibrary
                 if (!string.IsNullOrWhiteSpace(request.UserId))
                 if (!string.IsNullOrWhiteSpace(request.UserId))
                 {
                 {
                     items = request.Recursive ?
                     items = request.Recursive ?
-                        folder.GetRecursiveChildren(user, filter) :
+                        folder.GetRecursiveChildren(user, query) :
                         folder.GetChildren(user, true).Where(filter);
                         folder.GetChildren(user, true).Where(filter);
                 }
                 }
                 else
                 else

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

@@ -138,25 +138,19 @@ namespace MediaBrowser.Api.UserLibrary
 
 
             if (request.Recursive)
             if (request.Recursive)
             {
             {
-                var result = await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
-
-                return result;
+                return await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
             }
             }
 
 
             if (user == null)
             if (user == null)
             {
             {
-                var result = await ((Folder)item).GetItems(GetItemsQuery(request, null)).ConfigureAwait(false);
-
-                return result;
+                return await ((Folder)item).GetItems(GetItemsQuery(request, null)).ConfigureAwait(false);
             }
             }
 
 
             var userRoot = item as UserRootFolder;
             var userRoot = item as UserRootFolder;
 
 
             if (userRoot == null)
             if (userRoot == null)
             {
             {
-                var result = await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
-
-                return result;
+                return await ((Folder)item).GetItems(GetItemsQuery(request, user)).ConfigureAwait(false);
             }
             }
 
 
             IEnumerable<BaseItem> items = ((Folder)item).GetChildren(user, true);
             IEnumerable<BaseItem> items = ((Folder)item).GetChildren(user, true);

+ 3 - 3
MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj

@@ -55,7 +55,7 @@
       <HintPath>..\packages\morelinq.1.4.0\lib\net35\MoreLinq.dll</HintPath>
       <HintPath>..\packages\morelinq.1.4.0\lib\net35\MoreLinq.dll</HintPath>
     </Reference>
     </Reference>
     <Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
     <Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
-      <HintPath>..\packages\NLog.4.3.1\lib\net45\NLog.dll</HintPath>
+      <HintPath>..\packages\NLog.4.3.4\lib\net45\NLog.dll</HintPath>
       <Private>True</Private>
       <Private>True</Private>
     </Reference>
     </Reference>
     <Reference Include="Patterns.Logging">
     <Reference Include="Patterns.Logging">
@@ -65,8 +65,8 @@
       <SpecificVersion>False</SpecificVersion>
       <SpecificVersion>False</SpecificVersion>
       <HintPath>..\ThirdParty\SharpCompress\SharpCompress.dll</HintPath>
       <HintPath>..\ThirdParty\SharpCompress\SharpCompress.dll</HintPath>
     </Reference>
     </Reference>
-    <Reference Include="SimpleInjector, Version=3.1.3.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
-      <HintPath>..\packages\SimpleInjector.3.1.3\lib\net45\SimpleInjector.dll</HintPath>
+    <Reference Include="SimpleInjector, Version=3.1.4.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
+      <HintPath>..\packages\SimpleInjector.3.1.4\lib\net45\SimpleInjector.dll</HintPath>
       <Private>True</Private>
       <Private>True</Private>
     </Reference>
     </Reference>
     <Reference Include="System" />
     <Reference Include="System" />

+ 2 - 2
MediaBrowser.Common.Implementations/packages.config

@@ -2,7 +2,7 @@
 <packages>
 <packages>
   <package id="CommonIO" version="1.0.0.9" targetFramework="net45" />
   <package id="CommonIO" version="1.0.0.9" targetFramework="net45" />
   <package id="morelinq" version="1.4.0" targetFramework="net45" />
   <package id="morelinq" version="1.4.0" targetFramework="net45" />
-  <package id="NLog" version="4.3.1" targetFramework="net45" />
+  <package id="NLog" version="4.3.4" targetFramework="net45" />
   <package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
   <package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
-  <package id="SimpleInjector" version="3.1.3" targetFramework="net45" />
+  <package id="SimpleInjector" version="3.1.4" targetFramework="net45" />
 </packages>
 </packages>

+ 34 - 3
MediaBrowser.Controller/Entities/AggregateFolder.cs

@@ -64,10 +64,37 @@ namespace MediaBrowser.Controller.Entities
 
 
         protected override IEnumerable<FileSystemMetadata> GetFileSystemChildren(IDirectoryService directoryService)
         protected override IEnumerable<FileSystemMetadata> GetFileSystemChildren(IDirectoryService directoryService)
         {
         {
-            return CreateResolveArgs(directoryService).FileSystemChildren;
+            return CreateResolveArgs(directoryService, true).FileSystemChildren;
         }
         }
 
 
-        private ItemResolveArgs CreateResolveArgs(IDirectoryService directoryService)
+        private bool _requiresRefresh;
+        public override bool RequiresRefresh()
+        {
+            var changed = base.RequiresRefresh() || _requiresRefresh;
+
+            if (!changed)
+            {
+                var locations = PhysicalLocations.ToList();
+
+                var newLocations = CreateResolveArgs(new DirectoryService(BaseItem.FileSystem), false).PhysicalLocations.ToList();
+
+                if (!locations.SequenceEqual(newLocations))
+                {
+                    changed = true;
+                }
+            }
+
+            return changed;
+        }
+
+        public override bool BeforeMetadataRefresh()
+        {
+            var changed = base.BeforeMetadataRefresh() || _requiresRefresh;
+            _requiresRefresh = false;
+            return changed;
+        }
+
+        private ItemResolveArgs CreateResolveArgs(IDirectoryService directoryService, bool setPhysicalLocations)
         {
         {
             var path = ContainingFolderPath;
             var path = ContainingFolderPath;
 
 
@@ -100,7 +127,11 @@ namespace MediaBrowser.Controller.Entities
                 args.FileSystemDictionary = fileSystemDictionary;
                 args.FileSystemDictionary = fileSystemDictionary;
             }
             }
 
 
-            PhysicalLocationsList = args.PhysicalLocations.ToList();
+            _requiresRefresh = _requiresRefresh || !args.PhysicalLocations.SequenceEqual(PhysicalLocations);
+            if (setPhysicalLocations)
+            {
+                PhysicalLocationsList = args.PhysicalLocations.ToList();
+            }
 
 
             return args;
             return args;
         }
         }

+ 11 - 26
MediaBrowser.Controller/Entities/Audio/MusicArtist.cs

@@ -56,38 +56,23 @@ namespace MediaBrowser.Controller.Entities.Audio
 
 
         public IEnumerable<BaseItem> GetTaggedItems(InternalItemsQuery query)
         public IEnumerable<BaseItem> GetTaggedItems(InternalItemsQuery query)
         {
         {
-            var itemByNameFilter = GetItemFilter();
+            if (query.IncludeItemTypes.Length == 0)
+            {
+                query.IncludeItemTypes = new[] { typeof(Audio).Name, typeof(MusicVideo).Name, typeof(MusicAlbum).Name };
+                query.ArtistNames = new[] { Name };
+            }
 
 
-            if (query.User != null)
+            // Need this for now since the artist filter isn't yet supported by the db
+            if (ConfigurationManager.Configuration.SchemaVersion < 79)
             {
             {
-                return query.User.RootFolder
-                    .GetRecursiveChildren(query.User, i =>
-                    {
-                        if (query.IsFolder.HasValue)
-                        {
-                            if (query.IsFolder.Value != i.IsFolder)
-                            {
-                                return false;
-                            }
-                        }
-                        return itemByNameFilter(i);
-                    });
+                var filter = GetItemFilter();
+                return LibraryManager.GetItemList(query).Where(filter);
             }
             }
 
 
-            return LibraryManager.RootFolder
-                .GetRecursiveChildren(i =>
-                {
-                    if (query.IsFolder.HasValue)
-                    {
-                        if (query.IsFolder.Value != i.IsFolder)
-                        {
-                            return false;
-                        }
-                    }
-                    return itemByNameFilter(i);
-                });
+            return LibraryManager.GetItemList(query);
         }
         }
 
 
+        [IgnoreDataMember]
         protected override IEnumerable<BaseItem> ActualChildren
         protected override IEnumerable<BaseItem> ActualChildren
         {
         {
             get
             get

+ 35 - 25
MediaBrowser.Controller/Entities/CollectionFolder.cs

@@ -23,19 +23,6 @@ namespace MediaBrowser.Controller.Entities
             PhysicalLocationsList = new List<string>();
             PhysicalLocationsList = new List<string>();
         }
         }
 
 
-        /// <summary>
-        /// Gets a value indicating whether this instance is virtual folder.
-        /// </summary>
-        /// <value><c>true</c> if this instance is virtual folder; otherwise, <c>false</c>.</value>
-        [IgnoreDataMember]
-        public override bool IsVirtualFolder
-        {
-            get
-            {
-                return true;
-            }
-        }
-
         [IgnoreDataMember]
         [IgnoreDataMember]
         protected override bool SupportsShortcutChildren
         protected override bool SupportsShortcutChildren
         {
         {
@@ -83,7 +70,34 @@ namespace MediaBrowser.Controller.Entities
 
 
         protected override IEnumerable<FileSystemMetadata> GetFileSystemChildren(IDirectoryService directoryService)
         protected override IEnumerable<FileSystemMetadata> GetFileSystemChildren(IDirectoryService directoryService)
         {
         {
-            return CreateResolveArgs(directoryService).FileSystemChildren;
+            return CreateResolveArgs(directoryService, true).FileSystemChildren;
+        }
+
+        private bool _requiresRefresh;
+        public override bool RequiresRefresh()
+        {
+            var changed = base.RequiresRefresh() || _requiresRefresh;
+
+            if (!changed)
+            {
+                var locations = PhysicalLocations.ToList();
+
+                var newLocations = CreateResolveArgs(new DirectoryService(BaseItem.FileSystem), false).PhysicalLocations.ToList();
+
+                if (!locations.SequenceEqual(newLocations))
+                {
+                    changed = true;
+                }
+            }
+
+            return changed;
+        }
+
+        public override bool BeforeMetadataRefresh()
+        {
+            var changed = base.BeforeMetadataRefresh() || _requiresRefresh;
+            _requiresRefresh = false;
+            return changed;
         }
         }
 
 
         internal override bool IsValidFromResolver(BaseItem newItem)
         internal override bool IsValidFromResolver(BaseItem newItem)
@@ -101,7 +115,7 @@ namespace MediaBrowser.Controller.Entities
             return base.IsValidFromResolver(newItem);
             return base.IsValidFromResolver(newItem);
         }
         }
 
 
-        private ItemResolveArgs CreateResolveArgs(IDirectoryService directoryService)
+        private ItemResolveArgs CreateResolveArgs(IDirectoryService directoryService, bool setPhysicalLocations)
         {
         {
             var path = ContainingFolderPath;
             var path = ContainingFolderPath;
 
 
@@ -135,7 +149,11 @@ namespace MediaBrowser.Controller.Entities
                 args.FileSystemDictionary = fileSystemDictionary;
                 args.FileSystemDictionary = fileSystemDictionary;
             }
             }
 
 
-            PhysicalLocationsList = args.PhysicalLocations.ToList();
+            _requiresRefresh = _requiresRefresh || !args.PhysicalLocations.SequenceEqual(PhysicalLocations);
+            if (setPhysicalLocations)
+            {
+                PhysicalLocationsList = args.PhysicalLocations.ToList();
+            }
 
 
             return args;
             return args;
         }
         }
@@ -153,15 +171,6 @@ namespace MediaBrowser.Controller.Entities
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
         protected override Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService)
         protected override Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool recursive, bool refreshChildMetadata, MetadataRefreshOptions refreshOptions, IDirectoryService directoryService)
         {
         {
-            var list = PhysicalLocationsList.ToList();
-
-            CreateResolveArgs(directoryService);
-
-            if (!list.SequenceEqual(PhysicalLocationsList))
-            {
-                return UpdateToRepository(ItemUpdateType.MetadataImport, cancellationToken);
-            }
-
             return Task.FromResult(true);
             return Task.FromResult(true);
         }
         }
 
 
@@ -188,6 +197,7 @@ namespace MediaBrowser.Controller.Entities
         /// Our children are actually just references to the ones in the physical root...
         /// Our children are actually just references to the ones in the physical root...
         /// </summary>
         /// </summary>
         /// <value>The actual children.</value>
         /// <value>The actual children.</value>
+        [IgnoreDataMember]
         protected override IEnumerable<BaseItem> ActualChildren
         protected override IEnumerable<BaseItem> ActualChildren
         {
         {
             get { return GetActualChildren(); }
             get { return GetActualChildren(); }

+ 43 - 50
MediaBrowser.Controller/Entities/Folder.cs

@@ -126,19 +126,6 @@ namespace MediaBrowser.Controller.Entities
         /// <value><c>true</c> if this instance is root; otherwise, <c>false</c>.</value>
         /// <value><c>true</c> if this instance is root; otherwise, <c>false</c>.</value>
         public bool IsRoot { get; set; }
         public bool IsRoot { get; set; }
 
 
-        /// <summary>
-        /// Gets a value indicating whether this instance is virtual folder.
-        /// </summary>
-        /// <value><c>true</c> if this instance is virtual folder; otherwise, <c>false</c>.</value>
-        [IgnoreDataMember]
-        public virtual bool IsVirtualFolder
-        {
-            get
-            {
-                return false;
-            }
-        }
-
         public virtual List<LinkedChild> LinkedChildren { get; set; }
         public virtual List<LinkedChild> LinkedChildren { get; set; }
 
 
         [IgnoreDataMember]
         [IgnoreDataMember]
@@ -285,6 +272,7 @@ namespace MediaBrowser.Controller.Entities
         /// Gets the actual children.
         /// Gets the actual children.
         /// </summary>
         /// </summary>
         /// <value>The actual children.</value>
         /// <value>The actual children.</value>
+        [IgnoreDataMember]
         protected virtual IEnumerable<BaseItem> ActualChildren
         protected virtual IEnumerable<BaseItem> ActualChildren
         {
         {
             get
             get
@@ -749,7 +737,7 @@ namespace MediaBrowser.Controller.Entities
         {
         {
             var user = query.User;
             var user = query.User;
 
 
-            if (RequiresPostFiltering(query))
+            if (!query.ForceDirect && RequiresPostFiltering(query))
             {
             {
                 IEnumerable<BaseItem> items;
                 IEnumerable<BaseItem> items;
                 Func<BaseItem, bool> filter = i => UserViewBuilder.Filter(i, user, query, UserDataManager, LibraryManager);
                 Func<BaseItem, bool> filter = i => UserViewBuilder.Filter(i, user, query, UserDataManager, LibraryManager);
@@ -760,7 +748,7 @@ namespace MediaBrowser.Controller.Entities
                 }
                 }
                 else
                 else
                 {
                 {
-                    items = GetRecursiveChildren(user, filter);
+                    items = GetRecursiveChildren(user, query);
                 }
                 }
 
 
                 return PostFilterAndSort(items, query);
                 return PostFilterAndSort(items, query);
@@ -784,7 +772,7 @@ namespace MediaBrowser.Controller.Entities
                     return true;
                     return true;
                 }
                 }
             }
             }
-            
+
             var supportsUserDataQueries = ConfigurationManager.Configuration.SchemaVersion >= 76;
             var supportsUserDataQueries = ConfigurationManager.Configuration.SchemaVersion >= 76;
 
 
             if (query.SortBy != null && query.SortBy.Length > 0)
             if (query.SortBy != null && query.SortBy.Length > 0)
@@ -817,19 +805,24 @@ namespace MediaBrowser.Controller.Entities
                         return true;
                         return true;
                     }
                     }
                 }
                 }
-                if (query.SortBy.Contains(ItemSortBy.AiredEpisodeOrder, StringComparer.OrdinalIgnoreCase))
-                {
-                    Logger.Debug("Query requires post-filtering due to ItemSortBy.AiredEpisodeOrder");
-                    return true;
-                }
-                if (query.SortBy.Contains(ItemSortBy.AlbumArtist, StringComparer.OrdinalIgnoreCase))
+
+                if (ConfigurationManager.Configuration.SchemaVersion < 79)
                 {
                 {
-                    Logger.Debug("Query requires post-filtering due to ItemSortBy.AlbumArtist");
-                    return true;
+                    if (query.SortBy.Contains(ItemSortBy.AlbumArtist, StringComparer.OrdinalIgnoreCase))
+                    {
+                        Logger.Debug("Query requires post-filtering due to ItemSortBy.AlbumArtist");
+                        return true;
+                    }
+                    if (query.SortBy.Contains(ItemSortBy.Artist, StringComparer.OrdinalIgnoreCase))
+                    {
+                        Logger.Debug("Query requires post-filtering due to ItemSortBy.Artist");
+                        return true;
+                    }
                 }
                 }
-                if (query.SortBy.Contains(ItemSortBy.Artist, StringComparer.OrdinalIgnoreCase))
+
+                if (query.SortBy.Contains(ItemSortBy.AiredEpisodeOrder, StringComparer.OrdinalIgnoreCase))
                 {
                 {
-                    Logger.Debug("Query requires post-filtering due to ItemSortBy.Artist");
+                    Logger.Debug("Query requires post-filtering due to ItemSortBy.AiredEpisodeOrder");
                     return true;
                     return true;
                 }
                 }
                 if (query.SortBy.Contains(ItemSortBy.Budget, StringComparer.OrdinalIgnoreCase))
                 if (query.SortBy.Contains(ItemSortBy.Budget, StringComparer.OrdinalIgnoreCase))
@@ -1109,10 +1102,13 @@ namespace MediaBrowser.Controller.Entities
                 return true;
                 return true;
             }
             }
 
 
-            if (query.ArtistNames.Length > 0)
+            if (ConfigurationManager.Configuration.SchemaVersion < 79)
             {
             {
-                Logger.Debug("Query requires post-filtering due to ArtistNames");
-                return true;
+                if (query.ArtistNames.Length > 0)
+                {
+                    Logger.Debug("Query requires post-filtering due to ArtistNames");
+                    return true;
+                }
             }
             }
 
 
             return false;
             return false;
@@ -1178,7 +1174,7 @@ namespace MediaBrowser.Controller.Entities
             else
             else
             {
             {
                 items = query.Recursive
                 items = query.Recursive
-                   ? GetRecursiveChildren(user, filter)
+                   ? GetRecursiveChildren(user, query)
                    : GetChildren(user, true).Where(filter);
                    : GetChildren(user, true).Where(filter);
             }
             }
 
 
@@ -1215,19 +1211,14 @@ namespace MediaBrowser.Controller.Entities
         /// <summary>
         /// <summary>
         /// Adds the children to list.
         /// Adds the children to list.
         /// </summary>
         /// </summary>
-        /// <param name="user">The user.</param>
-        /// <param name="includeLinkedChildren">if set to <c>true</c> [include linked children].</param>
-        /// <param name="result">The result.</param>
-        /// <param name="recursive">if set to <c>true</c> [recursive].</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 recursive, Func<BaseItem, bool> filter)
+        private void AddChildren(User user, bool includeLinkedChildren, Dictionary<Guid, BaseItem> result, bool recursive, InternalItemsQuery query)
         {
         {
             foreach (var child in GetEligibleChildrenForRecursiveChildren(user))
             foreach (var child in GetEligibleChildrenForRecursiveChildren(user))
             {
             {
                 if (child.IsVisible(user))
                 if (child.IsVisible(user))
                 {
                 {
-                    if (filter == null || filter(child))
+                    if (query == null || UserViewBuilder.FilterItem(child, query))
                     {
                     {
                         result[child.Id] = child;
                         result[child.Id] = child;
                     }
                     }
@@ -1236,7 +1227,7 @@ namespace MediaBrowser.Controller.Entities
                     {
                     {
                         var folder = (Folder)child;
                         var folder = (Folder)child;
 
 
-                        folder.AddChildren(user, includeLinkedChildren, result, true, filter);
+                        folder.AddChildren(user, includeLinkedChildren, result, true, query);
                     }
                     }
                 }
                 }
             }
             }
@@ -1247,7 +1238,7 @@ namespace MediaBrowser.Controller.Entities
                 {
                 {
                     if (child.IsVisible(user))
                     if (child.IsVisible(user))
                     {
                     {
-                        if (filter == null || filter(child))
+                        if (query == null || UserViewBuilder.FilterItem(child, query))
                         {
                         {
                             result[child.Id] = child;
                             result[child.Id] = child;
                         }
                         }
@@ -1265,10 +1256,10 @@ namespace MediaBrowser.Controller.Entities
         /// <exception cref="System.ArgumentNullException"></exception>
         /// <exception cref="System.ArgumentNullException"></exception>
         public IEnumerable<BaseItem> GetRecursiveChildren(User user, bool includeLinkedChildren = true)
         public IEnumerable<BaseItem> GetRecursiveChildren(User user, bool includeLinkedChildren = true)
         {
         {
-            return GetRecursiveChildren(user, i => true);
+            return GetRecursiveChildren(user, null);
         }
         }
 
 
-        public virtual IEnumerable<BaseItem> GetRecursiveChildren(User user, Func<BaseItem, bool> filter)
+        public virtual IEnumerable<BaseItem> GetRecursiveChildren(User user, InternalItemsQuery query)
         {
         {
             if (user == null)
             if (user == null)
             {
             {
@@ -1277,7 +1268,7 @@ namespace MediaBrowser.Controller.Entities
 
 
             var result = new Dictionary<Guid, BaseItem>();
             var result = new Dictionary<Guid, BaseItem>();
 
 
-            AddChildren(user, true, result, true, filter);
+            AddChildren(user, true, result, true, query);
 
 
             return result.Values;
             return result.Values;
         }
         }
@@ -1303,7 +1294,7 @@ namespace MediaBrowser.Controller.Entities
         /// <summary>
         /// <summary>
         /// Adds the children to list.
         /// Adds the children to list.
         /// </summary>
         /// </summary>
-        private void AddChildrenToList(Dictionary<Guid,BaseItem> result, bool includeLinkedChildren, bool recursive, Func<BaseItem, bool> filter)
+        private void AddChildrenToList(Dictionary<Guid, BaseItem> result, bool includeLinkedChildren, bool recursive, Func<BaseItem, bool> filter)
         {
         {
             foreach (var child in Children)
             foreach (var child in Children)
             {
             {
@@ -1534,13 +1525,12 @@ namespace MediaBrowser.Controller.Entities
                 User = user,
                 User = user,
                 Recursive = true,
                 Recursive = true,
                 IsFolder = false,
                 IsFolder = false,
-                IsUnaired = false
-
+                EnableTotalRecordCount = false
             };
             };
 
 
-            if (!user.Configuration.DisplayMissingEpisodes)
+            if (!user.Configuration.DisplayMissingEpisodes || !user.Configuration.DisplayUnairedEpisodes)
             {
             {
-                query.IsMissing = false;
+                query.ExcludeLocationTypes = new[] { LocationType.Virtual };
             }
             }
 
 
             var itemsResult = await GetItems(query).ConfigureAwait(false);
             var itemsResult = await GetItems(query).ConfigureAwait(false);
@@ -1562,7 +1552,8 @@ namespace MediaBrowser.Controller.Entities
             {
             {
                 User = user,
                 User = user,
                 Recursive = true,
                 Recursive = true,
-                IsFolder = false
+                IsFolder = false,
+                EnableTotalRecordCount = false
 
 
             }).ConfigureAwait(false);
             }).ConfigureAwait(false);
 
 
@@ -1578,7 +1569,8 @@ namespace MediaBrowser.Controller.Entities
             {
             {
                 Recursive = true,
                 Recursive = true,
                 IsFolder = false,
                 IsFolder = false,
-                ExcludeLocationTypes = new[] { LocationType.Virtual }
+                ExcludeLocationTypes = new[] { LocationType.Virtual },
+                EnableTotalRecordCount = false
 
 
             }).Result;
             }).Result;
 
 
@@ -1630,7 +1622,8 @@ namespace MediaBrowser.Controller.Entities
             {
             {
                 Recursive = true,
                 Recursive = true,
                 IsFolder = false,
                 IsFolder = false,
-                ExcludeLocationTypes = new[] { LocationType.Virtual }
+                ExcludeLocationTypes = new[] { LocationType.Virtual },
+                EnableTotalRecordCount = false
 
 
             }).Result;
             }).Result;
 
 

+ 0 - 6
MediaBrowser.Controller/Entities/IItemByName.cs

@@ -15,12 +15,6 @@ namespace MediaBrowser.Controller.Entities
         /// <returns>IEnumerable{BaseItem}.</returns>
         /// <returns>IEnumerable{BaseItem}.</returns>
         IEnumerable<BaseItem> GetTaggedItems(IEnumerable<BaseItem> inputItems);
         IEnumerable<BaseItem> GetTaggedItems(IEnumerable<BaseItem> inputItems);
 
 
-        /// <summary>
-        /// Gets the item filter.
-        /// </summary>
-        /// <returns>Func&lt;BaseItem, System.Boolean&gt;.</returns>
-        Func<BaseItem, bool> GetItemFilter();
-
         IEnumerable<BaseItem> GetTaggedItems(InternalItemsQuery query);
         IEnumerable<BaseItem> GetTaggedItems(InternalItemsQuery query);
     }
     }
 
 

+ 1 - 2
MediaBrowser.Controller/Entities/InternalItemsQuery.cs

@@ -19,8 +19,6 @@ namespace MediaBrowser.Controller.Entities
 
 
         public User User { get; set; }
         public User User { get; set; }
 
 
-        public Func<BaseItem, bool> Filter { get; set; }
-
         public bool? IsFolder { get; set; }
         public bool? IsFolder { get; set; }
         public bool? IsFavorite { get; set; }
         public bool? IsFavorite { get; set; }
         public bool? IsFavoriteOrLiked { get; set; }
         public bool? IsFavoriteOrLiked { get; set; }
@@ -138,6 +136,7 @@ namespace MediaBrowser.Controller.Entities
 
 
         public bool GroupByPresentationUniqueKey { get; set; }
         public bool GroupByPresentationUniqueKey { get; set; }
         public bool EnableTotalRecordCount { get; set; }
         public bool EnableTotalRecordCount { get; set; }
+        public bool ForceDirect { get; set; }
 
 
         public InternalItemsQuery()
         public InternalItemsQuery()
         {
         {

+ 17 - 17
MediaBrowser.Controller/Entities/TV/Season.cs

@@ -134,7 +134,7 @@ namespace MediaBrowser.Controller.Entities.TV
 
 
             if (!result)
             if (!result)
             {
             {
-                if (!IsMissingSeason.HasValue)
+                if (!IsVirtualItem.HasValue)
                 {
                 {
                     return true;
                     return true;
                 }
                 }
@@ -144,18 +144,23 @@ namespace MediaBrowser.Controller.Entities.TV
         }
         }
 
 
         [IgnoreDataMember]
         [IgnoreDataMember]
-        public bool? IsMissingSeason { get; set; }
+        public bool? IsVirtualItem { get; set; }
 
 
         [IgnoreDataMember]
         [IgnoreDataMember]
-        public bool IsVirtualUnaired
+        public bool IsMissingSeason
         {
         {
-            get { return LocationType == LocationType.Virtual && IsUnaired; }
+            get { return (IsVirtualItem ?? DetectIsVirtualItem()) && !IsUnaired; }
         }
         }
 
 
         [IgnoreDataMember]
         [IgnoreDataMember]
-        public bool IsMissingOrVirtualUnaired
+        public bool IsVirtualUnaired
+        {
+            get { return (IsVirtualItem ?? DetectIsVirtualItem()) && IsUnaired; }
+        }
+
+        private bool DetectIsVirtualItem()
         {
         {
-            get { return (IsMissingSeason ?? false) || (LocationType == LocationType.Virtual && IsUnaired); }
+            return LocationType == LocationType.Virtual && GetEpisodes().All(i => i.LocationType == LocationType.Virtual);
         }
         }
 
 
         [IgnoreDataMember]
         [IgnoreDataMember]
@@ -319,19 +324,14 @@ namespace MediaBrowser.Controller.Entities.TV
         {
         {
             var hasChanges = base.BeforeMetadataRefresh();
             var hasChanges = base.BeforeMetadataRefresh();
 
 
-            var locationType = LocationType;
-
-            if (locationType == LocationType.FileSystem || locationType == LocationType.Offline)
+            if (!IndexNumber.HasValue && !string.IsNullOrEmpty(Path))
             {
             {
-                if (!IndexNumber.HasValue && !string.IsNullOrEmpty(Path))
-                {
-                    IndexNumber = IndexNumber ?? LibraryManager.GetSeasonNumberFromPath(Path);
+                IndexNumber = IndexNumber ?? LibraryManager.GetSeasonNumberFromPath(Path);
 
 
-                    // If a change was made record it
-                    if (IndexNumber.HasValue)
-                    {
-                        hasChanges = true;
-                    }
+                // If a change was made record it
+                if (IndexNumber.HasValue)
+                {
+                    hasChanges = true;
                 }
                 }
             }
             }
 
 

+ 12 - 15
MediaBrowser.Controller/Entities/TV/Series.cs

@@ -238,20 +238,13 @@ namespace MediaBrowser.Controller.Entities.TV
                 seasons = LibraryManager.Sort(base.GetChildren(user, true), user, new[] { ItemSortBy.SortName }, SortOrder.Ascending).OfType<Season>();
                 seasons = LibraryManager.Sort(base.GetChildren(user, true), user, new[] { ItemSortBy.SortName }, SortOrder.Ascending).OfType<Season>();
             }
             }
 
 
-            if (!includeMissingSeasons && !includeVirtualUnaired)
+            if (!includeMissingSeasons)
             {
             {
-                seasons = seasons.Where(i => !i.IsMissingOrVirtualUnaired);
+                seasons = seasons.Where(i => !(i.IsMissingSeason));
             }
             }
-            else
+            if (!includeVirtualUnaired)
             {
             {
-                if (!includeMissingSeasons)
-                {
-                    seasons = seasons.Where(i => !(i.IsMissingSeason ?? false));
-                }
-                if (!includeVirtualUnaired)
-                {
-                    seasons = seasons.Where(i => !i.IsVirtualUnaired);
-                }
+                seasons = seasons.Where(i => !i.IsVirtualUnaired);
             }
             }
 
 
             return seasons;
             return seasons;
@@ -381,14 +374,18 @@ namespace MediaBrowser.Controller.Entities.TV
                 }
                 }
                 else
                 else
                 {
                 {
-                    episodes = GetRecursiveChildren(user, i => i is Episode)
-                        .Cast<Episode>();
+                    episodes = GetRecursiveChildren(user, new InternalItemsQuery(user)
+                    {
+                        IncludeItemTypes = new[] { typeof(Episode).Name }
+                    }).Cast<Episode>();
                 }
                 }
             }
             }
             else
             else
             {
             {
-                episodes = GetRecursiveChildren(user, i => i is Episode)
-                    .Cast<Episode>();
+                episodes = GetRecursiveChildren(user, new InternalItemsQuery(user)
+                {
+                    IncludeItemTypes = new[] { typeof(Episode).Name }
+                }).Cast<Episode>();
             }
             }
 
 
             episodes = FilterEpisodesBySeason(episodes, seasonNumber, DisplaySpecialsWithSeasons);
             episodes = FilterEpisodesBySeason(episodes, seasonNumber, DisplaySpecialsWithSeasons);

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

@@ -305,14 +305,7 @@ namespace MediaBrowser.Controller.Entities
 
 
         public bool IsFolderGrouped(Guid id)
         public bool IsFolderGrouped(Guid id)
         {
         {
-            var config = Configuration;
-
-            if (config.ExcludeFoldersFromGrouping != null)
-            {
-                return !config.ExcludeFoldersFromGrouping.Select(i => new Guid(i)).Contains(id);
-            }
-
-            return config.GroupedFolders.Select(i => new Guid(i)).Contains(id);
+            return Configuration.GroupedFolders.Select(i => new Guid(i)).Contains(id);
         }
         }
 
 
         [IgnoreDataMember]
         [IgnoreDataMember]

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

@@ -64,15 +64,6 @@ namespace MediaBrowser.Controller.Entities
             return list;
             return list;
         }
         }
 
 
-        /// <summary>
-        /// Get the children of this folder from the actual file system
-        /// </summary>
-        /// <returns>IEnumerable{BaseItem}.</returns>
-        protected override IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService)
-        {
-            return base.GetNonCachedChildren(directoryService);
-        }
-
         public override bool BeforeMetadataRefresh()
         public override bool BeforeMetadataRefresh()
         {
         {
             var hasChanges = base.BeforeMetadataRefresh();
             var hasChanges = base.BeforeMetadataRefresh();

+ 7 - 4
MediaBrowser.Controller/Entities/UserView.cs

@@ -66,7 +66,8 @@ namespace MediaBrowser.Controller.Entities
         {
         {
             var result = GetItems(new InternalItemsQuery
             var result = GetItems(new InternalItemsQuery
             {
             {
-                User = user
+                User = user,
+                EnableTotalRecordCount = false
 
 
             }).Result;
             }).Result;
 
 
@@ -83,17 +84,19 @@ namespace MediaBrowser.Controller.Entities
             return true;
             return true;
         }
         }
 
 
-        public override IEnumerable<BaseItem> GetRecursiveChildren(User user, Func<BaseItem, bool> filter)
+        public override IEnumerable<BaseItem> GetRecursiveChildren(User user, InternalItemsQuery query)
         {
         {
             var result = GetItems(new InternalItemsQuery
             var result = GetItems(new InternalItemsQuery
             {
             {
                 User = user,
                 User = user,
                 Recursive = true,
                 Recursive = true,
-                Filter = filter
+                EnableTotalRecordCount = false,
+
+                ForceDirect = true
 
 
             }).Result;
             }).Result;
 
 
-            return result.Items;
+            return result.Items.Where(i => UserViewBuilder.FilterItem(i, query));
         }
         }
 
 
         protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user)
         protected override IEnumerable<BaseItem> GetEligibleChildrenForRecursiveChildren(User user)

+ 32 - 40
MediaBrowser.Controller/Entities/UserViewBuilder.cs

@@ -128,7 +128,11 @@ namespace MediaBrowser.Controller.Entities
                     {
                     {
                         if (query.Recursive)
                         if (query.Recursive)
                         {
                         {
-                            return GetResult(queryParent.GetRecursiveChildren(user, true), queryParent, query);
+                            query.Recursive = true;
+                            query.ParentId = queryParent.Id;
+                            query.SetUser(user);
+
+                            return _libraryManager.GetItemsResult(query);
                         }
                         }
                         return GetResult(queryParent.GetChildren(user, true), queryParent, query);
                         return GetResult(queryParent.GetChildren(user, true), queryParent, query);
                     }
                     }
@@ -251,7 +255,6 @@ namespace MediaBrowser.Controller.Entities
             if (query.Recursive)
             if (query.Recursive)
             {
             {
                 query.Recursive = true;
                 query.Recursive = true;
-                query.ParentId = parent.Id;
                 query.SetUser(user);
                 query.SetUser(user);
 
 
                 if (query.IncludeItemTypes.Length == 0)
                 if (query.IncludeItemTypes.Length == 0)
@@ -259,7 +262,7 @@ namespace MediaBrowser.Controller.Entities
                     query.IncludeItemTypes = new[] { typeof(MusicArtist).Name, typeof(MusicAlbum).Name, typeof(Audio.Audio).Name, typeof(MusicVideo).Name };
                     query.IncludeItemTypes = new[] { typeof(MusicArtist).Name, typeof(MusicAlbum).Name, typeof(Audio.Audio).Name, typeof(MusicVideo).Name };
                 }
                 }
 
 
-                return _libraryManager.GetItemsResult(query);
+                return parent.QueryRecursive(query);
             }
             }
 
 
             var list = new List<BaseItem>();
             var list = new List<BaseItem>();
@@ -329,9 +332,13 @@ namespace MediaBrowser.Controller.Entities
 
 
         private QueryResult<BaseItem> GetMusicAlbumArtists(Folder parent, User user, InternalItemsQuery query)
         private QueryResult<BaseItem> GetMusicAlbumArtists(Folder parent, User user, InternalItemsQuery query)
         {
         {
-            var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Music, CollectionType.MusicVideos })
-                .Where(i => !i.IsFolder)
-                .OfType<IHasAlbumArtist>();
+            var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
+            {
+                Recursive = true,
+                ParentId = parent.Id,
+                IncludeItemTypes = new[] { typeof(Audio.Audio).Name }
+
+            }).Cast<IHasAlbumArtist>();
 
 
             var artists = _libraryManager.GetAlbumArtists(items);
             var artists = _libraryManager.GetAlbumArtists(items);
 
 
@@ -340,9 +347,13 @@ namespace MediaBrowser.Controller.Entities
 
 
         private QueryResult<BaseItem> GetMusicArtists(Folder parent, User user, InternalItemsQuery query)
         private QueryResult<BaseItem> GetMusicArtists(Folder parent, User user, InternalItemsQuery query)
         {
         {
-            var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Music, CollectionType.MusicVideos })
-                .Where(i => !i.IsFolder)
-                .OfType<IHasArtist>();
+            var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
+            {
+                Recursive = true,
+                ParentId = parent.Id,
+                IncludeItemTypes = new[] { typeof(Audio.Audio).Name, typeof(MusicVideo).Name }
+
+            }).Cast<IHasArtist>();
 
 
             var artists = _libraryManager.GetArtists(items);
             var artists = _libraryManager.GetArtists(items);
 
 
@@ -351,9 +362,13 @@ namespace MediaBrowser.Controller.Entities
 
 
         private QueryResult<BaseItem> GetFavoriteArtists(Folder parent, User user, InternalItemsQuery query)
         private QueryResult<BaseItem> GetFavoriteArtists(Folder parent, User user, InternalItemsQuery query)
         {
         {
-            var items = GetRecursiveChildren(parent, user, new[] { CollectionType.Music, CollectionType.MusicVideos })
-                .Where(i => !i.IsFolder)
-                .OfType<IHasAlbumArtist>();
+            var items = _libraryManager.GetItemList(new InternalItemsQuery(user)
+            {
+                Recursive = true,
+                ParentId = parent.Id,
+                IncludeItemTypes = new[] { typeof(Audio.Audio).Name }
+
+            }).Cast<IHasAlbumArtist>();
 
 
             var artists = _libraryManager.GetAlbumArtists(items).Where(i => _userDataManager.GetUserData(user, i).IsFavorite);
             var artists = _libraryManager.GetAlbumArtists(items).Where(i => _userDataManager.GetUserData(user, i).IsFavorite);
 
 
@@ -448,7 +463,6 @@ namespace MediaBrowser.Controller.Entities
             if (query.Recursive)
             if (query.Recursive)
             {
             {
                 query.Recursive = true;
                 query.Recursive = true;
-                query.ParentId = parent.Id;
                 query.SetUser(user);
                 query.SetUser(user);
 
 
                 if (query.IncludeItemTypes.Length == 0)
                 if (query.IncludeItemTypes.Length == 0)
@@ -456,7 +470,7 @@ namespace MediaBrowser.Controller.Entities
                     query.IncludeItemTypes = new[] { typeof(Movie).Name, typeof(BoxSet).Name };
                     query.IncludeItemTypes = new[] { typeof(Movie).Name, typeof(BoxSet).Name };
                 }
                 }
 
 
-                return _libraryManager.GetItemsResult(query);
+                return parent.QueryRecursive(query);
             }
             }
 
 
             var list = new List<BaseItem>();
             var list = new List<BaseItem>();
@@ -613,7 +627,6 @@ namespace MediaBrowser.Controller.Entities
             if (query.Recursive)
             if (query.Recursive)
             {
             {
                 query.Recursive = true;
                 query.Recursive = true;
-                query.ParentId = parent.Id;
                 query.SetUser(user);
                 query.SetUser(user);
 
 
                 if (query.IncludeItemTypes.Length == 0)
                 if (query.IncludeItemTypes.Length == 0)
@@ -621,7 +634,7 @@ namespace MediaBrowser.Controller.Entities
                     query.IncludeItemTypes = new[] { typeof(Series).Name, typeof(Season).Name, typeof(Episode).Name };
                     query.IncludeItemTypes = new[] { typeof(Series).Name, typeof(Season).Name, typeof(Episode).Name };
                 }
                 }
 
 
-                return _libraryManager.GetItemsResult(query);
+                return parent.QueryRecursive(query);
             }
             }
 
 
             var list = new List<BaseItem>();
             var list = new List<BaseItem>();
@@ -756,9 +769,9 @@ namespace MediaBrowser.Controller.Entities
             return PostFilterAndSort(items, queryParent, null, query, _libraryManager);
             return PostFilterAndSort(items, queryParent, null, query, _libraryManager);
         }
         }
 
 
-        public bool FilterItem(BaseItem item, InternalItemsQuery query)
+        public static bool FilterItem(BaseItem item, InternalItemsQuery query)
         {
         {
-            return Filter(item, query.User, query, _userDataManager, _libraryManager);
+            return Filter(item, query.User, query, BaseItem.UserDataManager, BaseItem.LibraryManager);
         }
         }
 
 
         private QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items,
         private QueryResult<BaseItem> PostFilterAndSort(IEnumerable<BaseItem> items,
@@ -1121,22 +1134,6 @@ namespace MediaBrowser.Controller.Entities
             bool? isVirtualUnaired,
             bool? isVirtualUnaired,
             bool? isUnaired)
             bool? isUnaired)
         {
         {
-            if (isMissing.HasValue && isVirtualUnaired.HasValue)
-            {
-                if (!isMissing.Value && !isVirtualUnaired.Value)
-                {
-                    return items.Where(i =>
-                    {
-                        var e = i as Season;
-                        if (e != null)
-                        {
-                            return !e.IsMissingOrVirtualUnaired;
-                        }
-                        return true;
-                    });
-                }
-            }
-
             if (isMissing.HasValue)
             if (isMissing.HasValue)
             {
             {
                 var val = isMissing.Value;
                 var val = isMissing.Value;
@@ -1145,7 +1142,7 @@ namespace MediaBrowser.Controller.Entities
                     var e = i as Season;
                     var e = i as Season;
                     if (e != null)
                     if (e != null)
                     {
                     {
-                        return (e.IsMissingSeason ?? false) == val;
+                        return (e.IsMissingSeason) == val;
                     }
                     }
                     return true;
                     return true;
                 });
                 });
@@ -1277,11 +1274,6 @@ namespace MediaBrowser.Controller.Entities
                 return false;
                 return false;
             }
             }
 
 
-            if (query.Filter != null && !query.Filter(item))
-            {
-                return false;
-            }
-
             UserItemData userData = null;
             UserItemData userData = null;
 
 
             if (query.IsLiked.HasValue)
             if (query.IsLiked.HasValue)

+ 8 - 4
MediaBrowser.Controller/Playlists/Playlist.cs

@@ -63,13 +63,13 @@ namespace MediaBrowser.Controller.Playlists
             return GetPlayableItems(user).Result;
             return GetPlayableItems(user).Result;
         }
         }
 
 
-        public override IEnumerable<BaseItem> GetRecursiveChildren(User user, Func<BaseItem, bool> filter)
+        public override IEnumerable<BaseItem> GetRecursiveChildren(User user, InternalItemsQuery query)
         {
         {
             var items = GetPlayableItems(user).Result;
             var items = GetPlayableItems(user).Result;
 
 
-            if (filter != null)
+            if (query != null)
             {
             {
-                items = items.Where(filter);
+                items = items.Where(i => UserViewBuilder.FilterItem(i, query));
             }
             }
 
 
             return items;
             return items;
@@ -129,7 +129,11 @@ namespace MediaBrowser.Controller.Playlists
 
 
                 var items = user == null
                 var items = user == null
                     ? LibraryManager.RootFolder.GetRecursiveChildren(filter)
                     ? LibraryManager.RootFolder.GetRecursiveChildren(filter)
-                    : user.RootFolder.GetRecursiveChildren(user, filter);
+                    : user.RootFolder.GetRecursiveChildren(user, new InternalItemsQuery(user)
+                    {
+                        IncludeItemTypes = new[] { typeof(Audio).Name },
+                        ArtistNames = new[] { musicArtist.Name }
+                    });
 
 
                 return LibraryManager.Sort(items, user, new[] { ItemSortBy.AlbumArtist, ItemSortBy.Album, ItemSortBy.SortName }, SortOrder.Ascending);
                 return LibraryManager.Sort(items, user, new[] { ItemSortBy.AlbumArtist, ItemSortBy.Album, ItemSortBy.SortName }, SortOrder.Ascending);
             }
             }

+ 6 - 27
MediaBrowser.Dlna/ContentDirectory/ControlHandler.cs

@@ -401,10 +401,10 @@ namespace MediaBrowser.Dlna.ContentDirectory
                 SortOrder = sort.SortOrder,
                 SortOrder = sort.SortOrder,
                 User = user,
                 User = user,
                 Recursive = true,
                 Recursive = true,
-                Filter = FilterUnsupportedContent,
+                IsMissing = false,
+                ExcludeItemTypes = new[] { typeof(Game).Name, typeof(Book).Name },
                 IsFolder = isFolder,
                 IsFolder = isFolder,
                 MediaTypes = mediaTypes.ToArray()
                 MediaTypes = mediaTypes.ToArray()
-
             });
             });
         }
         }
 
 
@@ -461,8 +461,10 @@ namespace MediaBrowser.Dlna.ContentDirectory
                 SortBy = sortOrders.ToArray(),
                 SortBy = sortOrders.ToArray(),
                 SortOrder = sort.SortOrder,
                 SortOrder = sort.SortOrder,
                 User = user,
                 User = user,
-                Filter = FilterUnsupportedContent,
-                PresetViews = new[] { CollectionType.Movies, CollectionType.TvShows, CollectionType.Music }
+                IsMissing = false,
+                PresetViews = new[] { CollectionType.Movies, CollectionType.TvShows, CollectionType.Music },
+                ExcludeItemTypes = new[] { typeof(Game).Name, typeof(Book).Name },
+                IsPlaceHolder = false
 
 
             }).ConfigureAwait(false);
             }).ConfigureAwait(false);
 
 
@@ -579,29 +581,6 @@ namespace MediaBrowser.Dlna.ContentDirectory
             });
             });
         }
         }
 
 
-        private bool FilterUnsupportedContent(BaseItem i)
-        {
-            // Unplayable
-            if (i.LocationType == LocationType.Virtual && !i.IsFolder)
-            {
-                return false;
-            }
-
-            // Unplayable
-            var supportsPlaceHolder = i as ISupportsPlaceHolders;
-            if (supportsPlaceHolder != null && supportsPlaceHolder.IsPlaceHolder)
-            {
-                return false;
-            }
-
-            if (i is Game || i is Book)
-            {
-                //return false;
-            }
-
-            return true;
-        }
-
         private ServerItem GetItemFromObjectId(string id, User user)
         private ServerItem GetItemFromObjectId(string id, User user)
         {
         {
             return DidlBuilder.IsIdRoot(id)
             return DidlBuilder.IsIdRoot(id)

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

@@ -215,6 +215,9 @@ namespace MediaBrowser.Model.Configuration
         {
         {
             Migrations = new string[] { };
             Migrations = new string[] { };
 
 
+            EnableLocalizedGuids = true;
+            EnableCustomPathSubFolders = true;
+
             ImageSavingConvention = ImageSavingConvention.Compatible;
             ImageSavingConvention = ImageSavingConvention.Compatible;
             PublicPort = 8096;
             PublicPort = 8096;
             PublicHttpsPort = 8920;
             PublicHttpsPort = 8920;

+ 11 - 7
MediaBrowser.Model/Entities/MediaStream.cs

@@ -1,8 +1,8 @@
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Extensions;
 using MediaBrowser.Model.Extensions;
 using System.Diagnostics;
 using System.Diagnostics;
+using MediaBrowser.Model.MediaInfo;
 
 
 namespace MediaBrowser.Model.Entities
 namespace MediaBrowser.Model.Entities
 {
 {
@@ -53,18 +53,22 @@ namespace MediaBrowser.Model.Entities
 
 
                     if (!string.IsNullOrEmpty(Language))
                     if (!string.IsNullOrEmpty(Language))
                     {
                     {
-                        attributes.Add(Language);
+                        attributes.Add(StringHelper.FirstToUpper(Language));
                     }
                     }
                     if (!string.IsNullOrEmpty(Codec) && !StringHelper.EqualsIgnoreCase(Codec, "dca"))
                     if (!string.IsNullOrEmpty(Codec) && !StringHelper.EqualsIgnoreCase(Codec, "dca"))
                     {
                     {
-                        attributes.Add(Codec);
-                    }
-                    if (!string.IsNullOrEmpty(Profile) && !StringHelper.EqualsIgnoreCase(Profile, "lc"))
+                        attributes.Add(AudioCodec.GetFriendlyName(Codec));
+                    } 
+                    else if (!string.IsNullOrEmpty(Profile) && !StringHelper.EqualsIgnoreCase(Profile, "lc"))
                     {
                     {
                         attributes.Add(Profile);
                         attributes.Add(Profile);
                     }
                     }
 
 
-                    if (Channels.HasValue)
+                    if (!string.IsNullOrEmpty(ChannelLayout))
+                    {
+                        attributes.Add(ChannelLayout);
+                    }
+                    else if (Channels.HasValue)
                     {
                     {
                         attributes.Add(StringHelper.ToStringCultureInvariant(Channels.Value) + " ch");
                         attributes.Add(StringHelper.ToStringCultureInvariant(Channels.Value) + " ch");
                     }
                     }

+ 5 - 0
MediaBrowser.Model/Extensions/StringHelper.cs

@@ -125,5 +125,10 @@ namespace MediaBrowser.Model.Extensions
 
 
             return sb.ToString();
             return sb.ToString();
         }
         }
+
+        public static string FirstToUpper(this string str)
+        {
+            return string.IsNullOrEmpty(str) ? "" : str.Substring(0, 1).ToUpper() + str.Substring(1);
+        }
     }
     }
 }
 }

+ 3 - 0
MediaBrowser.Model/LiveTv/LiveTvOptions.cs

@@ -20,12 +20,15 @@ namespace MediaBrowser.Model.LiveTv
         public int PrePaddingSeconds { get; set; }
         public int PrePaddingSeconds { get; set; }
         public int PostPaddingSeconds { get; set; }
         public int PostPaddingSeconds { get; set; }
 
 
+        public string[] MediaLocationsCreated { get; set; }
+
         public LiveTvOptions()
         public LiveTvOptions()
         {
         {
             EnableMovieProviders = true;
             EnableMovieProviders = true;
             EnableRecordingSubfolders = true;
             EnableRecordingSubfolders = true;
             TunerHosts = new List<TunerHostInfo>();
             TunerHosts = new List<TunerHostInfo>();
             ListingProviders = new List<ListingsProviderInfo>();
             ListingProviders = new List<ListingsProviderInfo>();
+            MediaLocationsCreated = new string[] { };
         }
         }
     }
     }
 
 

+ 17 - 0
MediaBrowser.Model/MediaInfo/AudioCodec.cs

@@ -5,5 +5,22 @@
         public const string AAC = "aac";
         public const string AAC = "aac";
         public const string MP3 = "mp3";
         public const string MP3 = "mp3";
         public const string AC3 = "ac3";
         public const string AC3 = "ac3";
+
+        public static string GetFriendlyName(string codec)
+        {
+            if (string.IsNullOrEmpty(codec)) return "";
+
+            switch (codec.ToLower())
+            {
+                case "ac3":
+                    return "Dolby Digital";
+                case "eac3":
+                    return "Dolby Digital+";
+                case "dca":
+                    return "DTS";
+                default:
+                    return codec.ToUpper();
+            }
+        }
     }
     }
 }
 }

+ 4 - 0
MediaBrowser.Model/Querying/ItemQuery.cs

@@ -288,6 +288,8 @@ namespace MediaBrowser.Model.Querying
         [Obsolete]
         [Obsolete]
         public string Person { get; set; }
         public string Person { get; set; }
 
 
+        public bool EnableTotalRecordCount { get; set; }
+
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="ItemQuery" /> class.
         /// Initializes a new instance of the <see cref="ItemQuery" /> class.
         /// </summary>
         /// </summary>
@@ -306,6 +308,8 @@ namespace MediaBrowser.Model.Querying
 
 
             VideoTypes = new VideoType[] { };
             VideoTypes = new VideoType[] { };
 
 
+            EnableTotalRecordCount = true;
+
             Artists = new string[] { };
             Artists = new string[] { };
             Studios = new string[] { };
             Studios = new string[] { };
             
             

+ 9 - 14
MediaBrowser.Providers/Manager/MetadataService.cs

@@ -145,11 +145,15 @@ namespace MediaBrowser.Providers.Manager
 
 
             bool hasRefreshedMetadata = true;
             bool hasRefreshedMetadata = true;
             bool hasRefreshedImages = true;
             bool hasRefreshedImages = true;
+            var requiresRefresh = false;
 
 
             // Next run metadata providers
             // Next run metadata providers
             if (refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None)
             if (refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None)
             {
             {
-                var providers = GetProviders(item, refreshResult, refreshOptions)
+                // TODO: If this returns true, should we instead just change metadata refresh mode to Full?
+                requiresRefresh = item.RequiresRefresh();
+
+                var providers = GetProviders(item, refreshResult, refreshOptions, requiresRefresh)
                     .ToList();
                     .ToList();
 
 
                 var dateLastRefresh = EnableDateLastRefreshed(item)
                 var dateLastRefresh = EnableDateLastRefreshed(item)
@@ -217,11 +221,11 @@ namespace MediaBrowser.Providers.Manager
 
 
             var isFirstRefresh = GetLastRefreshDate(item) == default(DateTime);
             var isFirstRefresh = GetLastRefreshDate(item) == default(DateTime);
 
 
-            var beforeSaveResult = await BeforeSave(itemOfType, isFirstRefresh || refreshOptions.ReplaceAllMetadata || refreshOptions.MetadataRefreshMode == MetadataRefreshMode.FullRefresh, updateType).ConfigureAwait(false);
+            var beforeSaveResult = await BeforeSave(itemOfType, isFirstRefresh || refreshOptions.ReplaceAllMetadata || refreshOptions.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || requiresRefresh, updateType).ConfigureAwait(false);
             updateType = updateType | beforeSaveResult;
             updateType = updateType | beforeSaveResult;
 
 
             // Save if changes were made, or it's never been saved before
             // Save if changes were made, or it's never been saved before
-            if (refreshOptions.ForceSave || updateType > ItemUpdateType.None || isFirstRefresh || refreshOptions.ReplaceAllMetadata)
+            if (refreshOptions.ForceSave || updateType > ItemUpdateType.None || isFirstRefresh || refreshOptions.ReplaceAllMetadata || requiresRefresh)
             {
             {
                 // If any of these properties are set then make sure the updateType is not None, just to force everything to save
                 // If any of these properties are set then make sure the updateType is not None, just to force everything to save
                 if (refreshOptions.ForceSave || refreshOptions.ReplaceAllMetadata)
                 if (refreshOptions.ForceSave || refreshOptions.ReplaceAllMetadata)
@@ -461,11 +465,8 @@ namespace MediaBrowser.Providers.Manager
         /// <summary>
         /// <summary>
         /// Gets the providers.
         /// Gets the providers.
         /// </summary>
         /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="status">The status.</param>
-        /// <param name="options">The options.</param>
         /// <returns>IEnumerable{`0}.</returns>
         /// <returns>IEnumerable{`0}.</returns>
-        protected IEnumerable<IMetadataProvider> GetProviders(IHasMetadata item, MetadataStatus status, MetadataRefreshOptions options)
+        protected IEnumerable<IMetadataProvider> GetProviders(IHasMetadata item, MetadataStatus status, MetadataRefreshOptions options, bool requiresRefresh)
         {
         {
             // Get providers to refresh
             // Get providers to refresh
             var providers = ((ProviderManager)ProviderManager).GetMetadataProviders<TItemType>(item).ToList();
             var providers = ((ProviderManager)ProviderManager).GetMetadataProviders<TItemType>(item).ToList();
@@ -475,7 +476,7 @@ namespace MediaBrowser.Providers.Manager
                 : status.DateLastMetadataRefresh ?? default(DateTime);
                 : status.DateLastMetadataRefresh ?? default(DateTime);
 
 
             // Run all if either of these flags are true
             // Run all if either of these flags are true
-            var runAllProviders = options.ReplaceAllMetadata || options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || dateLastRefresh == default(DateTime) || item.RequiresRefresh();
+            var runAllProviders = options.ReplaceAllMetadata || options.MetadataRefreshMode == MetadataRefreshMode.FullRefresh || dateLastRefresh == default(DateTime) || requiresRefresh;
 
 
             if (!runAllProviders)
             if (!runAllProviders)
             {
             {
@@ -668,12 +669,6 @@ namespace MediaBrowser.Providers.Manager
 
 
                     // If a local provider fails, consider that a failure
                     // If a local provider fails, consider that a failure
                     refreshResult.ErrorMessage = ex.Message;
                     refreshResult.ErrorMessage = ex.Message;
-
-                    if (options.MetadataRefreshMode != MetadataRefreshMode.FullRefresh)
-                    {
-                        // If the local provider fails don't continue with remote providers because the user's saved metadata could be lost
-                        //return refreshResult;
-                    }
                 }
                 }
             }
             }
 
 

+ 5 - 7
MediaBrowser.Providers/TV/DummySeasonProvider.cs

@@ -69,7 +69,7 @@ namespace MediaBrowser.Providers.TV
 
 
                 if (!hasSeason)
                 if (!hasSeason)
                 {
                 {
-                    await AddSeason(series, seasonNumber, cancellationToken).ConfigureAwait(false);
+                    await AddSeason(series, seasonNumber, false, cancellationToken).ConfigureAwait(false);
 
 
                     hasChanges = true;
                     hasChanges = true;
                 }
                 }
@@ -83,7 +83,7 @@ namespace MediaBrowser.Providers.TV
 
 
                 if (!hasSeason)
                 if (!hasSeason)
                 {
                 {
-                    await AddSeason(series, null, cancellationToken).ConfigureAwait(false);
+                    await AddSeason(series, null, false, cancellationToken).ConfigureAwait(false);
 
 
                     hasChanges = true;
                     hasChanges = true;
                 }
                 }
@@ -95,12 +95,9 @@ namespace MediaBrowser.Providers.TV
         /// <summary>
         /// <summary>
         /// Adds the season.
         /// Adds the season.
         /// </summary>
         /// </summary>
-        /// <param name="series">The series.</param>
-        /// <param name="seasonNumber">The season number.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task{Season}.</returns>
         public async Task<Season> AddSeason(Series series,
         public async Task<Season> AddSeason(Series series,
             int? seasonNumber,
             int? seasonNumber,
+            bool isVirtualItem,
             CancellationToken cancellationToken)
             CancellationToken cancellationToken)
         {
         {
             var seasonName = seasonNumber == 0 ?
             var seasonName = seasonNumber == 0 ?
@@ -113,7 +110,8 @@ namespace MediaBrowser.Providers.TV
             {
             {
                 Name = seasonName,
                 Name = seasonName,
                 IndexNumber = seasonNumber,
                 IndexNumber = seasonNumber,
-                Id = _libraryManager.GetNewItemId((series.Id + (seasonNumber ?? -1).ToString(_usCulture) + seasonName), typeof(Season))
+                Id = _libraryManager.GetNewItemId((series.Id + (seasonNumber ?? -1).ToString(_usCulture) + seasonName), typeof(Season)),
+                IsVirtualItem = isVirtualItem
             };
             };
 
 
             season.SetParent(series);
             season.SetParent(series);

+ 1 - 1
MediaBrowser.Providers/TV/MissingEpisodeProvider.cs

@@ -418,7 +418,7 @@ namespace MediaBrowser.Providers.TV
             if (season == null)
             if (season == null)
             {
             {
                 var provider = new DummySeasonProvider(_config, _logger, _localization, _libraryManager, _fileSystem);
                 var provider = new DummySeasonProvider(_config, _logger, _localization, _libraryManager, _fileSystem);
-                season = await provider.AddSeason(series, seasonNumber, cancellationToken).ConfigureAwait(false);
+                season = await provider.AddSeason(series, seasonNumber, true, cancellationToken).ConfigureAwait(false);
             }
             }
 
 
             var name = string.Format("Episode {0}", episodeNumber.ToString(_usCulture));
             var name = string.Format("Episode {0}", episodeNumber.ToString(_usCulture));

+ 5 - 5
MediaBrowser.Providers/TV/SeasonMetadataService.cs

@@ -36,7 +36,7 @@ namespace MediaBrowser.Providers.TV
             {
             {
                 var episodes = item.GetEpisodes().ToList();
                 var episodes = item.GetEpisodes().ToList();
                 updateType |= SavePremiereDate(item, episodes);
                 updateType |= SavePremiereDate(item, episodes);
-                updateType |= SaveIsMissing(item, episodes);
+                updateType |= SaveIsVirtualItem(item, episodes);
             }
             }
 
 
             return updateType;
             return updateType;
@@ -67,13 +67,13 @@ namespace MediaBrowser.Providers.TV
             return ItemUpdateType.None;
             return ItemUpdateType.None;
         }
         }
 
 
-        private ItemUpdateType SaveIsMissing(Season item, List<Episode> episodes)
+        private ItemUpdateType SaveIsVirtualItem(Season item, List<Episode> episodes)
         {
         {
-            var isMissing = item.LocationType == LocationType.Virtual && episodes.All(i => i.IsMissingEpisode);
+            var isVirtualItem = item.LocationType == LocationType.Virtual && (episodes.Count == 0 || episodes.All(i => i.LocationType == LocationType.Virtual));
 
 
-            if (item.IsMissingSeason != isMissing)
+            if (item.IsVirtualItem != isVirtualItem)
             {
             {
-                item.IsMissingSeason = isMissing;
+                item.IsVirtualItem = isVirtualItem;
                 return ItemUpdateType.MetadataEdit;
                 return ItemUpdateType.MetadataEdit;
             }
             }
 
 

+ 6 - 2
MediaBrowser.Server.Implementations/Library/LibraryManager.cs

@@ -1448,8 +1448,12 @@ namespace MediaBrowser.Server.Implementations.Library
                 // Handle grouping
                 // Handle grouping
                 if (user != null && !string.IsNullOrWhiteSpace(view.ViewType) && UserView.IsEligibleForGrouping(view.ViewType))
                 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 user.RootFolder
+                        .GetChildren(user, true)
+                        .OfType<CollectionFolder>()
+                        .Where(i => string.IsNullOrWhiteSpace(i.CollectionType) || string.Equals(i.CollectionType, view.ViewType, StringComparison.OrdinalIgnoreCase))
+                        .Where(i => user.IsFolderGrouped(i.Id))
+                        .SelectMany(i => GetTopParentsForQuery(i, user));
                 }
                 }
                 return new BaseItem[] { };
                 return new BaseItem[] { };
             }
             }

+ 16 - 4
MediaBrowser.Server.Implementations/Library/MusicManager.cs

@@ -30,7 +30,10 @@ namespace MediaBrowser.Server.Implementations.Library
         public IEnumerable<Audio> GetInstantMixFromArtist(MusicArtist artist, User user)
         public IEnumerable<Audio> GetInstantMixFromArtist(MusicArtist artist, User user)
         {
         {
             var genres = user.RootFolder
             var genres = user.RootFolder
-                .GetRecursiveChildren(user, i => i is Audio)
+                .GetRecursiveChildren(user, new InternalItemsQuery(user)
+                {
+                    IncludeItemTypes = new[] { typeof(Audio).Name }
+                })
                 .Cast<Audio>()
                 .Cast<Audio>()
                 .Where(i => i.HasAnyArtist(artist.Name))
                 .Where(i => i.HasAnyArtist(artist.Name))
                 .SelectMany(i => i.Genres)
                 .SelectMany(i => i.Genres)
@@ -43,7 +46,10 @@ namespace MediaBrowser.Server.Implementations.Library
         public IEnumerable<Audio> GetInstantMixFromAlbum(MusicAlbum item, User user)
         public IEnumerable<Audio> GetInstantMixFromAlbum(MusicAlbum item, User user)
         {
         {
             var genres = item
             var genres = item
-                .GetRecursiveChildren(user, i => i is Audio)
+                .GetRecursiveChildren(user, new InternalItemsQuery(user)
+                {
+                    IncludeItemTypes = new[] { typeof(Audio).Name }
+                })
                .Cast<Audio>()
                .Cast<Audio>()
                .SelectMany(i => i.Genres)
                .SelectMany(i => i.Genres)
                .Concat(item.Genres)
                .Concat(item.Genres)
@@ -55,7 +61,10 @@ namespace MediaBrowser.Server.Implementations.Library
         public IEnumerable<Audio> GetInstantMixFromFolder(Folder item, User user)
         public IEnumerable<Audio> GetInstantMixFromFolder(Folder item, User user)
         {
         {
             var genres = item
             var genres = item
-               .GetRecursiveChildren(user, i => i is Audio)
+               .GetRecursiveChildren(user, new InternalItemsQuery(user)
+               {
+                   IncludeItemTypes = new[] {typeof(Audio).Name}
+               })
                .Cast<Audio>()
                .Cast<Audio>()
                .SelectMany(i => i.Genres)
                .SelectMany(i => i.Genres)
                .Concat(item.Genres)
                .Concat(item.Genres)
@@ -67,7 +76,10 @@ namespace MediaBrowser.Server.Implementations.Library
         public IEnumerable<Audio> GetInstantMixFromPlaylist(Playlist item, User user)
         public IEnumerable<Audio> GetInstantMixFromPlaylist(Playlist item, User user)
         {
         {
             var genres = item
             var genres = item
-               .GetRecursiveChildren(user, i => i is Audio)
+               .GetRecursiveChildren(user, new InternalItemsQuery(user)
+               {
+                   IncludeItemTypes = new[] { typeof(Audio).Name }
+               })
                .Cast<Audio>()
                .Cast<Audio>()
                .SelectMany(i => i.Genres)
                .SelectMany(i => i.Genres)
                .Concat(item.Genres)
                .Concat(item.Genres)

+ 24 - 6
MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs

@@ -115,17 +115,13 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
         {
         {
             var recordingFolders = GetRecordingFolders();
             var recordingFolders = GetRecordingFolders();
 
 
-            var defaultRecordingPath = DefaultRecordingPath;
-            if (!recordingFolders.Any(i => i.Locations.Contains(defaultRecordingPath, StringComparer.OrdinalIgnoreCase)))
-            {
-                RemovePathFromLibrary(defaultRecordingPath);
-            }
-
             var virtualFolders = _libraryManager.GetVirtualFolders()
             var virtualFolders = _libraryManager.GetVirtualFolders()
                 .ToList();
                 .ToList();
 
 
             var allExistingPaths = virtualFolders.SelectMany(i => i.Locations).ToList();
             var allExistingPaths = virtualFolders.SelectMany(i => i.Locations).ToList();
 
 
+            var pathsAdded = new List<string>();
+
             foreach (var recordingFolder in recordingFolders)
             foreach (var recordingFolder in recordingFolders)
             {
             {
                 var pathsToCreate = recordingFolder.Locations
                 var pathsToCreate = recordingFolder.Locations
@@ -145,11 +141,33 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
                 {
                 {
                     _logger.ErrorException("Error creating virtual folder", ex);
                     _logger.ErrorException("Error creating virtual folder", ex);
                 }
                 }
+
+                pathsAdded.AddRange(pathsToCreate);
+            }
+
+            var config = GetConfiguration();
+
+            var pathsToRemove = config.MediaLocationsCreated
+                .Except(recordingFolders.SelectMany(i => i.Locations))
+                .ToList();
+
+            if (pathsAdded.Count > 0 || pathsToRemove.Count > 0)
+            {
+                pathsAdded.InsertRange(0, config.MediaLocationsCreated);
+                config.MediaLocationsCreated = pathsAdded.Except(pathsToRemove).Distinct(StringComparer.OrdinalIgnoreCase).ToArray();
+                _config.SaveConfiguration("livetv", config);
+            }
+
+            foreach (var path in pathsToRemove)
+            {
+                RemovePathFromLibrary(path);
             }
             }
         }
         }
 
 
         private void RemovePathFromLibrary(string path)
         private void RemovePathFromLibrary(string path)
         {
         {
+            _logger.Debug("Removing path from library: {0}", path);
+
             var requiresRefresh = false;
             var requiresRefresh = false;
             var virtualFolders = _libraryManager.GetVirtualFolders()
             var virtualFolders = _libraryManager.GetVirtualFolders()
                .ToList();
                .ToList();

+ 23 - 5
MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs

@@ -527,6 +527,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
         private async Task<LiveTvChannel> GetChannel(ChannelInfo channelInfo, string serviceName, Guid parentFolderId, CancellationToken cancellationToken)
         private async Task<LiveTvChannel> GetChannel(ChannelInfo channelInfo, string serviceName, Guid parentFolderId, CancellationToken cancellationToken)
         {
         {
             var isNew = false;
             var isNew = false;
+            var forceUpdate = false;
 
 
             var id = _tvDtoService.GetInternalChannelId(serviceName, channelInfo.Id);
             var id = _tvDtoService.GetInternalChannelId(serviceName, channelInfo.Id);
 
 
@@ -576,10 +577,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 if (!string.IsNullOrWhiteSpace(channelInfo.ImagePath))
                 if (!string.IsNullOrWhiteSpace(channelInfo.ImagePath))
                 {
                 {
                     item.SetImagePath(ImageType.Primary, channelInfo.ImagePath);
                     item.SetImagePath(ImageType.Primary, channelInfo.ImagePath);
+                    forceUpdate = true;
                 }
                 }
                 else if (!string.IsNullOrWhiteSpace(channelInfo.ImageUrl))
                 else if (!string.IsNullOrWhiteSpace(channelInfo.ImageUrl))
                 {
                 {
                     item.SetImagePath(ImageType.Primary, channelInfo.ImageUrl);
                     item.SetImagePath(ImageType.Primary, channelInfo.ImageUrl);
+                    forceUpdate = true;
                 }
                 }
             }
             }
 
 
@@ -588,9 +591,18 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 item.Name = channelInfo.Name;
                 item.Name = channelInfo.Name;
             }
             }
 
 
+            if (isNew)
+            {
+                await _libraryManager.CreateItem(item, cancellationToken).ConfigureAwait(false);
+            }
+            else if (forceUpdate)
+            {
+                await _libraryManager.UpdateItem(item, ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
+            }
+
             await item.RefreshMetadata(new MetadataRefreshOptions(_fileSystem)
             await item.RefreshMetadata(new MetadataRefreshOptions(_fileSystem)
             {
             {
-                ForceSave = isNew
+                ForceSave = isNew || forceUpdate
 
 
             }, cancellationToken);
             }, cancellationToken);
 
 
@@ -1398,16 +1410,22 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 .Where(i => i.IsVisibleStandalone(user))
                 .Where(i => i.IsVisibleStandalone(user))
                 .ToList();
                 .ToList();
 
 
-            var items = _libraryManager.GetItemsResult(new InternalItemsQuery(user)
+            if (folders.Count == 0)
+            {
+                return new QueryResult<BaseItem>();
+            }
+
+            return _libraryManager.GetItemsResult(new InternalItemsQuery(user)
             {
             {
                 MediaTypes = new[] { MediaType.Video },
                 MediaTypes = new[] { MediaType.Video },
                 Recursive = true,
                 Recursive = true,
                 AncestorIds = folders.Select(i => i.Id.ToString("N")).ToArray(),
                 AncestorIds = folders.Select(i => i.Id.ToString("N")).ToArray(),
+                IsFolder = false,
                 ExcludeLocationTypes = new[] { LocationType.Virtual },
                 ExcludeLocationTypes = new[] { LocationType.Virtual },
-                Limit = Math.Min(10, query.Limit ?? int.MaxValue)
+                Limit = Math.Min(200, query.Limit ?? int.MaxValue),
+                SortBy = new[] { ItemSortBy.DateCreated },
+                SortOrder = SortOrder.Descending
             });
             });
-
-            return items;
         }
         }
 
 
         public async Task<QueryResult<BaseItem>> GetInternalRecordings(RecordingQuery query, CancellationToken cancellationToken)
         public async Task<QueryResult<BaseItem>> GetInternalRecordings(RecordingQuery query, CancellationToken cancellationToken)

+ 5 - 5
MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj

@@ -55,9 +55,9 @@
     <Reference Include="Interfaces.IO">
     <Reference Include="Interfaces.IO">
       <HintPath>..\packages\Interfaces.IO.1.0.0.5\lib\portable-net45+sl4+wp71+win8+wpa81\Interfaces.IO.dll</HintPath>
       <HintPath>..\packages\Interfaces.IO.1.0.0.5\lib\portable-net45+sl4+wp71+win8+wpa81\Interfaces.IO.dll</HintPath>
     </Reference>
     </Reference>
-    <Reference Include="MediaBrowser.Naming, Version=1.0.5917.1514, Culture=neutral, processorArchitecture=MSIL">
-      <SpecificVersion>False</SpecificVersion>
-      <HintPath>..\packages\MediaBrowser.Naming.1.0.0.49\lib\portable-net45+sl4+wp71+win8+wpa81\MediaBrowser.Naming.dll</HintPath>
+    <Reference Include="MediaBrowser.Naming, Version=1.0.5981.21615, Culture=neutral, processorArchitecture=MSIL">
+      <HintPath>..\packages\MediaBrowser.Naming.1.0.0.50\lib\portable-net45+sl4+wp71+win8+wpa81\MediaBrowser.Naming.dll</HintPath>
+      <Private>True</Private>
     </Reference>
     </Reference>
     <Reference Include="MoreLinq">
     <Reference Include="MoreLinq">
       <HintPath>..\packages\morelinq.1.4.0\lib\net35\MoreLinq.dll</HintPath>
       <HintPath>..\packages\morelinq.1.4.0\lib\net35\MoreLinq.dll</HintPath>
@@ -68,8 +68,8 @@
     <Reference Include="ServiceStack.Api.Swagger">
     <Reference Include="ServiceStack.Api.Swagger">
       <HintPath>..\ThirdParty\ServiceStack\ServiceStack.Api.Swagger.dll</HintPath>
       <HintPath>..\ThirdParty\ServiceStack\ServiceStack.Api.Swagger.dll</HintPath>
     </Reference>
     </Reference>
-    <Reference Include="SimpleInjector, Version=3.1.3.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
-      <HintPath>..\packages\SimpleInjector.3.1.3\lib\net45\SimpleInjector.dll</HintPath>
+    <Reference Include="SimpleInjector, Version=3.1.4.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
+      <HintPath>..\packages\SimpleInjector.3.1.4\lib\net45\SimpleInjector.dll</HintPath>
       <Private>True</Private>
       <Private>True</Private>
     </Reference>
     </Reference>
     <Reference Include="SocketHttpListener, Version=1.0.5955.1537, Culture=neutral, processorArchitecture=MSIL">
     <Reference Include="SocketHttpListener, Version=1.0.5955.1537, Culture=neutral, processorArchitecture=MSIL">

+ 139 - 14
MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs

@@ -81,10 +81,13 @@ namespace MediaBrowser.Server.Implementations.Persistence
         private IDbCommand _deleteUserDataKeysCommand;
         private IDbCommand _deleteUserDataKeysCommand;
         private IDbCommand _saveUserDataKeysCommand;
         private IDbCommand _saveUserDataKeysCommand;
 
 
+        private IDbCommand _deleteItemValuesCommand;
+        private IDbCommand _saveItemValuesCommand;
+
         private IDbCommand _updateInheritedRatingCommand;
         private IDbCommand _updateInheritedRatingCommand;
         private IDbCommand _updateInheritedTagsCommand;
         private IDbCommand _updateInheritedTagsCommand;
 
 
-        public const int LatestSchemaVersion = 78;
+        public const int LatestSchemaVersion = 79;
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="SqliteItemRepository"/> class.
         /// Initializes a new instance of the <see cref="SqliteItemRepository"/> class.
@@ -136,6 +139,9 @@ namespace MediaBrowser.Server.Implementations.Persistence
                                 "create table if not exists UserDataKeys (ItemId GUID, UserDataKey TEXT, PRIMARY KEY (ItemId, UserDataKey))",
                                 "create table if not exists UserDataKeys (ItemId GUID, UserDataKey TEXT, PRIMARY KEY (ItemId, UserDataKey))",
                                 "create index if not exists idx_UserDataKeys1 on UserDataKeys(ItemId)",
                                 "create index if not exists idx_UserDataKeys1 on UserDataKeys(ItemId)",
 
 
+                                "create table if not exists ItemValues (ItemId GUID, Type INT, Value TEXT)",
+                                "create index if not exists idx_ItemValues on ItemValues(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 idxPeopleItemId on People(ItemId)",
                                 "create index if not exists idxPeopleName on People(Name)",
                                 "create index if not exists idxPeopleName on People(Name)",
@@ -232,6 +238,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
             _connection.AddColumn(Logger, "TypedBaseItems", "PrimaryVersionId", "Text");
             _connection.AddColumn(Logger, "TypedBaseItems", "PrimaryVersionId", "Text");
             _connection.AddColumn(Logger, "TypedBaseItems", "DateLastMediaAdded", "DATETIME");
             _connection.AddColumn(Logger, "TypedBaseItems", "DateLastMediaAdded", "DATETIME");
             _connection.AddColumn(Logger, "TypedBaseItems", "Album", "Text");
             _connection.AddColumn(Logger, "TypedBaseItems", "Album", "Text");
+            _connection.AddColumn(Logger, "TypedBaseItems", "IsVirtualItem", "BIT");
 
 
             _connection.AddColumn(Logger, "UserDataKeys", "Priority", "INT");
             _connection.AddColumn(Logger, "UserDataKeys", "Priority", "INT");
 
 
@@ -353,7 +360,8 @@ namespace MediaBrowser.Server.Implementations.Persistence
             "DateLastMediaAdded",
             "DateLastMediaAdded",
             "Album",
             "Album",
             "CriticRating",
             "CriticRating",
-            "CriticRatingSummary"
+            "CriticRatingSummary",
+            "IsVirtualItem"
         };
         };
 
 
         private readonly string[] _mediaStreamSaveColumns =
         private readonly string[] _mediaStreamSaveColumns =
@@ -468,7 +476,8 @@ namespace MediaBrowser.Server.Implementations.Persistence
                 "OriginalTitle",
                 "OriginalTitle",
                 "PrimaryVersionId",
                 "PrimaryVersionId",
                 "DateLastMediaAdded",
                 "DateLastMediaAdded",
-                "Album"
+                "Album",
+                "IsVirtualItem"
             };
             };
             _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 (";
@@ -565,6 +574,17 @@ namespace MediaBrowser.Server.Implementations.Persistence
             _saveUserDataKeysCommand.Parameters.Add(_saveUserDataKeysCommand, "@UserDataKey");
             _saveUserDataKeysCommand.Parameters.Add(_saveUserDataKeysCommand, "@UserDataKey");
             _saveUserDataKeysCommand.Parameters.Add(_saveUserDataKeysCommand, "@Priority");
             _saveUserDataKeysCommand.Parameters.Add(_saveUserDataKeysCommand, "@Priority");
 
 
+            // item values
+            _deleteItemValuesCommand = _connection.CreateCommand();
+            _deleteItemValuesCommand.CommandText = "delete from ItemValues where ItemId=@Id";
+            _deleteItemValuesCommand.Parameters.Add(_deleteItemValuesCommand, "@Id");
+
+            _saveItemValuesCommand = _connection.CreateCommand();
+            _saveItemValuesCommand.CommandText = "insert into ItemValues (ItemId, Type, Value) values (@ItemId, @Type, @Value)";
+            _saveItemValuesCommand.Parameters.Add(_saveItemValuesCommand, "@ItemId");
+            _saveItemValuesCommand.Parameters.Add(_saveItemValuesCommand, "@Type");
+            _saveItemValuesCommand.Parameters.Add(_saveItemValuesCommand, "@Value");
+
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -722,7 +742,15 @@ namespace MediaBrowser.Server.Implementations.Persistence
                         _saveItemCommand.GetParameter(index++).Value = item.DateLastRefreshed;
                         _saveItemCommand.GetParameter(index++).Value = item.DateLastRefreshed;
                     }
                     }
 
 
-                    _saveItemCommand.GetParameter(index++).Value = item.DateLastSaved;
+                    if (item.DateLastSaved == default(DateTime))
+                    {
+                        _saveItemCommand.GetParameter(index++).Value = null;
+                    }
+                    else
+                    {
+                        _saveItemCommand.GetParameter(index++).Value = item.DateLastSaved;
+                    }
+
                     _saveItemCommand.GetParameter(index++).Value = item.IsInMixedFolder;
                     _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.LockedFields.Select(i => i.ToString()).ToArray());
                     _saveItemCommand.GetParameter(index++).Value = string.Join("|", item.Studios.ToArray());
                     _saveItemCommand.GetParameter(index++).Value = string.Join("|", item.Studios.ToArray());
@@ -841,6 +869,16 @@ namespace MediaBrowser.Server.Implementations.Persistence
 
 
                     _saveItemCommand.GetParameter(index++).Value = item.Album;
                     _saveItemCommand.GetParameter(index++).Value = item.Album;
 
 
+                    var season = item as Season;
+                    if (season != null && season.IsVirtualItem.HasValue)
+                    {
+                        _saveItemCommand.GetParameter(index++).Value = season.IsVirtualItem.Value;
+                    }
+                    else
+                    {
+                        _saveItemCommand.GetParameter(index++).Value = null;
+                    }
+
                     _saveItemCommand.Transaction = transaction;
                     _saveItemCommand.Transaction = transaction;
 
 
                     _saveItemCommand.ExecuteNonQuery();
                     _saveItemCommand.ExecuteNonQuery();
@@ -851,6 +889,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
                     }
                     }
 
 
                     UpdateUserDataKeys(item.Id, item.GetUserDataKeys().Distinct(StringComparer.OrdinalIgnoreCase).ToList(), transaction);
                     UpdateUserDataKeys(item.Id, item.GetUserDataKeys().Distinct(StringComparer.OrdinalIgnoreCase).ToList(), transaction);
+                    UpdateItemValues(item.Id, GetItemValues(item), transaction);
                 }
                 }
 
 
                 transaction.Commit();
                 transaction.Commit();
@@ -1255,6 +1294,12 @@ namespace MediaBrowser.Server.Implementations.Persistence
                 item.CriticRatingSummary = reader.GetString(57);
                 item.CriticRatingSummary = reader.GetString(57);
             }
             }
 
 
+            var season = item as Season;
+            if (season != null && !reader.IsDBNull(58))
+            {
+                season.IsVirtualItem = reader.GetBoolean(58);
+            }
+
             return item;
             return item;
         }
         }
 
 
@@ -1661,7 +1706,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
         {
         {
             var elapsed = (DateTime.UtcNow - startDate).TotalMilliseconds;
             var elapsed = (DateTime.UtcNow - startDate).TotalMilliseconds;
 
 
-            if (elapsed >= 500)
+            if (elapsed >= 400)
             {
             {
                 Logger.Debug("{2} query time (slow): {0}ms. Query: {1}",
                 Logger.Debug("{2} query time (slow): {0}ms. Query: {1}",
                     Convert.ToInt32(elapsed),
                     Convert.ToInt32(elapsed),
@@ -1795,7 +1840,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
             }).ToArray());
             }).ToArray());
         }
         }
 
 
-        private Tuple<string,bool> MapOrderByField(string name)
+        private Tuple<string, bool> MapOrderByField(string name)
         {
         {
             if (string.Equals(name, ItemSortBy.AirTime, StringComparison.OrdinalIgnoreCase))
             if (string.Equals(name, ItemSortBy.AirTime, StringComparison.OrdinalIgnoreCase))
             {
             {
@@ -1838,6 +1883,14 @@ namespace MediaBrowser.Server.Implementations.Persistence
             {
             {
                 return new Tuple<string, bool>("DateLastMediaAdded", false);
                 return new Tuple<string, bool>("DateLastMediaAdded", false);
             }
             }
+            if (string.Equals(name, ItemSortBy.Artist, StringComparison.OrdinalIgnoreCase))
+            {
+                return new Tuple<string, bool>("(select value from itemvalues where ItemId=Guid and Type=0 LIMIT 1)", false);
+            }
+            if (string.Equals(name, ItemSortBy.AlbumArtist, StringComparison.OrdinalIgnoreCase))
+            {
+                return new Tuple<string, bool>("(select value from itemvalues where ItemId=Guid and Type=1 LIMIT 1)", false);
+            }
 
 
             return new Tuple<string, bool>(name, false);
             return new Tuple<string, bool>(name, false);
         }
         }
@@ -2405,17 +2458,20 @@ namespace MediaBrowser.Server.Implementations.Persistence
                 cmd.Parameters.Add(cmd, "@IsFavorite", DbType.Boolean).Value = query.IsFavorite.Value;
                 cmd.Parameters.Add(cmd, "@IsFavorite", DbType.Boolean).Value = query.IsFavorite.Value;
             }
             }
 
 
-            if (query.IsPlayed.HasValue)
+            if (EnableJoinUserData(query))
             {
             {
-                if (query.IsPlayed.Value)
-                {
-                    whereClauses.Add("(played=@IsPlayed)");
-                }
-                else
+                if (query.IsPlayed.HasValue)
                 {
                 {
-                    whereClauses.Add("(played is null or played=@IsPlayed)");
+                    if (query.IsPlayed.Value)
+                    {
+                        whereClauses.Add("(played=@IsPlayed)");
+                    }
+                    else
+                    {
+                        whereClauses.Add("(played is null or played=@IsPlayed)");
+                    }
+                    cmd.Parameters.Add(cmd, "@IsPlayed", DbType.Boolean).Value = query.IsPlayed.Value;
                 }
                 }
-                cmd.Parameters.Add(cmd, "@IsPlayed", DbType.Boolean).Value = query.IsPlayed.Value;
             }
             }
 
 
             if (query.IsResumable.HasValue)
             if (query.IsResumable.HasValue)
@@ -2430,6 +2486,20 @@ namespace MediaBrowser.Server.Implementations.Persistence
                 }
                 }
             }
             }
 
 
+            if (query.ArtistNames.Length > 0)
+            {
+                var clauses = new List<string>();
+                var index = 0;
+                foreach (var artist in query.ArtistNames)
+                {
+                    clauses.Add("@ArtistName" + index + " in (select value from itemvalues where ItemId=Guid and Type <= 1)");
+                    cmd.Parameters.Add(cmd, "@ArtistName" + index, DbType.String).Value = artist;
+                    index++;
+                }
+                var clause = "(" + string.Join(" OR ", clauses.ToArray()) + ")";
+                whereClauses.Add(clause);
+            }
+
             if (query.Genres.Length > 0)
             if (query.Genres.Length > 0)
             {
             {
                 var clauses = new List<string>();
                 var clauses = new List<string>();
@@ -2967,6 +3037,11 @@ namespace MediaBrowser.Server.Implementations.Persistence
                 _deleteUserDataKeysCommand.Transaction = transaction;
                 _deleteUserDataKeysCommand.Transaction = transaction;
                 _deleteUserDataKeysCommand.ExecuteNonQuery();
                 _deleteUserDataKeysCommand.ExecuteNonQuery();
 
 
+                // Delete item values
+                _deleteItemValuesCommand.GetParameter(0).Value = id;
+                _deleteItemValuesCommand.Transaction = transaction;
+                _deleteItemValuesCommand.ExecuteNonQuery();
+
                 // Delete the item
                 // Delete the item
                 _deleteItemCommand.GetParameter(0).Value = id;
                 _deleteItemCommand.GetParameter(0).Value = id;
                 _deleteItemCommand.Transaction = transaction;
                 _deleteItemCommand.Transaction = transaction;
@@ -3159,6 +3234,56 @@ namespace MediaBrowser.Server.Implementations.Persistence
             }
             }
         }
         }
 
 
+        private List<Tuple<int, string>> GetItemValues(BaseItem item)
+        {
+            var list = new List<Tuple<int, string>>();
+
+            var hasArtist = item as IHasArtist;
+            if (hasArtist != null)
+            {
+                list.AddRange(hasArtist.Artists.Select(i => new Tuple<int, string>(0, i)));
+            }
+
+            var hasAlbumArtist = item as IHasAlbumArtist;
+            if (hasAlbumArtist != null)
+            {
+                list.AddRange(hasAlbumArtist.AlbumArtists.Select(i => new Tuple<int, string>(1, i)));
+            }
+
+            return list;
+        }
+
+        private void UpdateItemValues(Guid itemId, List<Tuple<int, string>> values, IDbTransaction transaction)
+        {
+            if (itemId == Guid.Empty)
+            {
+                throw new ArgumentNullException("itemId");
+            }
+
+            if (values == null)
+            {
+                throw new ArgumentNullException("keys");
+            }
+
+            CheckDisposed();
+
+            // First delete 
+            _deleteItemValuesCommand.GetParameter(0).Value = itemId;
+            _deleteItemValuesCommand.Transaction = transaction;
+
+            _deleteItemValuesCommand.ExecuteNonQuery();
+
+            foreach (var pair in values)
+            {
+                _saveItemValuesCommand.GetParameter(0).Value = itemId;
+                _saveItemValuesCommand.GetParameter(1).Value = pair.Item1;
+                _saveItemValuesCommand.GetParameter(2).Value = pair.Item2;
+                _saveItemValuesCommand.Transaction = transaction;
+
+                _saveItemValuesCommand.ExecuteNonQuery();
+            }
+        }
+
         private void UpdateUserDataKeys(Guid itemId, List<string> keys, IDbTransaction transaction)
         private void UpdateUserDataKeys(Guid itemId, List<string> keys, IDbTransaction transaction)
         {
         {
             if (itemId == Guid.Empty)
             if (itemId == Guid.Empty)

+ 2 - 2
MediaBrowser.Server.Implementations/packages.config

@@ -4,10 +4,10 @@
   <package id="Emby.XmlTv" version="1.0.0.48" targetFramework="net45" />
   <package id="Emby.XmlTv" version="1.0.0.48" targetFramework="net45" />
   <package id="ini-parser" version="2.2.4" targetFramework="net45" />
   <package id="ini-parser" version="2.2.4" targetFramework="net45" />
   <package id="Interfaces.IO" version="1.0.0.5" targetFramework="net45" />
   <package id="Interfaces.IO" version="1.0.0.5" targetFramework="net45" />
-  <package id="MediaBrowser.Naming" version="1.0.0.49" targetFramework="net45" />
+  <package id="MediaBrowser.Naming" version="1.0.0.50" targetFramework="net45" />
   <package id="Mono.Nat" version="1.2.24.0" targetFramework="net45" />
   <package id="Mono.Nat" version="1.2.24.0" targetFramework="net45" />
   <package id="morelinq" version="1.4.0" targetFramework="net45" />
   <package id="morelinq" version="1.4.0" targetFramework="net45" />
   <package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
   <package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" />
-  <package id="SimpleInjector" version="3.1.3" targetFramework="net45" />
+  <package id="SimpleInjector" version="3.1.4" targetFramework="net45" />
   <package id="SocketHttpListener" version="1.0.0.30" targetFramework="net45" />
   <package id="SocketHttpListener" version="1.0.0.30" targetFramework="net45" />
 </packages>
 </packages>

+ 3 - 3
MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj

@@ -107,9 +107,6 @@
     <Content Include="dashboard-ui\components\chromecasthelpers.js">
     <Content Include="dashboard-ui\components\chromecasthelpers.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     </Content>
-    <Content Include="dashboard-ui\bower_components\fastclick\lib\fastclick.js">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
     <Content Include="dashboard-ui\components\favoriteitems.js">
     <Content Include="dashboard-ui\components\favoriteitems.js">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
     </Content>
@@ -1686,6 +1683,9 @@
     <None Include="dashboard-ui\strings\id.json">
     <None Include="dashboard-ui\strings\id.json">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </None>
     </None>
+    <None Include="dashboard-ui\strings\sk.json">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
     <None Include="dashboard-ui\strings\zh-HK.json">
     <None Include="dashboard-ui\strings\zh-HK.json">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </None>
     </None>

+ 4 - 4
Nuget/MediaBrowser.Common.Internal.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
     <metadata>
         <id>MediaBrowser.Common.Internal</id>
         <id>MediaBrowser.Common.Internal</id>
-        <version>3.0.647</version>
+        <version>3.0.648</version>
         <title>MediaBrowser.Common.Internal</title>
         <title>MediaBrowser.Common.Internal</title>
         <authors>Luke</authors>
         <authors>Luke</authors>
         <owners>ebr,Luke,scottisafool</owners>
         <owners>ebr,Luke,scottisafool</owners>
@@ -12,9 +12,9 @@
         <description>Contains common components shared by Emby Theater and Emby Server. Not intended for plugin developer consumption.</description>
         <description>Contains common components shared by Emby Theater and Emby Server. Not intended for plugin developer consumption.</description>
         <copyright>Copyright © Emby 2013</copyright>
         <copyright>Copyright © Emby 2013</copyright>
         <dependencies>
         <dependencies>
-            <dependency id="MediaBrowser.Common" version="3.0.647" />
-            <dependency id="NLog" version="4.3.1" />
-            <dependency id="SimpleInjector" version="3.1.3" />
+            <dependency id="MediaBrowser.Common" version="3.0.648" />
+            <dependency id="NLog" version="4.3.4" />
+            <dependency id="SimpleInjector" version="3.1.4" />
         </dependencies>
         </dependencies>
     </metadata>
     </metadata>
     <files>
     <files>

+ 1 - 1
Nuget/MediaBrowser.Common.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
     <metadata>
         <id>MediaBrowser.Common</id>
         <id>MediaBrowser.Common</id>
-        <version>3.0.647</version>
+        <version>3.0.648</version>
         <title>MediaBrowser.Common</title>
         <title>MediaBrowser.Common</title>
         <authors>Emby Team</authors>
         <authors>Emby Team</authors>
         <owners>ebr,Luke,scottisafool</owners>
         <owners>ebr,Luke,scottisafool</owners>

+ 0 - 20
Nuget/MediaBrowser.Model.Signed.nuspec

@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
-    <metadata>
-        <id>MediaBrowser.Model.Signed</id>
-        <version>3.0.647</version>
-        <title>MediaBrowser.Model - Signed Edition</title>
-        <authors>Emby Team</authors>
-        <owners>ebr,Luke,scottisafool</owners>
-        <projectUrl>https://github.com/MediaBrowser/MediaBrowser</projectUrl>
-        <iconUrl>http://www.mb3admin.com/images/mb3icons1-1.png</iconUrl>
-        <requireLicenseAcceptance>false</requireLicenseAcceptance>
-        <description>Contains common model objects and interfaces used by all Emby solutions.</description>
-        <copyright>Copyright © Emby 2013</copyright>
-        <dependencies>
-        </dependencies>
-    </metadata>
-    <files>
-        <file src="dllssigned\net45\MediaBrowser.Model.dll" target="lib\net45\MediaBrowser.Model.dll" />
-    </files>
-</package>

+ 2 - 2
Nuget/MediaBrowser.Server.Core.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
 <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
     <metadata>
     <metadata>
         <id>MediaBrowser.Server.Core</id>
         <id>MediaBrowser.Server.Core</id>
-        <version>3.0.647</version>
+        <version>3.0.648</version>
         <title>Media Browser.Server.Core</title>
         <title>Media Browser.Server.Core</title>
         <authors>Emby Team</authors>
         <authors>Emby Team</authors>
         <owners>ebr,Luke,scottisafool</owners>
         <owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
         <description>Contains core components required to build plugins for Emby Server.</description>
         <description>Contains core components required to build plugins for Emby Server.</description>
         <copyright>Copyright © Emby 2013</copyright>
         <copyright>Copyright © Emby 2013</copyright>
         <dependencies>
         <dependencies>
-            <dependency id="MediaBrowser.Common" version="3.0.647" />
+            <dependency id="MediaBrowser.Common" version="3.0.648" />
 			<dependency id="Interfaces.IO" version="1.0.0.5" />
 			<dependency id="Interfaces.IO" version="1.0.0.5" />
         </dependencies>
         </dependencies>
     </metadata>
     </metadata>