浏览代码

Merge pull request #1082 from MediaBrowser/dev

3.0.5582.2
Luke 10 年之前
父节点
当前提交
73688e3fcc
共有 26 个文件被更改,包括 415 次插入329 次删除
  1. 4 4
      Emby.Drawing/Emby.Drawing.csproj
  2. 1 1
      Emby.Drawing/packages.config
  3. 1 0
      MediaBrowser.Api/MediaBrowser.Api.csproj
  4. 0 45
      MediaBrowser.Api/UserLibrary/UserLibraryService.cs
  5. 121 0
      MediaBrowser.Api/UserLibrary/UserViewsService.cs
  6. 3 3
      MediaBrowser.Controller/Entities/BaseItem.cs
  7. 4 1
      MediaBrowser.Controller/Entities/ICollectionFolder.cs
  8. 10 1
      MediaBrowser.Controller/Entities/UserViewBuilder.cs
  9. 47 9
      MediaBrowser.Controller/Library/NameExtensions.cs
  10. 8 1
      MediaBrowser.Controller/Persistence/IItemRepository.cs
  11. 27 20
      MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
  12. 1 1
      MediaBrowser.Model/Configuration/ServerConfiguration.cs
  13. 2 0
      MediaBrowser.Model/Configuration/UserConfiguration.cs
  14. 0 153
      MediaBrowser.Providers/FolderImages/DefaultImageProvider.cs
  15. 0 1
      MediaBrowser.Providers/MediaBrowser.Providers.csproj
  16. 5 1
      MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
  17. 4 1
      MediaBrowser.Server.Implementations/Dto/DtoService.cs
  18. 6 5
      MediaBrowser.Server.Implementations/Library/LibraryManager.cs
  19. 72 28
      MediaBrowser.Server.Implementations/Library/UserViewManager.cs
  20. 59 47
      MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
  21. 4 1
      MediaBrowser.Server.Implementations/Localization/Server/server.json
  22. 32 2
      MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs
  23. 1 1
      MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs
  24. 1 1
      MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
  25. 1 1
      MediaBrowser.ServerApplication/packages.config
  26. 1 1
      SharedVersion.cs

+ 4 - 4
Emby.Drawing/Emby.Drawing.csproj

@@ -34,7 +34,7 @@
   <ItemGroup>
   <ItemGroup>
     <Reference Include="ImageMagickSharp, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
     <Reference Include="ImageMagickSharp, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
       <SpecificVersion>False</SpecificVersion>
       <SpecificVersion>False</SpecificVersion>
-      <HintPath>..\packages\ImageMagickSharp.1.0.0.14\lib\net45\ImageMagickSharp.dll</HintPath>
+      <HintPath>..\packages\ImageMagickSharp.1.0.0.15\lib\net45\ImageMagickSharp.dll</HintPath>
     </Reference>
     </Reference>
     <Reference Include="System" />
     <Reference Include="System" />
     <Reference Include="System.Core" />
     <Reference Include="System.Core" />
@@ -65,9 +65,6 @@
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="ImageMagick\UnplayedCountIndicator.cs" />
     <Compile Include="ImageMagick\UnplayedCountIndicator.cs" />
   </ItemGroup>
   </ItemGroup>
-  <ItemGroup>
-    <None Include="packages.config" />
-  </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <EmbeddedResource Include="ImageMagick\fonts\MontserratLight.otf" />
     <EmbeddedResource Include="ImageMagick\fonts\MontserratLight.otf" />
     <EmbeddedResource Include="ImageMagick\fonts\robotoregular.ttf" />
     <EmbeddedResource Include="ImageMagick\fonts\robotoregular.ttf" />
@@ -87,6 +84,9 @@
       <Name>MediaBrowser.Model</Name>
       <Name>MediaBrowser.Model</Name>
     </ProjectReference>
     </ProjectReference>
   </ItemGroup>
   </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config" />
+  </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.
        Other similar extension points exist, see Microsoft.Common.targets.

+ 1 - 1
Emby.Drawing/packages.config

@@ -1,4 +1,4 @@
 <?xml version="1.0" encoding="utf-8"?>
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
 <packages>
-  <package id="ImageMagickSharp" version="1.0.0.14" targetFramework="net45" />
+  <package id="ImageMagickSharp" version="1.0.0.15" targetFramework="net45" />
 </packages>
 </packages>

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

@@ -156,6 +156,7 @@
     <Compile Include="UserLibrary\PlaystateService.cs" />
     <Compile Include="UserLibrary\PlaystateService.cs" />
     <Compile Include="UserLibrary\StudiosService.cs" />
     <Compile Include="UserLibrary\StudiosService.cs" />
     <Compile Include="UserLibrary\UserLibraryService.cs" />
     <Compile Include="UserLibrary\UserLibraryService.cs" />
+    <Compile Include="UserLibrary\UserViewsService.cs" />
     <Compile Include="UserLibrary\YearsService.cs" />
     <Compile Include="UserLibrary\YearsService.cs" />
     <Compile Include="UserService.cs" />
     <Compile Include="UserService.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />

+ 0 - 45
MediaBrowser.Api/UserLibrary/UserLibraryService.cs

@@ -38,20 +38,6 @@ namespace MediaBrowser.Api.UserLibrary
         public string Id { get; set; }
         public string Id { get; set; }
     }
     }
 
 
-    [Route("/Users/{UserId}/Views", "GET")]
-    public class GetUserViews : IReturn<QueryResult<BaseItemDto>>
-    {
-        /// <summary>
-        /// Gets or sets the user id.
-        /// </summary>
-        /// <value>The user id.</value>
-        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
-        public string UserId { get; set; }
-
-        [ApiMember(Name = "IncludeExternalContent", Description = "Whether or not to include external views such as channels or live tv", IsRequired = true, DataType = "boolean", ParameterType = "query", Verb = "POST")]
-        public bool? IncludeExternalContent { get; set; }
-    }
-
     /// <summary>
     /// <summary>
     /// Class GetItem
     /// Class GetItem
     /// </summary>
     /// </summary>
@@ -345,37 +331,6 @@ namespace MediaBrowser.Api.UserLibrary
             return ToOptimizedResult(dtos.ToList());
             return ToOptimizedResult(dtos.ToList());
         }
         }
 
 
