Răsfoiți Sursa

Merge pull request #1082 from MediaBrowser/dev

3.0.5582.2
Luke 10 ani în urmă
părinte
comite
73688e3fcc
26 a modificat fișierele cu 415 adăugiri și 329 ștergeri
  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>
     <Reference Include="ImageMagickSharp, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
       <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 Include="System" />
     <Reference Include="System.Core" />
@@ -65,9 +65,6 @@
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="ImageMagick\UnplayedCountIndicator.cs" />
   </ItemGroup>
-  <ItemGroup>
-    <None Include="packages.config" />
-  </ItemGroup>
   <ItemGroup>
     <EmbeddedResource Include="ImageMagick\fonts\MontserratLight.otf" />
     <EmbeddedResource Include="ImageMagick\fonts\robotoregular.ttf" />
@@ -87,6 +84,9 @@
       <Name>MediaBrowser.Model</Name>
     </ProjectReference>
   </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config" />
+  </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <!-- 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.

+ 1 - 1
Emby.Drawing/packages.config

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

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

@@ -156,6 +156,7 @@
     <Compile Include="UserLibrary\PlaystateService.cs" />
     <Compile Include="UserLibrary\StudiosService.cs" />
     <Compile Include="UserLibrary\UserLibraryService.cs" />
+    <Compile Include="UserLibrary\UserViewsService.cs" />
     <Compile Include="UserLibrary\YearsService.cs" />
     <Compile Include="UserService.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; }
     }
 
-    [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>
     /// Class GetItem
     /// </summary>
@@ -345,37 +331,6 @@ namespace MediaBrowser.Api.UserLibrary
             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)
         {
             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);
         }
 
-        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>
             {

+ 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
 {
@@ -9,6 +10,8 @@ namespace MediaBrowser.Controller.Entities
     {
         string CollectionType { get; }
         string Path { get; }
+        string Name { get; }
+        Guid Id { 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);
 
                 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 System;
 using System.Collections.Generic;
+using System.Globalization;
 using System.Linq;
 
 namespace MediaBrowser.Controller.Library
 {
     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)
         {
-            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 name;
             return name.RemoveDiacritics();
         }
 
         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>
         /// <param name="type">The type.</param>
         /// <returns>IEnumerable{Guid}.</returns>
-        IEnumerable<Guid> GetItemsOfType(Type type);
+        IEnumerable<Guid> GetItemIdsOfType(Type type);
 
         /// <summary>
         /// Saves the children.
@@ -133,6 +133,13 @@ namespace MediaBrowser.Controller.Persistence
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         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);
 
             var processWrapper = new ProcessWrapper(process, this);
-
-            StartProcess(processWrapper);
+            bool ranToCompletion;
 
             var memoryStream = new MemoryStream();
 
+            try
+            {
+                StartProcess(processWrapper);
+
 #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
 
-            // 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;
 
-            process.Dispose();
-
             if (exitCode == -1 || memoryStream.Length == 0)
             {
                 memoryStream.Dispose();
@@ -594,7 +599,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
             await resourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
 
-            bool ranToCompletion;
+            bool ranToCompletion = false;
 
             var processWrapper = new ProcessWrapper(process, this);
 
@@ -609,19 +614,23 @@ namespace MediaBrowser.MediaEncoding.Encoder
                 bool isResponsive = true;
                 int lastCount = 0;
 
-                while (isResponsive && !process.WaitForExit(30000))
+                while (isResponsive)
                 {
+                    if (process.WaitForExit(30000))
+                    {
+                        ranToCompletion = true;
+                        break;
+                    }
+
                     cancellationToken.ThrowIfCancellationRequested();
 
-                    int jpegCount = Directory.GetFiles(targetDirectory)
+                    var jpegCount = Directory.GetFiles(targetDirectory)
                         .Count(i => string.Equals(Path.GetExtension(i), ".jpg", StringComparison.OrdinalIgnoreCase));
 
                     isResponsive = (jpegCount > lastCount);
                     lastCount = jpegCount;
                 }
 
-                ranToCompletion = process.HasExited;
-
                 if (!ranToCompletion)
                 {
                     StopProcess(processWrapper, 1000, false);
@@ -634,8 +643,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
 
             var exitCode = ranToCompletion ? processWrapper.ExitCode ?? 0 : -1;
 
-            process.Dispose();
-
             if (exitCode == -1)
             {
                 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>
         /// <value><c>true</c> if [enable user specific user views]; otherwise, <c>false</c>.</value>
         public bool EnableUserSpecificUserViews { get; set; }
-        
+
         /// <summary>
         /// Gets or sets the value pointing to the file system where the ssl certiifcate is located..
         /// </summary>

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

@@ -46,6 +46,7 @@ namespace MediaBrowser.Model.Configuration
         public bool EnableCinemaMode { get; set; }
 
         public string[] LatestItemsExcludes { get; set; }
+        public string[] PlainFolderViews { get; set; }
 
         public bool HidePlayedInLatest { get; set; }
 
@@ -62,6 +63,7 @@ namespace MediaBrowser.Model.Configuration
             DisplayChannelsWithinViews = new string[] { };
 
             ExcludeFoldersFromGrouping = new string[] { };
+            PlainFolderViews = new string[] { };
             DisplayCollectionsView = 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="Channels\ChannelMetadataService.cs" />
     <Compile Include="Chapters\ChapterManager.cs" />
-    <Compile Include="FolderImages\DefaultImageProvider.cs" />
     <Compile Include="Folders\FolderMetadataService.cs" />
     <Compile Include="Channels\AudioChannelItemMetadataService.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.Entities;
 using MediaBrowser.Model.IO;
+using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.MediaInfo;
 using System;
 using System.Collections.Generic;
@@ -20,13 +21,15 @@ namespace MediaBrowser.Providers.MediaInfo
         private readonly IMediaEncoder _mediaEncoder;
         private readonly IServerConfigurationManager _config;
         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;
             _mediaEncoder = mediaEncoder;
             _config = config;
             _libraryManager = libraryManager;
+            _logger = logger;
         }
 
         /// <summary>
@@ -74,6 +77,7 @@ namespace MediaBrowser.Providers.MediaInfo
             // Can't extract if we didn't find a video stream in the file
             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 });
             }
 

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

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

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

@@ -1678,11 +1678,6 @@ namespace MediaBrowser.Server.Implementations.Library
                 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 path = Path.Combine(ConfigurationManager.ApplicationPaths.InternalMetadataPath, "views", id.ToString("N"));
@@ -1716,6 +1711,12 @@ namespace MediaBrowser.Server.Implementations.Library
                 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;
 
             if (refresh)

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

@@ -53,6 +53,7 @@ namespace MediaBrowser.Server.Implementations.Library
                 .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
                 .Where(i => UserView.IsExcludedFromGrouping(i) || excludeFolderIds.Contains(i.Id))
@@ -72,13 +73,17 @@ namespace MediaBrowser.Server.Implementations.Library
                     var collectionFolder = folder as ICollectionFolder;
                     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
                     {
-                        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);
             }
 
-            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)
             {
-                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)
@@ -151,7 +169,7 @@ namespace MediaBrowser.Server.Implementations.Library
                 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 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);
         }
 
