Luke Pulverenti пре 9 година
родитељ
комит
b253d26aba
44 измењених фајлова са 528 додато и 372 уклоњено
  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,
                 MediaTypes = request.GetMediaTypes(),
                 IncludeItemTypes = request.GetIncludeItemTypes(),
-                Recursive = true
+                Recursive = true,
+                EnableTotalRecordCount = false
             };
 
             return query;

+ 4 - 1
MediaBrowser.Api/GamesService.cs

@@ -162,7 +162,10 @@ namespace MediaBrowser.Api
 
             var items = user == null ? 
                 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();
 

+ 10 - 5
MediaBrowser.Api/StartupWizardService.cs

@@ -65,11 +65,7 @@ namespace MediaBrowser.Api
         public void Post(ReportStartupWizardComplete request)
         {
             _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();
         }
 
@@ -111,6 +107,15 @@ namespace MediaBrowser.Api
             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)
         {
             _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)
         {
-            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)
             {
                 var val = request.IsMissing.Value;
-                items = items.Where(i => (i.IsMissingSeason ?? false) == val);
+                items = items.Where(i => (i.IsMissingSeason) == val);
             }
 
             if (request.IsVirtualUnaired.HasValue)

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

@@ -121,6 +121,13 @@ namespace MediaBrowser.Api.UserLibrary
             var includeItemTypes = request.GetIncludeItemTypes();
             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);
 
             if (parentItem.IsFolder)
@@ -130,7 +137,7 @@ namespace MediaBrowser.Api.UserLibrary
                 if (!string.IsNullOrWhiteSpace(request.UserId))
                 {
                     items = request.Recursive ?
-                        folder.GetRecursiveChildren(user, filter) :
+                        folder.GetRecursiveChildren(user, query) :
                         folder.GetChildren(user, true).Where(filter);
                 }
                 else

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

@@ -138,25 +138,19 @@ namespace MediaBrowser.Api.UserLibrary
 
             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)
             {
-                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;
 
             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);

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

@@ -55,7 +55,7 @@
       <HintPath>..\packages\morelinq.1.4.0\lib\net35\MoreLinq.dll</HintPath>
     </Reference>
     <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>
     </Reference>
     <Reference Include="Patterns.Logging">
@@ -65,8 +65,8 @@
       <SpecificVersion>False</SpecificVersion>
       <HintPath>..\ThirdParty\SharpCompress\SharpCompress.dll</HintPath>
     </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>
     </Reference>
     <Reference Include="System" />

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

@@ -2,7 +2,7 @@
 <packages>
   <package id="CommonIO" version="1.0.0.9" 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="SimpleInjector" version="3.1.3" targetFramework="net45" />
+  <package id="SimpleInjector" version="3.1.4" targetFramework="net45" />
 </packages>

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

@@ -64,10 +64,37 @@ namespace MediaBrowser.Controller.Entities
 
         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;
 
@@ -100,7 +127,11 @@ namespace MediaBrowser.Controller.Entities
                 args.FileSystemDictionary = fileSystemDictionary;
             }
 
-            PhysicalLocationsList = args.PhysicalLocations.ToList();
+            _requiresRefresh = _requiresRefresh || !args.PhysicalLocations.SequenceEqual(PhysicalLocations);
+            if (setPhysicalLocations)
+            {
+                PhysicalLocationsList = args.PhysicalLocations.ToList();
+            }
 
             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)
         {
-            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
         {
             get

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

@@ -23,19 +23,6 @@ namespace MediaBrowser.Controller.Entities
             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]
         protected override bool SupportsShortcutChildren
         {
@@ -83,7 +70,34 @@ namespace MediaBrowser.Controller.Entities
 
         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)
@@ -101,7 +115,7 @@ namespace MediaBrowser.Controller.Entities
             return base.IsValidFromResolver(newItem);
         }
 
-        private ItemResolveArgs CreateResolveArgs(IDirectoryService directoryService)
+        private ItemResolveArgs CreateResolveArgs(IDirectoryService directoryService, bool setPhysicalLocations)
         {
             var path = ContainingFolderPath;
 
@@ -135,7 +149,11 @@ namespace MediaBrowser.Controller.Entities
                 args.FileSystemDictionary = fileSystemDictionary;
             }
 