-        public async Task<object> Get(GetUserViews request)
-        {
-            var user = _userManager.GetUserById(request.UserId);
-
-            var query = new UserViewQuery
-            {
-                UserId = request.UserId
-
-            };
-
-            if (request.IncludeExternalContent.HasValue)
-            {
-                query.IncludeExternalContent = request.IncludeExternalContent.Value;
-            }
-
-            var folders = await _userViewManager.GetUserViews(query, CancellationToken.None).ConfigureAwait(false);
-
-            var dtoOptions = GetDtoOptions(request);
-
-            var dtos = folders.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user))
-                .ToArray();
-
-            var result = new QueryResult<BaseItemDto>
-            {
-                Items = dtos,
-                TotalRecordCount = dtos.Length
-            };
-
-            return ToOptimizedResult(result);
-        }
-
         private List<BaseItemDto> GetAsync(GetSpecialFeatures request)
         private List<BaseItemDto> GetAsync(GetSpecialFeatures request)
         {
         {
             var user = _userManager.GetUserById(request.UserId);
             var user = _userManager.GetUserById(request.UserId);

+ 121 - 0
MediaBrowser.Api/UserLibrary/UserViewsService.cs

@@ -0,0 +1,121 @@
+using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Library;
+using MediaBrowser.Model.Querying;
+using ServiceStack;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Api.UserLibrary
+{
+    [Route("/Users/{UserId}/Views", "GET")]
+    public class GetUserViews : IReturn<QueryResult<BaseItemDto>>
+    {
+        /// <summary>
+        /// Gets or sets the user id.
+        /// </summary>
+        /// <value>The user id.</value>
+        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+        public string UserId { get; set; }
+
+        [ApiMember(Name = "IncludeExternalContent", Description = "Whether or not to include external views such as channels or live tv", IsRequired = true, DataType = "boolean", ParameterType = "query", Verb = "POST")]
+        public bool? IncludeExternalContent { get; set; }
+    }
+
+    [Route("/Users/{UserId}/SpecialViewOptions", "GET")]
+    public class GetSpecialViewOptions : IReturn<List<SpecialViewOption>>
+    {
+        /// <summary>
+        /// Gets or sets the user id.
+        /// </summary>
+        /// <value>The user id.</value>
+        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+        public string UserId { get; set; }
+    }
+
+    public class UserViewsService : BaseApiService
+    {
+        private readonly IUserManager _userManager;
+        private readonly IUserViewManager _userViewManager;
+        private readonly IDtoService _dtoService;
+
+        public UserViewsService(IUserManager userManager, IUserViewManager userViewManager, IDtoService dtoService)
+        {
+            _userManager = userManager;
+            _userViewManager = userViewManager;
+            _dtoService = dtoService;
+        }
+
+        public async Task<object> Get(GetUserViews request)
+        {
+            var query = new UserViewQuery
+            {
+                UserId = request.UserId
+            };
+
+            if (request.IncludeExternalContent.HasValue)
+            {
+                query.IncludeExternalContent = request.IncludeExternalContent.Value;
+            }
+
+            var folders = await _userViewManager.GetUserViews(query, CancellationToken.None).ConfigureAwait(false);
+
+            var dtoOptions = GetDtoOptions(request);
+
+            var user = _userManager.GetUserById(request.UserId);
+
+            var dtos = folders.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user))
+                .ToArray();
+
+            var result = new QueryResult<BaseItemDto>
+            {
+                Items = dtos,
+                TotalRecordCount = dtos.Length
+            };
+
+            return ToOptimizedResult(result);
+        }
+
+        public async Task<object> Get(GetSpecialViewOptions request)
+        {
+            var user = _userManager.GetUserById(request.UserId);
+
+            var views = user.RootFolder
+                .GetChildren(user, true)
+                .OfType<CollectionFolder>()
+                .Where(i => IsEligibleForSpecialView(i))
+                .ToList();
+
+            var list = views
+                .Select(i => new SpecialViewOption
+                {
+                    Name = i.Name,
+                    Id = i.Id.ToString("N")
+
+                })
+            .OrderBy(i => i.Name)
+            .ToList();
+
+            return ToOptimizedResult(list);
+        }
+
+        private bool IsEligibleForSpecialView(CollectionFolder view)
+        {
+            var types = new[] { CollectionType.Movies, CollectionType.TvShows, CollectionType.Games, CollectionType.Music };
+
+            return types.Contains(view.CollectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase);
+        }
+    }
+
+    class SpecialViewOption
+    {
+        public string Name { get; set; }
+        public string Id { get; set; }
+    }
+}

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

@@ -1894,12 +1894,12 @@ namespace MediaBrowser.Controller.Entities
             return video.RefreshMetadata(newOptions, cancellationToken);
             return video.RefreshMetadata(newOptions, cancellationToken);
         }
         }
 
 
-        public string GetEtag()
+        public string GetEtag(User user)
         {
         {
-            return string.Join("|", GetEtagValues().ToArray()).GetMD5().ToString("N");
+            return string.Join("|", GetEtagValues(user).ToArray()).GetMD5().ToString("N");
         }
         }
 
 
-        protected virtual List<string> GetEtagValues()
+        protected virtual List<string> GetEtagValues(User user)
         {
         {
             return new List<string>
             return new List<string>
             {
             {

+ 4 - 1
MediaBrowser.Controller/Entities/ICollectionFolder.cs

@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
 
 
 namespace MediaBrowser.Controller.Entities
 namespace MediaBrowser.Controller.Entities
 {
 {
@@ -9,6 +10,8 @@ namespace MediaBrowser.Controller.Entities
     {
     {
         string CollectionType { get; }
         string CollectionType { get; }
         string Path { get; }
         string Path { get; }
+        string Name { get; }
+        Guid Id { get; }
         IEnumerable<string> PhysicalLocations { get; }
         IEnumerable<string> PhysicalLocations { get; }
     }
     }
 }
 }

+ 10 - 1
MediaBrowser.Controller/Entities/UserViewBuilder.cs

@@ -247,7 +247,16 @@ namespace MediaBrowser.Controller.Entities
                     return GetFavoriteSongs(queryParent, user, query);
                     return GetFavoriteSongs(queryParent, user, query);
 
 
                 default:
                 default:
-                    return GetResult(GetMediaFolders(user).SelectMany(i => i.GetChildren(user, true)), queryParent, query);
+                {
+                    if (queryParent is UserView)
+                    {
+                        return GetResult(GetMediaFolders(user).SelectMany(i => i.GetChildren(user, true)), queryParent, query);
+                    }
+                    else
+                    {
+                        return GetResult(queryParent.GetChildren(user, true), queryParent, query);
+                    }
+                }
             }
             }
         }
         }
 
 

+ 47 - 9
MediaBrowser.Controller/Library/NameExtensions.cs

@@ -2,40 +2,78 @@
 using MoreLinq;
 using MoreLinq;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Globalization;
 using System.Linq;
 using System.Linq;
 
 
 namespace MediaBrowser.Controller.Library
 namespace MediaBrowser.Controller.Library
 {
 {
     public static class NameExtensions
     public static class NameExtensions
     {
     {
-        public static bool AreEqual(string name1, string name2)
+        public static bool AreEqual(string x, string y)
         {
         {
-            name1 = NormalizeForComparison(name1);
-            name2 = NormalizeForComparison(name2);
+            if (string.IsNullOrWhiteSpace(x) && string.IsNullOrWhiteSpace(y))
+            {
+                return true;
+            }
 
 
-            return string.Equals(name1, name2, StringComparison.OrdinalIgnoreCase);
+            return string.Compare(x, y, CultureInfo.InvariantCulture, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace) == 0;
         }
         }
 
 
-        public static bool EqualsAny(IEnumerable<string> names, string name)
+        public static bool EqualsAny(IEnumerable<string> names, string x)
         {
         {
-            name = NormalizeForComparison(name);
+            x = NormalizeForComparison(x);
 
 
-            return names.Any(i => string.Equals(NormalizeForComparison(i), name, StringComparison.OrdinalIgnoreCase));
+            return names.Any(y => string.Compare(x, y, CultureInfo.InvariantCulture, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace) == 0);
         }
         }
 
 
         private static string NormalizeForComparison(string name)
         private static string NormalizeForComparison(string name)
         {
         {
-            if (string.IsNullOrWhiteSpace(name))
+            if (name == null)
+            {
+                return string.Empty;
+            }
+
+            return name;
+            //return name.RemoveDiacritics();
+        }
+
+        private static string RemoveDiacritics(string name)
+        {
+            if (name == null)
             {
             {
                 return string.Empty;
                 return string.Empty;
             }
             }
 
 
+            //return name;
             return name.RemoveDiacritics();
             return name.RemoveDiacritics();
         }
         }
 
 
         public static IEnumerable<string> DistinctNames(this IEnumerable<string> names)
         public static IEnumerable<string> DistinctNames(this IEnumerable<string> names)
         {
         {
-            return names.DistinctBy(NormalizeForComparison, StringComparer.OrdinalIgnoreCase);
+            return names.DistinctBy(RemoveDiacritics, StringComparer.OrdinalIgnoreCase);
+        }
+    }
+
+    class TextComparer : IComparer<string>, IEqualityComparer<string>
+    {
+        public int Compare(string x, string y)
+        {
+            if (string.IsNullOrWhiteSpace(x) && string.IsNullOrWhiteSpace(y))
+            {
+                return 0;
+            }
+
+            return string.Compare(x, y, CultureInfo.InvariantCulture, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace);
+        }
+
+        public bool Equals(string x, string y)
+        {
+            return Compare(x, y) == 0;
+        }
+
+        public int GetHashCode(string obj)
+        {
+            return (obj ?? string.Empty).GetHashCode();
         }
         }
     }
     }
 }
 }