-        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)
@@ -317,7 +361,7 @@ namespace MediaBrowser.Server.Implementations.Library
                 .RootFolder
                 .GetRecursiveChildren(filter);
         }
-        
+
         private IEnumerable<BaseItem> GetItemsConfiguredForLatest(User user, Func<BaseItem, bool> filter)
         {
             // 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>();
 
         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 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)
         {
             _config = config;
@@ -109,6 +107,37 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             _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)
         {
             var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId);
@@ -260,7 +289,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
             LiveTvProgram obj = null;
 
-            _programs.TryGetValue(guid, out obj);
+            GetProgramsDictionary().TryGetValue(guid, out obj);
 
             if (obj != null)
             {
@@ -597,7 +626,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
             item.ProductionYear = info.ProductionYear;
             item.PremiereDate = item.PremiereDate ?? info.OriginalAirDate;
-            
+
             await item.UpdateToRepository(ItemUpdateType.MetadataImport, cancellationToken).ConfigureAwait(false);
 
             return item;
@@ -691,7 +720,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
         public async Task<QueryResult<ProgramInfoDto>> GetPrograms(ProgramQuery query, CancellationToken cancellationToken)
         {
-            IEnumerable<LiveTvProgram> programs = _programs.Values;
+            IEnumerable<LiveTvProgram> programs = GetPrograms();
 
             if (query.MinEndDate.HasValue)
             {
@@ -806,7 +835,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
         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);
 
@@ -995,24 +1024,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
         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)
@@ -1136,7 +1156,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 progress.Report(80 * percent + 10);
             }
 
-            _programs = programs.ToDictionary(i => i.Id);
+            lock (_programsDataLock)
+            {
+                _programs = programs.ToDictionary(i => i.Id);
+            }
+
             _refreshedPrograms.Clear();
             progress.Report(90);
 
@@ -1147,28 +1171,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             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)
         {
-            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)
         {
-            var list = _itemRepo.GetItemsOfType(typeof(LiveTvProgram)).ToList();
+            var list = _itemRepo.GetItemIdsOfType(typeof(LiveTvProgram)).ToList();
 
             var numComplete = 0;
 
@@ -1549,7 +1559,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
         {
             var now = DateTime.UtcNow;
 
-            var program = _programs.Values
+            var program = GetPrograms()
                 .Where(i => string.Equals(externalChannelId, i.ExternalChannelId, StringComparison.OrdinalIgnoreCase))
                 .OrderBy(i => i.StartDate)
                 .SkipWhile(i => now >= (i.EndDate ?? DateTime.MinValue))
@@ -1742,7 +1752,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
         {
             var dtoOptions = new DtoOptions();
             dtoOptions.Fields.Remove(ItemFields.SyncInfo);
-            
+
             var recordingResult = await GetRecordings(new RecordingQuery
             {
                 UserId = query.UserId
@@ -1855,13 +1865,15 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
         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
             {

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

@@ -1430,5 +1430,8 @@
     "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.",
     "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)
             {
@@ -530,7 +530,37 @@ namespace MediaBrowser.Server.Implementations.Persistence
             }
 
             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())
             {
                 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,
                             Property = ProfileConditionValue.AudioChannels,
-                            Value = "5",
+                            Value = "6",
                             IsRequired = false
                         },
                         new ProfileCondition

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

@@ -62,7 +62,7 @@
   <ItemGroup>
     <Reference Include="ImageMagickSharp, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
       <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 Include="MediaBrowser.IsoMounter">
       <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"?>
 <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="System.Data.SQLite.Core" version="1.0.94.0" targetFramework="net45" />
 </packages>

+ 1 - 1
SharedVersion.cs

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