-            PhysicalLocationsList = args.PhysicalLocations.ToList();
+            _requiresRefresh = _requiresRefresh || !args.PhysicalLocations.SequenceEqual(PhysicalLocations);
+            if (setPhysicalLocations)
+            {
+                PhysicalLocationsList = args.PhysicalLocations.ToList();
+            }
 
             return args;
         }
@@ -153,15 +171,6 @@ namespace MediaBrowser.Controller.Entities
         /// <returns>Task.</returns>
         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);
         }
 
@@ -188,6 +197,7 @@ namespace MediaBrowser.Controller.Entities
         /// Our children are actually just references to the ones in the physical root...
         /// </summary>
         /// <value>The actual children.</value>
+        [IgnoreDataMember]
         protected override IEnumerable<BaseItem> ActualChildren
         {
             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>
         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; }
 
         [IgnoreDataMember]
@@ -285,6 +272,7 @@ namespace MediaBrowser.Controller.Entities
         /// Gets the actual children.
         /// </summary>
         /// <value>The actual children.</value>
+        [IgnoreDataMember]
         protected virtual IEnumerable<BaseItem> ActualChildren
         {
             get
@@ -749,7 +737,7 @@ namespace MediaBrowser.Controller.Entities
         {
             var user = query.User;
 
-            if (RequiresPostFiltering(query))
+            if (!query.ForceDirect && RequiresPostFiltering(query))
             {
                 IEnumerable<BaseItem> items;
                 Func<BaseItem, bool> filter = i => UserViewBuilder.Filter(i, user, query, UserDataManager, LibraryManager);
@@ -760,7 +748,7 @@ namespace MediaBrowser.Controller.Entities
                 }
                 else
                 {
-                    items = GetRecursiveChildren(user, filter);
+                    items = GetRecursiveChildren(user, query);
                 }
 
                 return PostFilterAndSort(items, query);
@@ -784,7 +772,7 @@ namespace MediaBrowser.Controller.Entities
                     return true;
                 }
             }
-            
+
             var supportsUserDataQueries = ConfigurationManager.Configuration.SchemaVersion >= 76;
 
             if (query.SortBy != null && query.SortBy.Length > 0)
@@ -817,19 +805,24 @@ namespace MediaBrowser.Controller.Entities
                         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;
                 }
                 if (query.SortBy.Contains(ItemSortBy.Budget, StringComparer.OrdinalIgnoreCase))
@@ -1109,10 +1102,13 @@ namespace MediaBrowser.Controller.Entities
                 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;
@@ -1178,7 +1174,7 @@ namespace MediaBrowser.Controller.Entities
             else
             {
                 items = query.Recursive
-                   ? GetRecursiveChildren(user, filter)
+                   ? GetRecursiveChildren(user, query)
                    : GetChildren(user, true).Where(filter);
             }
 
@@ -1215,19 +1211,14 @@ namespace MediaBrowser.Controller.Entities
         /// <summary>
         /// Adds the children to list.
         /// </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>
-        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))
             {
                 if (child.IsVisible(user))
                 {
-                    if (filter == null || filter(child))
+                    if (query == null || UserViewBuilder.FilterItem(child, query))
                     {
                         result[child.Id] = child;
                     }
@@ -1236,7 +1227,7 @@ namespace MediaBrowser.Controller.Entities
                     {
                         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 (filter == null || filter(child))
+                        if (query == null || UserViewBuilder.FilterItem(child, query))
                         {
                             result[child.Id] = child;
                         }
@@ -1265,10 +1256,10 @@ namespace MediaBrowser.Controller.Entities
         /// <exception cref="System.ArgumentNullException"></exception>
         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)
             {
@@ -1277,7 +1268,7 @@ namespace MediaBrowser.Controller.Entities
 
             var result = new Dictionary<Guid, BaseItem>();
 
-            AddChildren(user, true, result, true, filter);
+            AddChildren(user, true, result, true, query);
 
             return result.Values;
         }
@@ -1303,7 +1294,7 @@ namespace MediaBrowser.Controller.Entities
         /// <summary>
         /// Adds the children to list.
         /// </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)
             {
@@ -1534,13 +1525,12 @@ namespace MediaBrowser.Controller.Entities
                 User = user,
                 Recursive = true,
                 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);
@@ -1562,7 +1552,8 @@ namespace MediaBrowser.Controller.Entities
             {
                 User = user,
                 Recursive = true,
-                IsFolder = false
+                IsFolder = false,
+                EnableTotalRecordCount = false
 
             }).ConfigureAwait(false);
 
@@ -1578,7 +1569,8 @@ namespace MediaBrowser.Controller.Entities
             {
                 Recursive = true,
                 IsFolder = false,
-                ExcludeLocationTypes = new[] { LocationType.Virtual }
+                ExcludeLocationTypes = new[] { LocationType.Virtual },
+                EnableTotalRecordCount = false
 
             }).Result;
 