+ 8 - 1
MediaBrowser.Controller/Persistence/IItemRepository.cs

@@ -107,7 +107,7 @@ namespace MediaBrowser.Controller.Persistence
         /// </summary>
         /// </summary>
         /// <param name="type">The type.</param>
         /// <param name="type">The type.</param>
         /// <returns>IEnumerable{Guid}.</returns>
         /// <returns>IEnumerable{Guid}.</returns>
-        IEnumerable<Guid> GetItemsOfType(Type type);
+        IEnumerable<Guid> GetItemIdsOfType(Type type);
 
 
         /// <summary>
         /// <summary>
         /// Saves the children.
         /// Saves the children.
@@ -133,6 +133,13 @@ namespace MediaBrowser.Controller.Persistence
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
         Task SaveMediaStreams(Guid id, IEnumerable<MediaStream> streams, CancellationToken cancellationToken);
         Task SaveMediaStreams(Guid id, IEnumerable<MediaStream> streams, CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Gets the type of the items of.
+        /// </summary>
+        /// <param name="type">The type.</param>
+        /// <returns>IEnumerable&lt;BaseItem&gt;.</returns>
+        IEnumerable<BaseItem> GetItemsOfType(Type type);
     }
     }
 }
 }
 
 

+ 27 - 20
MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs

@@ -489,32 +489,37 @@ namespace MediaBrowser.MediaEncoding.Encoder
             await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
             await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
 
 
             var processWrapper = new ProcessWrapper(process, this);
             var processWrapper = new ProcessWrapper(process, this);
-
-            StartProcess(processWrapper);
+            bool ranToCompletion;
 
 
             var memoryStream = new MemoryStream();
             var memoryStream = new MemoryStream();
 
 
+            try
+            {
+                StartProcess(processWrapper);
+
 #pragma warning disable 4014
 #pragma warning disable 4014
-            // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
-            process.StandardOutput.BaseStream.CopyToAsync(memoryStream);
+                // Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
+                process.StandardOutput.BaseStream.CopyToAsync(memoryStream);
 #pragma warning restore 4014
 #pragma warning restore 4014
 
 
-            // MUST read both stdout and stderr asynchronously or a deadlock may occurr
-            process.BeginErrorReadLine();
+                // MUST read both stdout and stderr asynchronously or a deadlock may occurr
+                process.BeginErrorReadLine();
+
+                ranToCompletion = process.WaitForExit(10000);
 
 
-            var ranToCompletion = process.WaitForExit(10000);
+                if (!ranToCompletion)
+                {
+                    StopProcess(processWrapper, 1000, false);
+                }
 
 
-            if (!ranToCompletion)
+            }
+            finally
             {
             {
-                StopProcess(processWrapper, 1000, false);
+                resourcePool.Release();
             }
             }
 
 
-            resourcePool.Release();
-
             var exitCode = ranToCompletion ? processWrapper.ExitCode ?? 0 : -1;
             var exitCode = ranToCompletion ? processWrapper.ExitCode ?? 0 : -1;
 
 