@@ -1630,7 +1622,8 @@ namespace MediaBrowser.Controller.Entities
             {
                 Recursive = true,
                 IsFolder = false,
-                ExcludeLocationTypes = new[] { LocationType.Virtual }
+                ExcludeLocationTypes = new[] { LocationType.Virtual },
+                EnableTotalRecordCount = false
 
             }).Result;
 

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

@@ -15,12 +15,6 @@ namespace MediaBrowser.Controller.Entities
         /// <returns>IEnumerable{BaseItem}.</returns>
         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);
     }
 

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

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

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

@@ -134,7 +134,7 @@ namespace MediaBrowser.Controller.Entities.TV
 
             if (!result)
             {
-                if (!IsMissingSeason.HasValue)
+                if (!IsVirtualItem.HasValue)
                 {
                     return true;
                 }
@@ -144,18 +144,23 @@ namespace MediaBrowser.Controller.Entities.TV
         }
 
         [IgnoreDataMember]
-        public bool? IsMissingSeason { get; set; }
+        public bool? IsVirtualItem { get; set; }
 
         [IgnoreDataMember]
-        public bool IsVirtualUnaired
+        public bool IsMissingSeason
         {
-            get { return LocationType == LocationType.Virtual && IsUnaired; }
+            get { return (IsVirtualItem ?? DetectIsVirtualItem()) && !IsUnaired; }
         }
 
         [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]
@@ -319,19 +324,14 @@ namespace MediaBrowser.Controller.Entities.TV
         {
             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>();
             }
 
-            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;
@@ -381,14 +374,18 @@ namespace MediaBrowser.Controller.Entities.TV
                 }
                 else
                 {
-                    episodes = GetRecursiveChildren(user, i => i is Episode)
-                        .Cast<Episode>();
+                    episodes = GetRecursiveChildren(user, new InternalItemsQuery(user)
+                    {
+                        IncludeItemTypes = new[] { typeof(Episode).Name }
+                    }).Cast<Episode>();
                 }
             }
             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);

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

@@ -305,14 +305,7 @@ namespace MediaBrowser.Controller.Entities
 
         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]

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

@@ -64,15 +64,6 @@ namespace MediaBrowser.Controller.Entities
             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()
         {
             var hasChanges = base.BeforeMetadataRefresh();

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

@@ -66,7 +66,8 @@ namespace MediaBrowser.Controller.Entities
         {
             var result = GetItems(new InternalItemsQuery
             {
-                User = user
+                User = user,
+                EnableTotalRecordCount = false
 
             }).Result;
 
@@ -83,17 +84,19 @@ namespace MediaBrowser.Controller.Entities
             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
             {
                 User = user,
                 Recursive = true,
-                Filter = filter
+                EnableTotalRecordCount = false,
+
+                ForceDirect = true
 
             }).Result;
 
-            return result.Items;
+            return result.Items.Where(i => UserViewBuilder.FilterItem(i, query));
         }
 
         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)
                         {
-                            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);
                     }
@@ -251,7 +255,6 @@ namespace MediaBrowser.Controller.Entities
             if (query.Recursive)
             {
                 query.Recursive = true;
-                query.ParentId = parent.Id;
                 query.SetUser(user);
 
                 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 };
                 }
 
-                return _libraryManager.GetItemsResult(query);
+                return parent.QueryRecursive(query);
             }
 
             var list = new List<BaseItem>();
@@ -329,9 +332,13 @@ namespace MediaBrowser.Controller.Entities
 
         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);
 
@@ -340,9 +347,13 @@ namespace MediaBrowser.Controller.Entities
 
         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);
 
@@ -351,9 +362,13 @@ namespace MediaBrowser.Controller.Entities
 
         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);
 