-            process.Dispose();
-
             if (exitCode == -1 || memoryStream.Length == 0)
             if (exitCode == -1 || memoryStream.Length == 0)
             {
             {
                 memoryStream.Dispose();
                 memoryStream.Dispose();
@@ -594,7 +599,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
 
             await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
             await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
 
 
-            bool ranToCompletion;
+            bool ranToCompletion = false;
 
 
             var processWrapper = new ProcessWrapper(process, this);
             var processWrapper = new ProcessWrapper(process, this);
 
 
@@ -609,19 +614,23 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 bool isResponsive = true;
                 bool isResponsive = true;
                 int lastCount = 0;
                 int lastCount = 0;
 
 
-                while (isResponsive && !process.WaitForExit(30000))
+                while (isResponsive)
                 {
                 {
+                    if (process.WaitForExit(30000))
+                    {
+                        ranToCompletion = true;
+                        break;
+                    }
+
                     cancellationToken.ThrowIfCancellationRequested();
                     cancellationToken.ThrowIfCancellationRequested();
 
 
-                    int jpegCount = Directory.GetFiles(targetDirectory)
+                    var jpegCount = Directory.GetFiles(targetDirectory)
                         .Count(i => string.Equals(Path.GetExtension(i), ".jpg", StringComparison.OrdinalIgnoreCase));
                         .Count(i => string.Equals(Path.GetExtension(i), ".jpg", StringComparison.OrdinalIgnoreCase));
 
 
                     isResponsive = (jpegCount > lastCount);
                     isResponsive = (jpegCount > lastCount);
                     lastCount = jpegCount;
                     lastCount = jpegCount;
                 }
                 }
 
 
-                ranToCompletion = process.HasExited;
-
                 if (!ranToCompletion)
                 if (!ranToCompletion)
                 {
                 {
                     StopProcess(processWrapper, 1000, false);
                     StopProcess(processWrapper, 1000, false);
@@ -634,8 +643,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
 
             var exitCode = ranToCompletion ? processWrapper.ExitCode ?? 0 : -1;
             var exitCode = ranToCompletion ? processWrapper.ExitCode ?? 0 : -1;
 
 
-            process.Dispose();
-
             if (exitCode == -1)
             if (exitCode == -1)
             {
             {
                 var msg = string.Format("ffmpeg image extraction failed for {0}", inputArgument);
                 var msg = string.Format("ffmpeg image extraction failed for {0}", inputArgument);

+ 1 - 1
MediaBrowser.Model/Configuration/ServerConfiguration.cs

@@ -49,7 +49,7 @@ namespace MediaBrowser.Model.Configuration
         /// </summary>
         /// </summary>
         /// <value><c>true</c> if [enable user specific user views]; otherwise, <c>false</c>.</value>
         /// <value><c>true</c> if [enable user specific user views]; otherwise, <c>false</c>.</value>
         public bool EnableUserSpecificUserViews { get; set; }
         public bool EnableUserSpecificUserViews { get; set; }
-        
+
         /// <summary>
         /// <summary>
         /// Gets or sets the value pointing to the file system where the ssl certiifcate is located..
         /// Gets or sets the value pointing to the file system where the ssl certiifcate is located..
         /// </summary>
         /// </summary>

+ 2 - 0
MediaBrowser.Model/Configuration/UserConfiguration.cs

@@ -46,6 +46,7 @@ namespace MediaBrowser.Model.Configuration
         public bool EnableCinemaMode { get; set; }
         public bool EnableCinemaMode { get; set; }
 
 
         public string[] LatestItemsExcludes { get; set; }
         public string[] LatestItemsExcludes { get; set; }
+        public string[] PlainFolderViews { get; set; }
 
 
         public bool HidePlayedInLatest { get; set; }
         public bool HidePlayedInLatest { get; set; }
 
 
@@ -62,6 +63,7 @@ namespace MediaBrowser.Model.Configuration
             DisplayChannelsWithinViews = new string[] { };
             DisplayChannelsWithinViews = new string[] { };
 
 
             ExcludeFoldersFromGrouping = new string[] { };
             ExcludeFoldersFromGrouping = new string[] { };
+            PlainFolderViews = new string[] { };
             DisplayCollectionsView = true;
             DisplayCollectionsView = true;
 
 
             IncludeTrailersInSuggestions = true;
             IncludeTrailersInSuggestions = true;

+ 0 - 153
MediaBrowser.Providers/FolderImages/DefaultImageProvider.cs

@@ -1,153 +0,0 @@
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Providers;
-using MediaBrowser.Providers.Genres;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Providers.FolderImages
-{
-    public class DefaultImageProvider : IRemoteImageProvider, IHasItemChangeMonitor, IHasOrder
-    {
-        private readonly IHttpClient _httpClient;
-
-        public DefaultImageProvider(IHttpClient httpClient)
-        {
-            _httpClient = httpClient;
-        }
-
-        public IEnumerable<ImageType> GetSupportedImages(IHasImages item)
-        {
-            return new List<ImageType>
-            {
-                ImageType.Primary,
-                ImageType.Thumb
-            };
-        }
-
-        public Task<IEnumerable<RemoteImageInfo>> GetImages(IHasImages item, CancellationToken cancellationToken)
-        {
-            var view = item as UserView;
-
-            if (view != null)
-            {
-                return GetImages(view.ViewType, view.ParentId != Guid.Empty, cancellationToken);
-            }
-
-            var folder = (ICollectionFolder)item;
-            return GetImages(folder.CollectionType, false, cancellationToken);
-        }
-
-        private Task<IEnumerable<RemoteImageInfo>> GetImages(string viewType, bool isSubView, CancellationToken cancellationToken)
-        {
-            var url = GetImageUrl(viewType, isSubView);
-            var list = new List<RemoteImageInfo>();
-
-            if (!string.IsNullOrWhiteSpace(url))
-            {
-                list.AddRange(new List<RemoteImageInfo>
-                {
-                    new RemoteImageInfo
-                    {
-                        ProviderName = Name,
-                        Url = url,
-                        Type = ImageType.Primary
-                    },
-
-                    new RemoteImageInfo
-                    {
-                        ProviderName = Name,
-                        Url = url,
-                        Type = ImageType.Thumb
-                    }
-                });
-            }
-
-            return Task.FromResult<IEnumerable<RemoteImageInfo>>(list);
-        }
-
-        private string GetImageUrl(string viewType, bool isSubView)
-        {
-            const string urlPrefix = "https://raw.githubusercontent.com/MediaBrowser/MediaBrowser.Resources/master/images/folders/";
-
-            if (string.Equals(viewType, CollectionType.Books, StringComparison.OrdinalIgnoreCase))
-            {
-                return null;
-            }
-            if (string.Equals(viewType, CollectionType.Games, StringComparison.OrdinalIgnoreCase))
-            {
-                return null;
-            }
-            if (string.Equals(viewType, CollectionType.Music, StringComparison.OrdinalIgnoreCase))
-            {
-                return null;
-            }
-            if (string.Equals(viewType, CollectionType.Photos, StringComparison.OrdinalIgnoreCase))
-            {
-                return null;
-            }
-            if (string.Equals(viewType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
-            {
-                return null;
-            }
-            if (string.Equals(viewType, CollectionType.Channels, StringComparison.OrdinalIgnoreCase))
-            {
-                return null;
-            }
-            if (string.Equals(viewType, CollectionType.LiveTv, StringComparison.OrdinalIgnoreCase))
-            {
-                return null;
-            }
-            if (string.Equals(viewType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase))
-            {
-                return null;
-            }
-
-            if (isSubView)
-            {
-                return null;
-            }
-
-            return urlPrefix + "generic.png";
-        }
-
-        public string Name
-        {
-            get { return "Default Image Provider"; }
-        }
-
-        public bool Supports(IHasImages item)
-        {
-            return item is ICollectionFolder || item is UserView;
-        }
-
-        public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
-        {
-            return _httpClient.GetResponse(new HttpRequestOptions
-            {
-                CancellationToken = cancellationToken,
-                Url = url,
-                ResourcePool = GenreImageProvider.ImageDownloadResourcePool
-            });
-        }
-
-        public bool HasChanged(IHasMetadata item, MetadataStatus status, IDirectoryService directoryService)
-        {
-            return GetSupportedImages(item).Any(i => !item.HasImage(i));
-        }
-
-        public int Order
-        {
-            get
-            {
-                // Run after the dynamic image provider
-                return 1;
-            }
-        }
-    }
-}

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

@@ -83,7 +83,6 @@
     <Compile Include="BoxSets\MovieDbBoxSetProvider.cs" />
     <Compile Include="BoxSets\MovieDbBoxSetProvider.cs" />
     <Compile Include="Channels\ChannelMetadataService.cs" />
     <Compile Include="Channels\ChannelMetadataService.cs" />
     <Compile Include="Chapters\ChapterManager.cs" />
     <Compile Include="Chapters\ChapterManager.cs" />
-    <Compile Include="FolderImages\DefaultImageProvider.cs" />
     <Compile Include="Folders\FolderMetadataService.cs" />
     <Compile Include="Folders\FolderMetadataService.cs" />
     <Compile Include="Channels\AudioChannelItemMetadataService.cs" />
     <Compile Include="Channels\AudioChannelItemMetadataService.cs" />
     <Compile Include="Folders\UserViewMetadataService.cs" />
     <Compile Include="Folders\UserViewMetadataService.cs" />

+ 5 - 1
MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs

@@ -6,6 +6,7 @@ using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Drawing;
 using MediaBrowser.Model.Drawing;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.IO;
 using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.MediaInfo;
 using MediaBrowser.Model.MediaInfo;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
@@ -20,13 +21,15 @@ namespace MediaBrowser.Providers.MediaInfo
         private readonly IMediaEncoder _mediaEncoder;
         private readonly IMediaEncoder _mediaEncoder;
         private readonly IServerConfigurationManager _config;
         private readonly IServerConfigurationManager _config;
         private readonly ILibraryManager _libraryManager;
         private readonly ILibraryManager _libraryManager;
+        private readonly ILogger _logger;
 
 
-        public VideoImageProvider(IIsoManager isoManager, IMediaEncoder mediaEncoder, IServerConfigurationManager config, ILibraryManager libraryManager)
+        public VideoImageProvider(IIsoManager isoManager, IMediaEncoder mediaEncoder, IServerConfigurationManager config, ILibraryManager libraryManager, ILogger logger)
         {
         {
             _isoManager = isoManager;
             _isoManager = isoManager;
             _mediaEncoder = mediaEncoder;
             _mediaEncoder = mediaEncoder;
             _config = config;
             _config = config;
             _libraryManager = libraryManager;
             _libraryManager = libraryManager;
+            _logger = logger;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -74,6 +77,7 @@ namespace MediaBrowser.Providers.MediaInfo
             // Can't extract if we didn't find a video stream in the file
             // Can't extract if we didn't find a video stream in the file
             if (!video.DefaultVideoStreamIndex.HasValue)
             if (!video.DefaultVideoStreamIndex.HasValue)
             {
             {
+                _logger.Debug("Skipping image extraction due to missing DefaultVideoStreamIndex for {0}.", video.Path ?? string.Empty);
                 return Task.FromResult(new DynamicImageResponse { HasImage = false });
                 return Task.FromResult(new DynamicImageResponse { HasImage = false });
             }
             }
 
 

+ 4 - 1
MediaBrowser.Server.Implementations/Dto/DtoService.cs

@@ -357,7 +357,10 @@ namespace MediaBrowser.Server.Implementations.Dto
                     : item.CanDownload(user);
                     : item.CanDownload(user);
             }
             }
 
 
-
+            if (fields.Contains(ItemFields.Etag))
+            {
+                dto.Etag = item.GetEtag(user);
+            }
 
 
             return dto;
             return dto;
         }
         }

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

@@ -1678,11 +1678,6 @@ namespace MediaBrowser.Server.Implementations.Library
                 throw new ArgumentNullException("name");
                 throw new ArgumentNullException("name");
             }
             }
 
 
-            if (string.IsNullOrWhiteSpace(viewType))
-            {
-                throw new ArgumentNullException("viewType");
-            }
-
             var id = GetNewItemId("37_namedview_" + name + user.Id.ToString("N") + (parentId ?? string.Empty), typeof(UserView));
             var id = GetNewItemId("37_namedview_" + name + user.Id.ToString("N") + (parentId ?? string.Empty), typeof(UserView));
 
 
             var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N"));
             var path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N"));
@@ -1716,6 +1711,12 @@ namespace MediaBrowser.Server.Implementations.Library
                 isNew = true;
                 isNew = true;
             }
             }
 
 
+            if (!string.Equals(viewType, item.ViewType, StringComparison.OrdinalIgnoreCase))
+            {
+                item.ViewType = viewType;
+                await item.UpdateToRepository(ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false);
+            }
+
             var refresh = isNew || (DateTime.UtcNow - item.DateLastSaved).TotalHours >= 12;
             var refresh = isNew || (DateTime.UtcNow - item.DateLastSaved).TotalHours >= 12;
 
 
             if (refresh)
             if (refresh)

+ 72 - 28
MediaBrowser.Server.Implementations/Library/UserViewManager.cs

@@ -53,6 +53,7 @@ namespace MediaBrowser.Server.Implementations.Library
                 .ToList();
                 .ToList();
 
 
             var excludeFolderIds = user.Configuration.ExcludeFoldersFromGrouping.Select(i => new Guid(i)).ToList();
             var excludeFolderIds = user.Configuration.ExcludeFoldersFromGrouping.Select(i => new Guid(i)).ToList();
+            var plainFolderIds = user.Configuration.PlainFolderViews.Select(i => new Guid(i)).ToList();
 
 
             var standaloneFolders = folders
             var standaloneFolders = folders
                 .Where(i => UserView.IsExcludedFromGrouping(i) || excludeFolderIds.Contains(i.Id))
                 .Where(i => UserView.IsExcludedFromGrouping(i) || excludeFolderIds.Contains(i.Id))
@@ -72,13 +73,17 @@ namespace MediaBrowser.Server.Implementations.Library
                     var collectionFolder = folder as ICollectionFolder;
                     var collectionFolder = folder as ICollectionFolder;
                     var folderViewType = collectionFolder == null ? null : collectionFolder.CollectionType;
                     var folderViewType = collectionFolder == null ? null : collectionFolder.CollectionType;
 
 
-                    if (string.IsNullOrWhiteSpace(folderViewType))
+                    if (plainFolderIds.Contains(folder.Id))
                     {
                     {
-                        list.Add(folder);
+                        list.Add(await GetUserView(folder.Id, folder.Name, folderViewType, false, string.Empty, user, cancellationToken).ConfigureAwait(false));
+                    }
+                    else if (!string.IsNullOrWhiteSpace(folderViewType))
+                    {
+                        list.Add(await GetUserView(folder.Id, folder.Name, folderViewType, true, string.Empty, user, cancellationToken).ConfigureAwait(false));
                     }
                     }
                     else
                     else
                     {
                     {
-                        list.Add(await GetUserView(folder.Id, folder.Name, folderViewType, string.Empty, user, cancellationToken).ConfigureAwait(false));
+                        list.Add(folder);
                     }
                     }
                 }
                 }
             }
             }