@@ -448,7 +463,6 @@ namespace MediaBrowser.Controller.Entities
             if (query.Recursive)
             {
                 query.Recursive = true;
-                query.ParentId = parent.Id;
                 query.SetUser(user);
 
                 if (query.IncludeItemTypes.Length == 0)
@@ -456,7 +470,7 @@ namespace MediaBrowser.Controller.Entities
                     query.IncludeItemTypes = new[] { typeof(Movie).Name, typeof(BoxSet).Name };
                 }
 
-                return _libraryManager.GetItemsResult(query);
+                return parent.QueryRecursive(query);
             }
 
             var list = new List<BaseItem>();
@@ -613,7 +627,6 @@ namespace MediaBrowser.Controller.Entities
             if (query.Recursive)
             {
                 query.Recursive = true;
-                query.ParentId = parent.Id;
                 query.SetUser(user);
 
                 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 };
                 }
 
-                return _libraryManager.GetItemsResult(query);
+                return parent.QueryRecursive(query);
             }
 
             var list = new List<BaseItem>();
@@ -756,9 +769,9 @@ namespace MediaBrowser.Controller.Entities
             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,
@@ -1121,22 +1134,6 @@ namespace MediaBrowser.Controller.Entities
             bool? isVirtualUnaired,
             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)
             {
                 var val = isMissing.Value;
@@ -1145,7 +1142,7 @@ namespace MediaBrowser.Controller.Entities
                     var e = i as Season;
                     if (e != null)
                     {
-                        return (e.IsMissingSeason ?? false) == val;
+                        return (e.IsMissingSeason) == val;
                     }
                     return true;
                 });
@@ -1277,11 +1274,6 @@ namespace MediaBrowser.Controller.Entities
                 return false;
             }
 
-            if (query.Filter != null && !query.Filter(item))
-            {
-                return false;
-            }
-
             UserItemData userData = null;
 
             if (query.IsLiked.HasValue)

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

@@ -63,13 +63,13 @@ namespace MediaBrowser.Controller.Playlists
             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;
 
-            if (filter != null)
+            if (query != null)
             {
-                items = items.Where(filter);
+                items = items.Where(i => UserViewBuilder.FilterItem(i, query));
             }
 
             return items;
@@ -129,7 +129,11 @@ namespace MediaBrowser.Controller.Playlists
 
                 var items = user == null
                     ? 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);
             }

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

@@ -401,10 +401,10 @@ namespace MediaBrowser.Dlna.ContentDirectory
                 SortOrder = sort.SortOrder,
                 User = user,
                 Recursive = true,
-                Filter = FilterUnsupportedContent,
+                IsMissing = false,
+                ExcludeItemTypes = new[] { typeof(Game).Name, typeof(Book).Name },
                 IsFolder = isFolder,
                 MediaTypes = mediaTypes.ToArray()
-
             });
         }
 
@@ -461,8 +461,10 @@ namespace MediaBrowser.Dlna.ContentDirectory
                 SortBy = sortOrders.ToArray(),
                 SortOrder = sort.SortOrder,
                 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);
 