@@ -87,44 +92,57 @@ namespace MediaBrowser.Server.Implementations.Library
                 list.AddRange(standaloneFolders);
                 list.AddRange(standaloneFolders);
             }
             }
 
 
-            if (foldersWithViewTypes.Any(i => string.Equals(i.CollectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase)) ||
-                foldersWithViewTypes.Any(i => string.IsNullOrWhiteSpace(i.CollectionType)))
+            var parents = foldersWithViewTypes.Where(i => string.Equals(i.CollectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) || string.IsNullOrWhiteSpace(i.CollectionType))
+                .ToList();
+
+            if (parents.Count > 0)
             {
             {
-                list.Add(await GetUserView(CollectionType.TvShows, string.Empty, user, cancellationToken).ConfigureAwait(false));
+                list.Add(await GetUserView(parents, CollectionType.TvShows, string.Empty, user, cancellationToken).ConfigureAwait(false));
             }
             }
 
 
-            if (foldersWithViewTypes.Any(i => string.Equals(i.CollectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase)) ||
-                foldersWithViewTypes.Any(i => string.Equals(i.CollectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase)))
+            parents = foldersWithViewTypes.Where(i => string.Equals(i.CollectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase) || string.Equals(i.CollectionType, CollectionType.MusicVideos, StringComparison.OrdinalIgnoreCase) || string.IsNullOrWhiteSpace(i.CollectionType))
+                .ToList();
+
+            if (parents.Count > 0)
             {
             {
-                list.Add(await GetUserView(CollectionType.Music, string.Empty, user, cancellationToken).ConfigureAwait(false));
+                list.Add(await GetUserView(parents, CollectionType.Music, string.Empty, user, cancellationToken).ConfigureAwait(false));
             }
             }
 
 
-            if (foldersWithViewTypes.Any(i => string.Equals(i.CollectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase)) ||
-                foldersWithViewTypes.Any(i => string.IsNullOrWhiteSpace(i.CollectionType)))
+            parents = foldersWithViewTypes.Where(i => string.Equals(i.CollectionType, CollectionType.Movies, StringComparison.OrdinalIgnoreCase) || string.IsNullOrWhiteSpace(i.CollectionType))
+               .ToList();
+
+            if (parents.Count > 0)
             {
             {
-                list.Add(await GetUserView(CollectionType.Movies, string.Empty, user, cancellationToken).ConfigureAwait(false));
+                list.Add(await GetUserView(parents, CollectionType.Movies, string.Empty, user, cancellationToken).ConfigureAwait(false));
             }
             }
 
 
-            if (foldersWithViewTypes.Any(i => string.Equals(i.CollectionType, CollectionType.Games, StringComparison.OrdinalIgnoreCase)))
+            parents = foldersWithViewTypes.Where(i => string.Equals(i.CollectionType, CollectionType.Games, StringComparison.OrdinalIgnoreCase))
+               .ToList();
+
+            if (parents.Count > 0)
             {
             {
-                list.Add(await GetUserView(CollectionType.Games, string.Empty, user, cancellationToken).ConfigureAwait(false));
+                list.Add(await GetUserView(parents, CollectionType.Games, string.Empty, user, cancellationToken).ConfigureAwait(false));
             }
             }
 
 
-            if (foldersWithViewTypes.Any(i => string.Equals(i.CollectionType, CollectionType.BoxSets, StringComparison.OrdinalIgnoreCase)))
+            parents = foldersWithViewTypes.Where(i => string.Equals(i.CollectionType, CollectionType.BoxSets, StringComparison.OrdinalIgnoreCase))
+               .ToList();
+
+            if (parents.Count > 0)
             {
             {
-                //list.Add(_collectionManager.GetCollectionsFolder(user.Id.ToString("N")));
-                list.Add(await GetUserView(CollectionType.BoxSets, string.Empty, user, cancellationToken).ConfigureAwait(false));
+                list.Add(await GetUserView(parents, CollectionType.BoxSets, string.Empty, user, cancellationToken).ConfigureAwait(false));
             }
             }
 
 
-            if (foldersWithViewTypes.Any(i => string.Equals(i.CollectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase)))
+            parents = foldersWithViewTypes.Where(i => string.Equals(i.CollectionType, CollectionType.Playlists, StringComparison.OrdinalIgnoreCase))
+               .ToList();
+
+            if (parents.Count > 0)
             {
             {
-                //list.Add(_playlists.GetPlaylistsFolder(user.Id.ToString("N")));
-                list.Add(await GetUserView(CollectionType.Playlists, string.Empty, user, cancellationToken).ConfigureAwait(false));
+                list.Add(await GetUserView(parents, CollectionType.Playlists, string.Empty, user, cancellationToken).ConfigureAwait(false));
             }
             }
 
 
             if (user.Configuration.DisplayFoldersView)
             if (user.Configuration.DisplayFoldersView)
             {
             {
-                list.Add(await GetUserView(CollectionType.Folders, "zz_" + CollectionType.Folders, user, cancellationToken).ConfigureAwait(false));
+                list.Add(await GetUserView(new List<ICollectionFolder>(), CollectionType.Folders, "zz_" + CollectionType.Folders, user, cancellationToken).ConfigureAwait(false));
             }
             }
 
 
             if (query.IncludeExternalContent)
             if (query.IncludeExternalContent)
@@ -151,7 +169,7 @@ namespace MediaBrowser.Server.Implementations.Library
                 if (_liveTvManager.GetEnabledUsers().Select(i => i.Id.ToString("N")).Contains(query.UserId))
                 if (_liveTvManager.GetEnabledUsers().Select(i => i.Id.ToString("N")).Contains(query.UserId))
                 {
                 {
                     //list.Add(await _liveTvManager.GetInternalLiveTvFolder(query.UserId, cancellationToken).ConfigureAwait(false));
                     //list.Add(await _liveTvManager.GetInternalLiveTvFolder(query.UserId, cancellationToken).ConfigureAwait(false));
-                    list.Add(await GetUserView(CollectionType.LiveTv, string.Empty, user, cancellationToken).ConfigureAwait(false));
+                    list.Add(await GetUserView(new List<ICollectionFolder>(), CollectionType.LiveTv, string.Empty, user, cancellationToken).ConfigureAwait(false));
                 }
                 }
             }
             }
 
 
@@ -182,16 +200,42 @@ namespace MediaBrowser.Server.Implementations.Library
             return GetUserSubView(name, parentId, type, user, sortName, cancellationToken);
             return GetUserSubView(name, parentId, type, user, sortName, cancellationToken);
         }
         }
 
 
-        public Task<UserView> GetUserView(string type, string sortName, User user, CancellationToken cancellationToken)
+        public async Task<UserView> GetUserView(List<ICollectionFolder> parents, string viewType, string sortName, User user, CancellationToken cancellationToken)
         {
         {
-            var name = _localizationManager.GetLocalizedString("ViewType" + type);
+            if (parents.Count == 1 && parents.All(i => string.Equals(i.CollectionType, viewType, StringComparison.OrdinalIgnoreCase)))
+            {
+                var name = parents[0].Name;
+                var parentId = parents[0].Id;
 
 
-            return _libraryManager.GetNamedView(user, name, type, sortName, cancellationToken);
+                var enableRichView = !user.Configuration.PlainFolderViews.Contains(parentId.ToString("N"), StringComparer.OrdinalIgnoreCase);
+
+                if (_config.Configuration.EnableUserSpecificUserViews)
+                {
+                    viewType = enableRichView ? viewType : null;
+                    var view = await _libraryManager.GetNamedView(user, name, viewType, sortName, cancellationToken).ConfigureAwait(false);
+
+                    if (view.ParentId != parentId)
+                    {
+                        view.ParentId = parentId;
+                        await view.UpdateToRepository(ItemUpdateType.MetadataEdit, cancellationToken).ConfigureAwait(false);
+                    }
+                    return view;
+                }
+
+                viewType = enableRichView ? viewType : CollectionType.Folders;
+                return await _libraryManager.GetNamedView(user, name, viewType, sortName, cancellationToken).ConfigureAwait(false);
+            }
+            else
+            {
+                var name = _localizationManager.GetLocalizedString("ViewType" + viewType);
+
+                return await _libraryManager.GetNamedView(user, name, viewType, sortName, cancellationToken).ConfigureAwait(false);
+            }
         }
         }
 
 
-        public Task<UserView> GetUserView(Guid parentId, string name, string type, string sortName, User user, CancellationToken cancellationToken)
+        public Task<UserView> GetUserView(Guid parentId, string name, string viewType, bool enableRichView, string sortName, User user, CancellationToken cancellationToken)
         {
         {
-            return _libraryManager.GetNamedView(user, name, parentId.ToString("N"), type, sortName, cancellationToken);
+            return _libraryManager.GetNamedView(user, name, parentId.ToString("N"), viewType, sortName, cancellationToken);
         }
         }
 
 
         public List<Tuple<BaseItem, List<BaseItem>>> GetLatestItems(LatestItemsQuery request)
         public List<Tuple<BaseItem, List<BaseItem>>> GetLatestItems(LatestItemsQuery request)
@@ -317,7 +361,7 @@ namespace MediaBrowser.Server.Implementations.Library
                 .RootFolder
                 .RootFolder
                 .GetRecursiveChildren(filter);
                 .GetRecursiveChildren(filter);
         }
         }