@@ -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)
         {
             return DidlBuilder.IsIdRoot(id)

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

@@ -215,6 +215,9 @@ namespace MediaBrowser.Model.Configuration
         {
             Migrations = new string[] { };
 
+            EnableLocalizedGuids = true;
+            EnableCustomPathSubFolders = true;
+
             ImageSavingConvention = ImageSavingConvention.Compatible;
             PublicPort = 8096;
             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.Extensions;
 using System.Diagnostics;
+using MediaBrowser.Model.MediaInfo;
 
 namespace MediaBrowser.Model.Entities
 {
@@ -53,18 +53,22 @@ namespace MediaBrowser.Model.Entities
 
                     if (!string.IsNullOrEmpty(Language))
                     {
-                        attributes.Add(Language);
+                        attributes.Add(StringHelper.FirstToUpper(Language));
                     }
                     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);
                     }
 
-                    if (Channels.HasValue)
+                    if (!string.IsNullOrEmpty(ChannelLayout))
+                    {
+                        attributes.Add(ChannelLayout);
+                    }
+                    else if (Channels.HasValue)
                     {
                         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();
         }
+
+        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 PostPaddingSeconds { get; set; }
 
+        public string[] MediaLocationsCreated { get; set; }
+
         public LiveTvOptions()
         {
             EnableMovieProviders = true;
             EnableRecordingSubfolders = true;
             TunerHosts = new List<TunerHostInfo>();
             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 MP3 = "mp3";
         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]
         public string Person { get; set; }
 
+        public bool EnableTotalRecordCount { get; set; }
+
         /// <summary>
         /// Initializes a new instance of the <see cref="ItemQuery" /> class.
         /// </summary>
@@ -306,6 +308,8 @@ namespace MediaBrowser.Model.Querying
 
             VideoTypes = new VideoType[] { };
 
+            EnableTotalRecordCount = true;
+
             Artists = new string[] { };
             Studios = new string[] { };
             

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

@@ -145,11 +145,15 @@ namespace MediaBrowser.Providers.Manager
 
             bool hasRefreshedMetadata = true;
             bool hasRefreshedImages = true;
+            var requiresRefresh = false;
 
             // Next run metadata providers
             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();
 
                 var dateLastRefresh = EnableDateLastRefreshed(item)
@@ -217,11 +221,11 @@ namespace MediaBrowser.Providers.Manager
 
             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;
 
             // 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 (refreshOptions.ForceSave || refreshOptions.ReplaceAllMetadata)
@@ -461,11 +465,8 @@ namespace MediaBrowser.Providers.Manager
         /// <summary>
         /// Gets the providers.
         /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="status">The status.</param>
-        /// <param name="options">The options.</param>
         /// <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
             var providers = ((ProviderManager)ProviderManager).GetMetadataProviders<TItemType>(item).ToList();
@@ -475,7 +476,7 @@ namespace MediaBrowser.Providers.Manager
                 : status.DateLastMetadataRefresh ?? default(DateTime);
 
             // 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)
             {
@@ -668,12 +669,6 @@ namespace MediaBrowser.Providers.Manager
 
                     // If a local provider fails, consider that a failure
                     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)
                 {
-                    await AddSeason(series, seasonNumber, cancellationToken).ConfigureAwait(false);
+                    await AddSeason(series, seasonNumber, false, cancellationToken).ConfigureAwait(false);
 
                     hasChanges = true;
                 }
@@ -83,7 +83,7 @@ namespace MediaBrowser.Providers.TV
 
                 if (!hasSeason)
                 {
-                    await AddSeason(series, null, cancellationToken).ConfigureAwait(false);
+                    await AddSeason(series, null, false, cancellationToken).ConfigureAwait(false);
 
                     hasChanges = true;
                 }
@@ -95,12 +95,9 @@ namespace MediaBrowser.Providers.TV
         /// <summary>
         /// Adds the season.
         /// </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,
             int? seasonNumber,
+            bool isVirtualItem,
             CancellationToken cancellationToken)
         {
             var seasonName = seasonNumber == 0 ?
@@ -113,7 +110,8 @@ namespace MediaBrowser.Providers.TV
             {
                 Name = seasonName,
                 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);

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

@@ -418,7 +418,7 @@ namespace MediaBrowser.Providers.TV
             if (season == null)
             {
                 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));

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

@@ -36,7 +36,7 @@ namespace MediaBrowser.Providers.TV
             {
                 var episodes = item.GetEpisodes().ToList();
                 updateType |= SavePremiereDate(item, episodes);
-                updateType |= SaveIsMissing(item, episodes);
+                updateType |= SaveIsVirtualItem(item, episodes);
             }
 
             return updateType;
@@ -67,13 +67,13 @@ namespace MediaBrowser.Providers.TV
             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;
             }
 

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

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

+ 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)
         {
             var genres = user.RootFolder
-                .GetRecursiveChildren(user, i => i is Audio)
+                .GetRecursiveChildren(user, new InternalItemsQuery(user)
+                {
+                    IncludeItemTypes = new[] { typeof(Audio).Name }
+                })
                 .Cast<Audio>()
                 .Where(i => i.HasAnyArtist(artist.Name))
                 .SelectMany(i => i.Genres)
@@ -43,7 +46,10 @@ namespace MediaBrowser.Server.Implementations.Library
         public IEnumerable<Audio> GetInstantMixFromAlbum(MusicAlbum item, User user)
         {
             var genres = item
-                .GetRecursiveChildren(user, i => i is Audio)
+                .GetRecursiveChildren(user, new InternalItemsQuery(user)
+                {
+                    IncludeItemTypes = new[] { typeof(Audio).Name }
+                })
                .Cast<Audio>()
                .SelectMany(i => i.Genres)
                .Concat(item.Genres)
@@ -55,7 +61,10 @@ namespace MediaBrowser.Server.Implementations.Library
         public IEnumerable<Audio> GetInstantMixFromFolder(Folder item, User user)
         {
             var genres = item
-               .GetRecursiveChildren(user, i => i is Audio)
+               .GetRecursiveChildren(user, new InternalItemsQuery(user)
+               {
+                   IncludeItemTypes = new[] {typeof(Audio).Name}
+               })
                .Cast<Audio>()
                .SelectMany(i => i.Genres)
                .Concat(item.Genres)
@@ -67,7 +76,10 @@ namespace MediaBrowser.Server.Implementations.Library
         public IEnumerable<Audio> GetInstantMixFromPlaylist(Playlist item, User user)
         {
             var genres = item
-               .GetRecursiveChildren(user, i => i is Audio)
+               .GetRecursiveChildren(user, new InternalItemsQuery(user)
+               {
+                   IncludeItemTypes = new[] { typeof(Audio).Name }
+               })
                .Cast<Audio>()
                .SelectMany(i => i.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 defaultRecordingPath = DefaultRecordingPath;
-            if (!recordingFolders.Any(i => i.Locations.Contains(defaultRecordingPath, StringComparer.OrdinalIgnoreCase)))
-            {
-                RemovePathFromLibrary(defaultRecordingPath);
-            }
-
             var virtualFolders = _libraryManager.GetVirtualFolders()
                 .ToList();
 
             var allExistingPaths = virtualFolders.SelectMany(i => i.Locations).ToList();
 
+            var pathsAdded = new List<string>();
+
             foreach (var recordingFolder in recordingFolders)
             {
                 var pathsToCreate = recordingFolder.Locations
@@ -145,11 +141,33 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV
                 {
                     _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)
         {
+            _logger.Debug("Removing path from library: {0}", path);
+
             var requiresRefresh = false;
             var virtualFolders = _libraryManager.GetVirtualFolders()
                .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)
         {
             var isNew = false;
+            var forceUpdate = false;
 
             var id = _tvDtoService.GetInternalChannelId(serviceName, channelInfo.Id);
 
@@ -576,10 +577,12 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 if (!string.IsNullOrWhiteSpace(channelInfo.ImagePath))
                 {
                     item.SetImagePath(ImageType.Primary, channelInfo.ImagePath);
+                    forceUpdate = true;
                 }
                 else if (!string.IsNullOrWhiteSpace(channelInfo.ImageUrl))
                 {
                     item.SetImagePath(ImageType.Primary, channelInfo.ImageUrl);
+                    forceUpdate = true;
                 }
             }
 
@@ -588,9 +591,18 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 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)
             {
-                ForceSave = isNew
+                ForceSave = isNew || forceUpdate
 
             }, cancellationToken);
 
@@ -1398,16 +1410,22 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 .Where(i => i.IsVisibleStandalone(user))
                 .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 },
                 Recursive = true,
                 AncestorIds = folders.Select(i => i.Id.ToString("N")).ToArray(),
+                IsFolder = false,
                 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)

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

@@ -55,9 +55,9 @@
     <Reference Include="Interfaces.IO">
       <HintPath>..\packages\Interfaces.IO.1.0.0.5\lib\portable-net45+sl4+wp71+win8+wpa81\Interfaces.IO.dll</HintPath>
     </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 Include="MoreLinq">
       <HintPath>..\packages\morelinq.1.4.0\lib\net35\MoreLinq.dll</HintPath>
@@ -68,8 +68,8 @@
     <Reference Include="ServiceStack.Api.Swagger">
       <HintPath>..\ThirdParty\ServiceStack\ServiceStack.Api.Swagger.dll</HintPath>
     </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>
     </Reference>
     <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 _saveUserDataKeysCommand;
 
+        private IDbCommand _deleteItemValuesCommand;
+        private IDbCommand _saveItemValuesCommand;
+
         private IDbCommand _updateInheritedRatingCommand;
         private IDbCommand _updateInheritedTagsCommand;
 
-        public const int LatestSchemaVersion = 78;
+        public const int LatestSchemaVersion = 79;
 
         /// <summary>
         /// 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 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 index if not exists idxPeopleItemId on People(ItemId)",
                                 "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", "DateLastMediaAdded", "DATETIME");
             _connection.AddColumn(Logger, "TypedBaseItems", "Album", "Text");
+            _connection.AddColumn(Logger, "TypedBaseItems", "IsVirtualItem", "BIT");
 
             _connection.AddColumn(Logger, "UserDataKeys", "Priority", "INT");
 
@@ -353,7 +360,8 @@ namespace MediaBrowser.Server.Implementations.Persistence
             "DateLastMediaAdded",
             "Album",
             "CriticRating",
-            "CriticRatingSummary"
+            "CriticRatingSummary",
+            "IsVirtualItem"
         };
 
         private readonly string[] _mediaStreamSaveColumns =