-        
+
         private IEnumerable<BaseItem> GetItemsConfiguredForLatest(User user, Func<BaseItem, bool> filter)
         private IEnumerable<BaseItem> GetItemsConfiguredForLatest(User user, Func<BaseItem, bool> filter)
         {
         {
             // Avoid implicitly captured closure
             // Avoid implicitly captured closure

+ 59 - 47
MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs

@@ -54,11 +54,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             new ConcurrentDictionary<string, LiveStreamData>();
             new ConcurrentDictionary<string, LiveStreamData>();
 
 
         private List<Guid> _channelIdList = new List<Guid>();
         private List<Guid> _channelIdList = new List<Guid>();
-        private Dictionary<Guid, LiveTvProgram> _programs = new Dictionary<Guid, LiveTvProgram>();
+        private Dictionary<Guid, LiveTvProgram> _programs;
         private readonly ConcurrentDictionary<Guid, bool> _refreshedPrograms = new ConcurrentDictionary<Guid, bool>();
         private readonly ConcurrentDictionary<Guid, bool> _refreshedPrograms = new ConcurrentDictionary<Guid, bool>();
 
 
-        private readonly SemaphoreSlim _refreshSemaphore = new SemaphoreSlim(1, 1);
-
         public LiveTvManager(IApplicationHost appHost, IServerConfigurationManager config, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, IUserDataManager userDataManager, IDtoService dtoService, IUserManager userManager, ILibraryManager libraryManager, ITaskManager taskManager, ILocalizationManager localization, IJsonSerializer jsonSerializer, IProviderManager providerManager)
         public LiveTvManager(IApplicationHost appHost, IServerConfigurationManager config, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, IUserDataManager userDataManager, IDtoService dtoService, IUserManager userManager, ILibraryManager libraryManager, ITaskManager taskManager, ILocalizationManager localization, IJsonSerializer jsonSerializer, IProviderManager providerManager)
         {
         {
             _config = config;
             _config = config;
@@ -109,6 +107,37 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             _taskManager.CancelIfRunningAndQueue<RefreshChannelsScheduledTask>();
             _taskManager.CancelIfRunningAndQueue<RefreshChannelsScheduledTask>();
         }
         }
 
 
+        private readonly object _programsDataLock = new object();
+        private Dictionary<Guid, LiveTvProgram> GetProgramsDictionary()
+        {
+            if (_programs == null)
+            {
+                lock (_programsDataLock)
+                {
+                    if (_programs == null)
+                    {
+                        var dict = new Dictionary<Guid, LiveTvProgram>();
+
+                        foreach (var item in _itemRepo.GetItemsOfType(typeof (LiveTvProgram))
+                            .Cast<LiveTvProgram>()
+                            .ToList())
+                        {
+                            dict[item.Id] = item;    
+                        }
+
+                        _programs = dict;
+                    }
+                }
+            }
+
+            return _programs;
+        }
+
+        private IEnumerable<LiveTvProgram> GetPrograms()
+        {
+            return GetProgramsDictionary().Values;
+        }
+
         public async Task<QueryResult<LiveTvChannel>> GetInternalChannels(LiveTvChannelQuery query, CancellationToken cancellationToken)
         public async Task<QueryResult<LiveTvChannel>> GetInternalChannels(LiveTvChannelQuery query, CancellationToken cancellationToken)
         {
         {
             var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId);
             var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId);
@@ -260,7 +289,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
 
             LiveTvProgram obj = null;
             LiveTvProgram obj = null;
 
 
-            _programs.TryGetValue(guid, out obj);
+            GetProgramsDictionary().TryGetValue(guid, out obj);
 
 
             if (obj != null)
             if (obj != null)
             {
             {
@@ -597,7 +626,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
 
             item.ProductionYear = info.ProductionYear;
             item.ProductionYear = info.ProductionYear;
             item.PremiereDate = item.PremiereDate ?? info.OriginalAirDate;
             item.PremiereDate = item.PremiereDate ?? info.OriginalAirDate;
-            
+
             await item.UpdateToRepository(ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
             await item.UpdateToRepository(ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
 
 
             return item;
             return item;
@@ -691,7 +720,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
 
         public async Task<QueryResult<ProgramInfoDto>> GetPrograms(ProgramQuery query, CancellationToken cancellationToken)
         public async Task<QueryResult<ProgramInfoDto>> GetPrograms(ProgramQuery query, CancellationToken cancellationToken)
         {
         {
-            IEnumerable<LiveTvProgram> programs = _programs.Values;
+            IEnumerable<LiveTvProgram> programs = GetPrograms();
 
 
             if (query.MinEndDate.HasValue)
             if (query.MinEndDate.HasValue)
             {
             {
@@ -806,7 +835,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
 
         public async Task<QueryResult<LiveTvProgram>> GetRecommendedProgramsInternal(RecommendedProgramQuery query, CancellationToken cancellationToken)
         public async Task<QueryResult<LiveTvProgram>> GetRecommendedProgramsInternal(RecommendedProgramQuery query, CancellationToken cancellationToken)
         {
         {
-            IEnumerable<LiveTvProgram> programs = _programs.Values;
+            IEnumerable<LiveTvProgram> programs = GetPrograms();
 
 
             var user = _userManager.GetUserById(query.UserId);
             var user = _userManager.GetUserById(query.UserId);
 
 
@@ -995,24 +1024,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
 
         internal async Task RefreshChannels(IProgress<double> progress, CancellationToken cancellationToken)
         internal async Task RefreshChannels(IProgress<double> progress, CancellationToken cancellationToken)
         {
         {
-            await _refreshSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
-
-            try
-            {
-                var innerProgress = new ActionableProgress<double>();
-                innerProgress.RegisterAction(p => progress.Report(p * .9));
-                await RefreshChannelsInternal(innerProgress, cancellationToken).ConfigureAwait(false);
+            var innerProgress = new ActionableProgress<double>();
+            innerProgress.RegisterAction(p => progress.Report(p * .9));
+            await RefreshChannelsInternal(innerProgress, cancellationToken).ConfigureAwait(false);
 
 
-                innerProgress = new ActionableProgress<double>();
-                innerProgress.RegisterAction(p => progress.Report(90 + (p * .1)));
-                await CleanDatabaseInternal(progress, cancellationToken).ConfigureAwait(false);
+            innerProgress = new ActionableProgress<double>();
+            innerProgress.RegisterAction(p => progress.Report(90 + (p * .1)));
+            await CleanDatabaseInternal(progress, cancellationToken).ConfigureAwait(false);
 
 
-                RefreshIfNeeded(_programs.Values.Where(i => (i.StartDate - DateTime.UtcNow).TotalDays <= 1).ToList());
-            }
-            finally
-            {
-                _refreshSemaphore.Release();
-            }
+            RefreshIfNeeded(GetPrograms().Where(i => (i.StartDate - DateTime.UtcNow).TotalDays <= 1).ToList());
         }
         }
 
 
         private async Task RefreshChannelsInternal(IProgress<double> progress, CancellationToken cancellationToken)
         private async Task RefreshChannelsInternal(IProgress<double> progress, CancellationToken cancellationToken)
@@ -1136,7 +1156,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 progress.Report(80 * percent + 10);
                 progress.Report(80 * percent + 10);
             }
             }
 
 
-            _programs = programs.ToDictionary(i => i.Id);
+            lock (_programsDataLock)
+            {
+                _programs = programs.ToDictionary(i => i.Id);
+            }
+
             _refreshedPrograms.Clear();
             _refreshedPrograms.Clear();
             progress.Report(90);
             progress.Report(90);
 
 
@@ -1147,28 +1171,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             progress.Report(100);
             progress.Report(100);
         }
         }
 
 
-        public async Task CleanDatabase(IProgress<double> progress, CancellationToken cancellationToken)
-        {
-            await _refreshSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
-
-            try
-            {
-                await DeleteOldPrograms(_programs.Keys.ToList(), progress, cancellationToken).ConfigureAwait(false);
-            }
-            finally
-            {
-                _refreshSemaphore.Release();
-            }
-        }
-
         private Task CleanDatabaseInternal(IProgress<double> progress, CancellationToken cancellationToken)
         private Task CleanDatabaseInternal(IProgress<double> progress, CancellationToken cancellationToken)
         {
         {
-            return DeleteOldPrograms(_programs.Keys.ToList(), progress, cancellationToken);
+            return DeleteOldPrograms(GetProgramsDictionary().Keys.ToList(), progress, cancellationToken);
         }
         }
 
 
         private async Task DeleteOldPrograms(List<Guid> currentIdList, IProgress<double> progress, CancellationToken cancellationToken)
         private async Task DeleteOldPrograms(List<Guid> currentIdList, IProgress<double> progress, CancellationToken cancellationToken)
         {
         {
-            var list = _itemRepo.GetItemsOfType(typeof(LiveTvProgram)).ToList();
+            var list = _itemRepo.GetItemIdsOfType(typeof(LiveTvProgram)).ToList();
 
 
             var numComplete = 0;
             var numComplete = 0;
 
 
@@ -1549,7 +1559,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
         {
         {
             var now = DateTime.UtcNow;
             var now = DateTime.UtcNow;
 
 
-            var program = _programs.Values
+            var program = GetPrograms()
                 .Where(i => string.Equals(externalChannelId, i.ExternalChannelId, StringComparison.OrdinalIgnoreCase))
                 .Where(i => string.Equals(externalChannelId, i.ExternalChannelId, StringComparison.OrdinalIgnoreCase))
                 .OrderBy(i => i.StartDate)
                 .OrderBy(i => i.StartDate)
                 .SkipWhile(i => now >= (i.EndDate ?? DateTime.MinValue))
                 .SkipWhile(i => now >= (i.EndDate ?? DateTime.MinValue))
@@ -1742,7 +1752,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
         {
         {
             var dtoOptions = new DtoOptions();
             var dtoOptions = new DtoOptions();
             dtoOptions.Fields.Remove(ItemFields.SyncInfo);
             dtoOptions.Fields.Remove(ItemFields.SyncInfo);
-            
+
             var recordingResult = await GetRecordings(new RecordingQuery
             var recordingResult = await GetRecordings(new RecordingQuery
             {
             {
                 UserId = query.UserId
                 UserId = query.UserId
@@ -1855,13 +1865,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
 
         public GuideInfo GetGuideInfo()
         public GuideInfo GetGuideInfo()
         {
         {
-            var programs = _programs.ToList();
+            var programs = GetPrograms().OrderBy(i => i.StartDate).ToList();
 
 
-            var startDate = _programs.Count == 0 ? DateTime.MinValue :
-                programs.Select(i => i.Value.StartDate).Min();
+            var startDate = programs.Count == 0 ?
+                DateTime.MinValue :
+                programs[0].StartDate;
 
 
-            var endDate = programs.Count == 0 ? DateTime.MinValue :
-                programs.Select(i => i.Value.StartDate).Max();
+            var endDate = programs.Count == 0 ?
+                DateTime.MinValue :
+                programs[programs.Count - 1].StartDate;
 
 
             return new GuideInfo
             return new GuideInfo
             {
             {

+ 4 - 1
MediaBrowser.Server.Implementations/Localization/Server/server.json

@@ -1430,5 +1430,8 @@
     "ButtonMyPreferencesWelcomeNo": "No thanks, I'll do it later.",
     "ButtonMyPreferencesWelcomeNo": "No thanks, I'll do it later.",
     "MyPreferencesWelcomeMessage1": "We've presented your library in a way we think you'll enjoy. The appearance and grouping of content can be changed anytime by adjusting your preferences. Your preferences will apply to all Emby apps.",
     "MyPreferencesWelcomeMessage1": "We've presented your library in a way we think you'll enjoy. The appearance and grouping of content can be changed anytime by adjusting your preferences. Your preferences will apply to all Emby apps.",
     "MyPreferencesWelcomeMessage2": "Would you like to set your preferences now?",
     "MyPreferencesWelcomeMessage2": "Would you like to set your preferences now?",
-    "ToAccessPreferencesHelp": "To access your preferences later, click your user icon in the top right header and select My Preferences."
+    "ToAccessPreferencesHelp": "To access your preferences later, click your user icon in the top right header and select My Preferences.",
+    "HeaderViewStyles": "View Styles",
+    "LabelSelectViewStyles": "Enable rich presentations for:",
+    "LabelSelectViewStylesHelp": "If enabled, views will be built with metadata to offer categories such as Suggestions, Latest, Genres, and more. If disabled, they'll be displayed with simple folders."
 }
 }

+ 32 - 2
MediaBrowser.Server.Implementations/Persistence/SqliteItemRepository.cs

@@ -522,7 +522,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
             }
             }
         }
         }
 
 
-        public IEnumerable<Guid> GetItemsOfType(Type type)
+        public IEnumerable<BaseItem> GetItemsOfType(Type type)
         {
         {
             if (type == null)
             if (type == null)
             {
             {
@@ -530,7 +530,37 @@ namespace MediaBrowser.Server.Implementations.Persistence
             }
             }
 
 
             CheckDisposed();
             CheckDisposed();
-            
+
+            using (var cmd = _connection.CreateCommand())
+            {
+                cmd.CommandText = "select type,data from TypedBaseItems where type = @type";
+
+                cmd.Parameters.Add(cmd, "@type", DbType.String).Value = type.FullName;
+
+                using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult))
+                {
+                    while (reader.Read())
+                    {
+                        var item = GetItem(reader);
+
+                        if (item != null)
+                        {
+                            yield return item;
+                        }
+                    }
+                }
+            }
+        }
+
+        public IEnumerable<Guid> GetItemIdsOfType(Type type)
+        {
+            if (type == null)
+            {
+                throw new ArgumentNullException("type");
+            }
+
+            CheckDisposed();
+
             using (var cmd = _connection.CreateCommand())
             using (var cmd = _connection.CreateCommand())
             {
             {
                 cmd.CommandText = "select guid from TypedBaseItems where type = @type";
                 cmd.CommandText = "select guid from TypedBaseItems where type = @type";

+ 1 - 1
MediaBrowser.Server.Implementations/Sync/CloudSyncProfile.cs

@@ -205,7 +205,7 @@ namespace MediaBrowser.Server.Implementations.Sync
                         {
                         {
                             Condition = ProfileConditionType.LessThanEqual,
                             Condition = ProfileConditionType.LessThanEqual,
                             Property = ProfileConditionValue.AudioChannels,
                             Property = ProfileConditionValue.AudioChannels,
-                            Value = "5",
+                            Value = "6",
                             IsRequired = false
                             IsRequired = false
                         },
                         },
                         new ProfileCondition
                         new ProfileCondition

+ 1 - 1
MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj

@@ -62,7 +62,7 @@
   <ItemGroup>
   <ItemGroup>
     <Reference Include="ImageMagickSharp, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
     <Reference Include="ImageMagickSharp, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
       <SpecificVersion>False</SpecificVersion>
       <SpecificVersion>False</SpecificVersion>
-      <HintPath>..\packages\ImageMagickSharp.1.0.0.14\lib\net45\ImageMagickSharp.dll</HintPath>
+      <HintPath>..\packages\ImageMagickSharp.1.0.0.15\lib\net45\ImageMagickSharp.dll</HintPath>
     </Reference>
     </Reference>
     <Reference Include="MediaBrowser.IsoMounter">
     <Reference Include="MediaBrowser.IsoMounter">
       <HintPath>..\packages\MediaBrowser.IsoMounting.3.0.69\lib\net45\MediaBrowser.IsoMounter.dll</HintPath>
       <HintPath>..\packages\MediaBrowser.IsoMounting.3.0.69\lib\net45\MediaBrowser.IsoMounter.dll</HintPath>

+ 1 - 1
MediaBrowser.ServerApplication/packages.config

@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
 <packages>
-  <package id="ImageMagickSharp" version="1.0.0.14" targetFramework="net45" />
+  <package id="ImageMagickSharp" version="1.0.0.15" targetFramework="net45" />
   <package id="MediaBrowser.IsoMounting" version="3.0.69" targetFramework="net45" />
   <package id="MediaBrowser.IsoMounting" version="3.0.69" targetFramework="net45" />
   <package id="System.Data.SQLite.Core" version="1.0.94.0" targetFramework="net45" />
   <package id="System.Data.SQLite.Core" version="1.0.94.0" targetFramework="net45" />
 </packages>
 </packages>

+ 1 - 1
SharedVersion.cs

@@ -1,4 +1,4 @@
 using System.Reflection;
 using System.Reflection;
 
 
 //[assembly: AssemblyVersion("3.0.*")]
 //[assembly: AssemblyVersion("3.0.*")]
-[assembly: AssemblyVersion("3.0.5582.1")]
+[assembly: AssemblyVersion("3.0.5582.2")]