@@ -468,7 +476,8 @@ namespace MediaBrowser.Server.Implementations.Persistence
                 "OriginalTitle",
                 "PrimaryVersionId",
                 "DateLastMediaAdded",
-                "Album"
+                "Album",
+                "IsVirtualItem"
             };
             _saveItemCommand = _connection.CreateCommand();
             _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, "@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>
@@ -722,7 +742,15 @@ namespace MediaBrowser.Server.Implementations.Persistence
                         _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 = string.Join("|", item.LockedFields.Select(i => i.ToString()).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;
 
+                    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.ExecuteNonQuery();
@@ -851,6 +889,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
                     }
 
                     UpdateUserDataKeys(item.Id, item.GetUserDataKeys().Distinct(StringComparer.OrdinalIgnoreCase).ToList(), transaction);
+                    UpdateItemValues(item.Id, GetItemValues(item), transaction);
                 }
 
                 transaction.Commit();
@@ -1255,6 +1294,12 @@ namespace MediaBrowser.Server.Implementations.Persistence
                 item.CriticRatingSummary = reader.GetString(57);
             }
 
+            var season = item as Season;
+            if (season != null && !reader.IsDBNull(58))
+            {
+                season.IsVirtualItem = reader.GetBoolean(58);
+            }
+
             return item;
         }
 
@@ -1661,7 +1706,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
         {
             var elapsed = (DateTime.UtcNow - startDate).TotalMilliseconds;
 
-            if (elapsed >= 500)
+            if (elapsed >= 400)
             {
                 Logger.Debug("{2} query time (slow): {0}ms. Query: {1}",
                     Convert.ToInt32(elapsed),
@@ -1795,7 +1840,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
             }).ToArray());
         }
 
-        private Tuple<string,bool> MapOrderByField(string name)
+        private Tuple<string, bool> MapOrderByField(string name)
         {
             if (string.Equals(name, ItemSortBy.AirTime, StringComparison.OrdinalIgnoreCase))
             {
@@ -1838,6 +1883,14 @@ namespace MediaBrowser.Server.Implementations.Persistence
             {
                 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);
         }
@@ -2405,17 +2458,20 @@ namespace MediaBrowser.Server.Implementations.Persistence
                 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)
@@ -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)
             {
                 var clauses = new List<string>();
@@ -2967,6 +3037,11 @@ namespace MediaBrowser.Server.Implementations.Persistence
                 _deleteUserDataKeysCommand.Transaction = transaction;
                 _deleteUserDataKeysCommand.ExecuteNonQuery();
 
+                // Delete item values
+                _deleteItemValuesCommand.GetParameter(0).Value = id;
+                _deleteItemValuesCommand.Transaction = transaction;
+                _deleteItemValuesCommand.ExecuteNonQuery();
+
                 // Delete the item
                 _deleteItemCommand.GetParameter(0).Value = id;
                 _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)
         {
             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="ini-parser" version="2.2.4" 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="morelinq" version="1.4.0" 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" />
 </packages>

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

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

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

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Common.Internal</id>
-        <version>3.0.647</version>
+        <version>3.0.648</version>
         <title>MediaBrowser.Common.Internal</title>
         <authors>Luke</authors>
         <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>
         <copyright>Copyright © Emby 2013</copyright>
         <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>
     </metadata>
     <files>

+ 1 - 1
Nuget/MediaBrowser.Common.nuspec

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