Explorar o código

Merge branch 'master' of https://github.com/MediaBrowser/MediaBrowser

Techywarrior %!s(int64=12) %!d(string=hai) anos
pai
achega
51b71afd5c
Modificáronse 30 ficheiros con 490 adicións e 331 borrados
  1. 14 20
      MediaBrowser.Api/DisplayPreferencesService.cs
  2. 1 2
      MediaBrowser.Controller/Entities/CollectionFolder.cs
  3. 11 1
      MediaBrowser.Controller/Entities/Folder.cs
  4. 1 17
      MediaBrowser.Controller/Kernel.cs
  5. 1 1
      MediaBrowser.Controller/Library/DtoBuilder.cs
  6. 28 0
      MediaBrowser.Controller/Library/IDisplayPreferencesManager.cs
  7. 6 22
      MediaBrowser.Controller/Library/IUserManager.cs
  8. 1 0
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  9. 2 5
      MediaBrowser.Controller/Persistence/IDisplayPreferencesRepository.cs
  10. 2 2
      MediaBrowser.Controller/Providers/MediaInfo/BaseFFProbeProvider.cs
  11. 12 14
      MediaBrowser.Controller/Providers/MediaInfo/FFMpegVideoImageProvider.cs
  12. 24 27
      MediaBrowser.Controller/Providers/MediaInfo/FFProbeAudioInfoProvider.cs
  13. 26 29
      MediaBrowser.Controller/Providers/MediaInfo/FFProbeVideoInfoProvider.cs
  14. 1 1
      MediaBrowser.Model/Entities/DisplayPreferences.cs
  15. 99 0
      MediaBrowser.Server.Implementations/Library/DisplayPreferencesManager.cs
  16. 19 92
      MediaBrowser.Server.Implementations/Library/UserManager.cs
  17. 1 0
      MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
  18. 1 1
      MediaBrowser.Server.Implementations/Providers/ProviderManager.cs
  19. 48 34
      MediaBrowser.Server.Implementations/Sqlite/SQLiteDisplayPreferencesRepository.cs
  20. 38 12
      MediaBrowser.Server.Implementations/Sqlite/SQLiteRepository.cs
  21. 40 11
      MediaBrowser.Server.Implementations/Sqlite/SQLiteUserDataRepository.cs
  22. 41 12
      MediaBrowser.Server.Implementations/Sqlite/SQLiteUserRepository.cs
  23. 1 1
      MediaBrowser.ServerApplication/App.xaml.cs
  24. 47 10
      MediaBrowser.ServerApplication/ApplicationHost.cs
  25. 15 9
      MediaBrowser.ServerApplication/LibraryExplorer.xaml.cs
  26. 4 2
      MediaBrowser.ServerApplication/MainWindow.xaml.cs
  27. 1 1
      MediaBrowser.ServerApplication/MediaBrowser.ServerApplication.csproj
  28. 2 2
      Nuget/MediaBrowser.Common.Internal.nuspec
  29. 1 1
      Nuget/MediaBrowser.Common.nuspec
  30. 2 2
      Nuget/MediaBrowser.Server.Core.nuspec

+ 14 - 20
MediaBrowser.Api/DisplayPreferencesService.cs

@@ -12,7 +12,7 @@ namespace MediaBrowser.Api
     /// <summary>
     /// Class UpdateDisplayPreferences
     /// </summary>
-    [Route("/Users/{UserId}/DisplayPreferences/{Id}", "POST")]
+    [Route("/DisplayPreferences/{DisplayPreferencesId}", "POST")]
     [Api(("Updates a user's display preferences for an item"))]
     public class UpdateDisplayPreferences : DisplayPreferences, IReturnVoid
     {
@@ -20,22 +20,19 @@ namespace MediaBrowser.Api
         /// Gets or sets the id.
         /// </summary>
         /// <value>The id.</value>
-        [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
-        public Guid Id { get; set; }
+        [ApiMember(Name = "DisplayPreferencesId", Description = "DisplayPreferences Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
+        public Guid DisplayPreferencesId { get; set; }
     }
 
-    [Route("/Users/{UserId}/DisplayPreferences/{Id}", "GET")]
+    [Route("/DisplayPreferences/{Id}", "GET")]
     [Api(("Gets a user's display preferences for an item"))]
     public class GetDisplayPreferences : IReturn<DisplayPreferences>
     {
-        [ApiMember(Name = "UserId", Description = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
-        public Guid UserId { get; set; }
-        
         /// <summary>
         /// Gets or sets the id.
         /// </summary>
         /// <value>The id.</value>
-        [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
+        [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
         public Guid Id { get; set; }
     }
     
@@ -45,23 +42,23 @@ namespace MediaBrowser.Api
     public class DisplayPreferencesService : BaseApiService
     {
         /// <summary>
-        /// The _user manager
+        /// The _display preferences manager
         /// </summary>
-        private readonly IUserManager _userManager;
+        private readonly IDisplayPreferencesManager _displayPreferencesManager;
         /// <summary>
         /// The _json serializer
         /// </summary>
         private readonly IJsonSerializer _jsonSerializer;
 
         /// <summary>
-        /// Initializes a new instance of the <see cref="DisplayPreferencesService"/> class.
+        /// Initializes a new instance of the <see cref="DisplayPreferencesService" /> class.
         /// </summary>
-        /// <param name="userManager">The user manager.</param>
         /// <param name="jsonSerializer">The json serializer.</param>
-        public DisplayPreferencesService(IUserManager userManager, IJsonSerializer jsonSerializer)
+        /// <param name="displayPreferencesManager">The display preferences manager.</param>
+        public DisplayPreferencesService(IJsonSerializer jsonSerializer, IDisplayPreferencesManager displayPreferencesManager)
         {
-            _userManager = userManager;
             _jsonSerializer = jsonSerializer;
+            _displayPreferencesManager = displayPreferencesManager;
         }
 
         /// <summary>
@@ -70,7 +67,7 @@ namespace MediaBrowser.Api
         /// <param name="request">The request.</param>
         public object Get(GetDisplayPreferences request)
         {
-            var task = _userManager.GetDisplayPreferences(request.UserId, request.Id);
+            var task = _displayPreferencesManager.GetDisplayPreferences(request.Id);
 
             return ToOptimizedResult(task.Result);
         }
@@ -84,15 +81,12 @@ namespace MediaBrowser.Api
             // We need to parse this manually because we told service stack not to with IRequiresRequestStream
             // https://code.google.com/p/servicestack/source/browse/trunk/Common/ServiceStack.Text/ServiceStack.Text/Controller/PathInfo.cs
             var pathInfo = PathInfo.Parse(RequestContext.PathInfo);
-            var userId = new Guid(pathInfo.GetArgumentValue<string>(1));
-            var displayPreferencesId = new Guid(pathInfo.GetArgumentValue<string>(3));
-
-            var user = _userManager.GetUserById(userId);
+            var displayPreferencesId = new Guid(pathInfo.GetArgumentValue<string>(1));
 
             // Serialize to json and then back so that the core doesn't see the request dto type
             var displayPreferences = _jsonSerializer.DeserializeFromString<DisplayPreferences>(_jsonSerializer.SerializeToString(request));
 
-            var task = _userManager.SaveDisplayPreferences(user.Id, displayPreferencesId, displayPreferences, CancellationToken.None);
+            var task = _displayPreferencesManager.SaveDisplayPreferences(displayPreferences, CancellationToken.None);
 
             Task.WaitAll(task);
         }

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

@@ -1,6 +1,5 @@
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Tasks;
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
@@ -35,7 +34,7 @@ namespace MediaBrowser.Controller.Entities
         /// Allow different display preferences for each collection folder
         /// </summary>
         /// <value>The display prefs id.</value>
-        public override Guid DisplayPreferencesId
+        protected override Guid DisplayPreferencesId
         {
             get
             {

+ 11 - 1
MediaBrowser.Controller/Entities/Folder.cs

@@ -65,7 +65,7 @@ namespace MediaBrowser.Controller.Entities
         /// </summary>
         /// <value>The display prefs id.</value>
         [IgnoreDataMember]
-        public virtual Guid DisplayPreferencesId
+        protected virtual Guid DisplayPreferencesId
         {
             get
             {
@@ -74,6 +74,16 @@ namespace MediaBrowser.Controller.Entities
             }
         }
 
+        /// <summary>
+        /// Gets the display preferences id.
+        /// </summary>
+        /// <param name="userId">The user id.</param>
+        /// <returns>Guid.</returns>
+        public Guid GetDisplayPreferencesId(Guid userId)
+        {
+            return (userId + DisplayPreferencesId.ToString()).GetMD5();
+        }
+
         #region Indexing
 
         /// <summary>

+ 1 - 17
MediaBrowser.Controller/Kernel.cs

@@ -88,12 +88,6 @@ namespace MediaBrowser.Controller
         /// <value>The user repository.</value>
         public IUserRepository UserRepository { get; set; }
 
-        /// <summary>
-        /// Gets the active user repository
-        /// </summary>
-        /// <value>The display preferences repository.</value>
-        public IDisplayPreferencesRepository DisplayPreferencesRepository { get; set; }
-
         /// <summary>
         /// Gets the list of available item repositories
         /// </summary>
@@ -106,12 +100,6 @@ namespace MediaBrowser.Controller
         /// <value>The item repository.</value>
         public IItemRepository ItemRepository { get; set; }
 
-        /// <summary>
-        /// Gets the list of available DisplayPreferencesRepositories
-        /// </summary>
-        /// <value>The display preferences repositories.</value>
-        public IEnumerable<IDisplayPreferencesRepository> DisplayPreferencesRepositories { get; set; }
-
         /// <summary>
         /// Gets the list of available item repositories
         /// </summary>
@@ -155,11 +143,7 @@ namespace MediaBrowser.Controller
             UserDataRepository = GetRepository(UserDataRepositories, configurationManager.Configuration.UserDataRepository);
             var userDataRepoTask = UserDataRepository.Initialize();
 
-            // Get the current display preferences repository
-            DisplayPreferencesRepository = GetRepository(DisplayPreferencesRepositories, configurationManager.Configuration.DisplayPreferencesRepository);
-            var displayPreferencesRepoTask = DisplayPreferencesRepository.Initialize();
-
-            return Task.WhenAll(itemRepoTask, userRepoTask, userDataRepoTask, displayPreferencesRepoTask);
+            return Task.WhenAll(itemRepoTask, userRepoTask, userDataRepoTask);
         }
 
         /// <summary>

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

@@ -174,7 +174,7 @@ namespace MediaBrowser.Controller.Library
 
             if (item.IsFolder && fields.Contains(ItemFields.DisplayPreferencesId))
             {
-                dto.DisplayPreferencesId = ((Folder)item).DisplayPreferencesId.ToString();
+                dto.DisplayPreferencesId = ((Folder) item).GetDisplayPreferencesId(user.Id).ToString();
             }
 
             if (item.IsFolder)

+ 28 - 0
MediaBrowser.Controller/Library/IDisplayPreferencesManager.cs

@@ -0,0 +1,28 @@
+using MediaBrowser.Model.Entities;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Controller.Library
+{
+    /// <summary>
+    /// Interface IDisplayPreferencesManager
+    /// </summary>
+    public interface IDisplayPreferencesManager
+    {
+        /// <summary>
+        /// Gets the display preferences.
+        /// </summary>
+        /// <param name="displayPreferencesId">The display preferences id.</param>
+        /// <returns>DisplayPreferences.</returns>
+        Task<DisplayPreferences> GetDisplayPreferences(Guid displayPreferencesId);
+
+        /// <summary>
+        /// Saves display preferences for an item
+        /// </summary>
+        /// <param name="displayPreferences">The display preferences.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task SaveDisplayPreferences(DisplayPreferences displayPreferences, CancellationToken cancellationToken);
+    }
+}

+ 6 - 22
MediaBrowser.Controller/Library/IUserManager.cs

@@ -1,7 +1,6 @@
 using MediaBrowser.Common.Events;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Model.Connectivity;
-using MediaBrowser.Model.Entities;
 using System;
 using System.Collections.Generic;
 using System.Threading;
@@ -9,6 +8,9 @@ using System.Threading.Tasks;
 
 namespace MediaBrowser.Controller.Library
 {
+    /// <summary>
+    /// Interface IUserManager
+    /// </summary>
     public interface IUserManager
     {
         /// <summary>
@@ -173,7 +175,7 @@ namespace MediaBrowser.Controller.Library
         Task ChangePassword(User user, string newPassword);
 
         /// <summary>
-        /// Saves display preferences for an item
+        /// Saves the user data.
         /// </summary>
         /// <param name="userId">The user id.</param>
         /// <param name="userDataId">The user data id.</param>
@@ -184,29 +186,11 @@ namespace MediaBrowser.Controller.Library
                                     CancellationToken cancellationToken);
 
         /// <summary>
-        /// Gets the display preferences.
+        /// Gets the user data.
         /// </summary>
         /// <param name="userId">The user id.</param>
         /// <param name="userDataId">The user data id.</param>
-        /// <returns>Task{DisplayPreferences}.</returns>
+        /// <returns>Task{UserItemData}.</returns>
         Task<UserItemData> GetUserData(Guid userId, Guid userDataId);
-        
-        /// <summary>
-        /// Gets the display preferences.
-        /// </summary>
-        /// <param name="userId">The user id.</param>
-        /// <param name="displayPreferencesId">The display preferences id.</param>
-        /// <returns>DisplayPreferences.</returns>
-        Task<DisplayPreferences> GetDisplayPreferences(Guid userId, Guid displayPreferencesId);
-
-        /// <summary>
-        /// Saves display preferences for an item
-        /// </summary>
-        /// <param name="userId">The user id.</param>
-        /// <param name="displayPreferencesId">The display preferences id.</param>
-        /// <param name="displayPreferences">The display preferences.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        Task SaveDisplayPreferences(Guid userId, Guid displayPreferencesId, DisplayPreferences displayPreferences, CancellationToken cancellationToken);
     }
 }

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

@@ -88,6 +88,7 @@
     <Compile Include="Entities\Movies\BoxSet.cs" />
     <Compile Include="Entities\Movies\Movie.cs" />
     <Compile Include="Entities\Person.cs" />
+    <Compile Include="Library\IDisplayPreferencesManager.cs" />
     <Compile Include="Library\ILibrarySearchEngine.cs" />
     <Compile Include="Library\PlaybackProgressEventArgs.cs" />
     <Compile Include="Entities\Studio.cs" />

+ 2 - 5
MediaBrowser.Controller/Persistence/IDisplayPreferencesRepository.cs

@@ -13,20 +13,17 @@ namespace MediaBrowser.Controller.Persistence
         /// <summary>
         /// Saves display preferences for an item
         /// </summary>
-        /// <param name="userId">The user id.</param>
-        /// <param name="displayPreferencesId">The display preferences id.</param>
         /// <param name="displayPreferences">The display preferences.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
-        Task SaveDisplayPreferences(Guid userId, Guid displayPreferencesId, DisplayPreferences displayPreferences,
+        Task SaveDisplayPreferences(DisplayPreferences displayPreferences,
                                     CancellationToken cancellationToken);
 
         /// <summary>
         /// Gets the display preferences.
         /// </summary>
-        /// <param name="userId">The user id.</param>
         /// <param name="displayPreferencesId">The display preferences id.</param>
         /// <returns>Task{DisplayPreferences}.</returns>
-        Task<DisplayPreferences> GetDisplayPreferences(Guid userId, Guid displayPreferencesId);
+        Task<DisplayPreferences> GetDisplayPreferences(Guid displayPreferencesId);
     }
 }

+ 2 - 2
MediaBrowser.Controller/Providers/MediaInfo/BaseFFProbeProvider.cs

@@ -93,7 +93,7 @@ namespace MediaBrowser.Controller.Providers.MediaInfo
 
                 cancellationToken.ThrowIfCancellationRequested();
 
-                await Fetch(myItem, cancellationToken, result, isoMount).ConfigureAwait(false);
+                Fetch(myItem, cancellationToken, result, isoMount);
 
                 cancellationToken.ThrowIfCancellationRequested();
 
@@ -180,7 +180,7 @@ namespace MediaBrowser.Controller.Providers.MediaInfo
         /// <param name="result">The result.</param>
         /// <param name="isoMount">The iso mount.</param>
         /// <returns>Task.</returns>
-        protected abstract Task Fetch(T item, CancellationToken cancellationToken, FFProbeResult result, IIsoMount isoMount);
+        protected abstract void Fetch(T item, CancellationToken cancellationToken, FFProbeResult result, IIsoMount isoMount);
 
         /// <summary>
         /// Converts ffprobe stream info to our MediaStream class

+ 12 - 14
MediaBrowser.Controller/Providers/MediaInfo/FFMpegVideoImageProvider.cs

@@ -57,12 +57,6 @@ namespace MediaBrowser.Controller.Providers.MediaInfo
 
             if (video != null)
             {
-                // Can't extract images if there are no video streams
-                if (video.MediaStreams == null || video.MediaStreams.All(m => m.Type != MediaStreamType.Video))
-                {
-                    return false;
-                }
-
                 if (video.VideoType == VideoType.Iso && video.IsoType.HasValue && _isoManager.CanMount(item.Path))
                 {
                     return true;
@@ -93,17 +87,21 @@ namespace MediaBrowser.Controller.Providers.MediaInfo
             {
                 var video = (Video)item;
 
-                var filename = item.Id + "_" + item.DateModified.Ticks + "_primary";
+                // We can only extract images from videos if we know there's an embedded video stream
+                if (video.MediaStreams != null && video.MediaStreams.Any(m => m.Type == MediaStreamType.Video))
+                {
+                    var filename = item.Id + "_" + item.DateModified.Ticks + "_primary";
 
-                var path = Kernel.Instance.FFMpegManager.VideoImageCache.GetResourcePath(filename, ".jpg");
+                    var path = Kernel.Instance.FFMpegManager.VideoImageCache.GetResourcePath(filename, ".jpg");
 
-                if (!Kernel.Instance.FFMpegManager.VideoImageCache.ContainsFilePath(path))
-                {
-                    return ExtractImage(video, path, cancellationToken);
-                }
+                    if (!Kernel.Instance.FFMpegManager.VideoImageCache.ContainsFilePath(path))
+                    {
+                        return ExtractImage(video, path, cancellationToken);
+                    }
 
-                // Image is already in the cache
-                item.PrimaryImagePath = path;
+                    // Image is already in the cache
+                    item.PrimaryImagePath = path;
+                }
             }
 
             SetLastRefreshed(item, DateTime.UtcNow);

+ 24 - 27
MediaBrowser.Controller/Providers/MediaInfo/FFProbeAudioInfoProvider.cs

@@ -42,41 +42,38 @@ namespace MediaBrowser.Controller.Providers.MediaInfo
         /// <param name="data">The data.</param>
         /// <param name="isoMount">The iso mount.</param>
         /// <returns>Task.</returns>
-        protected override Task Fetch(Audio audio, CancellationToken cancellationToken, FFProbeResult data, IIsoMount isoMount)
+        protected override void Fetch(Audio audio, CancellationToken cancellationToken, FFProbeResult data, IIsoMount isoMount)
         {
-            return Task.Run(() =>
+            if (data.streams == null)
             {
-                if (data.streams == null)
-                {
-                    Logger.Error("Audio item has no streams: " + audio.Path);
-                    return;
-                }
+                Logger.Error("Audio item has no streams: " + audio.Path);
+                return;
+            }
 
-                audio.MediaStreams = data.streams.Select(s => GetMediaStream(s, data.format)).ToList();
+            audio.MediaStreams = data.streams.Select(s => GetMediaStream(s, data.format)).ToList();
 
-                // Get the first audio stream
-                var stream = data.streams.First(s => s.codec_type.Equals("audio", StringComparison.OrdinalIgnoreCase));
+            // Get the first audio stream
+            var stream = data.streams.First(s => s.codec_type.Equals("audio", StringComparison.OrdinalIgnoreCase));
 
-                // Get duration from stream properties
-                var duration = stream.duration;
+            // Get duration from stream properties
+            var duration = stream.duration;
 
-                // If it's not there go into format properties
-                if (string.IsNullOrEmpty(duration))
-                {
-                    duration = data.format.duration;
-                }
+            // If it's not there go into format properties
+            if (string.IsNullOrEmpty(duration))
+            {
+                duration = data.format.duration;
+            }
 
-                // If we got something, parse it
-                if (!string.IsNullOrEmpty(duration))
-                {
-                    audio.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(duration, UsCulture)).Ticks;
-                }
+            // If we got something, parse it
+            if (!string.IsNullOrEmpty(duration))
+            {
+                audio.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(duration, UsCulture)).Ticks;
+            }
 
-                if (data.format.tags != null)
-                {
-                    FetchDataFromTags(audio, data.format.tags);
-                }
-            });
+            if (data.format.tags != null)
+            {
+                FetchDataFromTags(audio, data.format.tags);
+            }
         }
 
         /// <summary>

+ 26 - 29
MediaBrowser.Controller/Providers/MediaInfo/FFProbeVideoInfoProvider.cs

@@ -187,44 +187,41 @@ namespace MediaBrowser.Controller.Providers.MediaInfo
         /// <param name="data">The data.</param>
         /// <param name="isoMount">The iso mount.</param>
         /// <returns>Task.</returns>
-        protected override Task Fetch(Video video, CancellationToken cancellationToken, FFProbeResult data, IIsoMount isoMount)
+        protected override void Fetch(Video video, CancellationToken cancellationToken, FFProbeResult data, IIsoMount isoMount)
         {
-            return Task.Run(() =>
+            if (data.format != null)
             {
-                if (data.format != null)
-                {
-                    // For dvd's this may not always be accurate, so don't set the runtime if the item already has one
-                    var needToSetRuntime = video.VideoType != VideoType.Dvd || video.RunTimeTicks == null || video.RunTimeTicks.Value == 0;
-
-                    if (needToSetRuntime && !string.IsNullOrEmpty(data.format.duration))
-                    {
-                        video.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(data.format.duration, UsCulture)).Ticks;
-                    }
-                }
+                // For dvd's this may not always be accurate, so don't set the runtime if the item already has one
+                var needToSetRuntime = video.VideoType != VideoType.Dvd || video.RunTimeTicks == null || video.RunTimeTicks.Value == 0;
 
-                if (data.streams != null)
+                if (needToSetRuntime && !string.IsNullOrEmpty(data.format.duration))
                 {
-                    video.MediaStreams = data.streams.Select(s => GetMediaStream(s, data.format)).ToList();
+                    video.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(data.format.duration, UsCulture)).Ticks;
                 }
+            }
 
-                if (data.Chapters != null)
-                {
-                    video.Chapters = data.Chapters;
-                }
+            if (data.streams != null)
+            {
+                video.MediaStreams = data.streams.Select(s => GetMediaStream(s, data.format)).ToList();
+            }
 
-                if (video.Chapters == null || video.Chapters.Count == 0)
-                {
-                    AddDummyChapters(video);
-                }
+            if (data.Chapters != null)
+            {
+                video.Chapters = data.Chapters;
+            }
 
-                if (video.VideoType == VideoType.BluRay || (video.IsoType.HasValue && video.IsoType.Value == IsoType.BluRay))
-                {
-                    var inputPath = isoMount != null ? isoMount.MountedPath : video.Path;
-                    FetchBdInfo(video, inputPath, BdInfoCache, cancellationToken);
-                }
+            if (video.Chapters == null || video.Chapters.Count == 0)
+            {
+                AddDummyChapters(video);
+            }
+
+            if (video.VideoType == VideoType.BluRay || (video.IsoType.HasValue && video.IsoType.Value == IsoType.BluRay))
+            {
+                var inputPath = isoMount != null ? isoMount.MountedPath : video.Path;
+                FetchBdInfo(video, inputPath, BdInfoCache, cancellationToken);
+            }
 
-                AddExternalSubtitles(video);
-            });
+            AddExternalSubtitles(video);
         }
 
         /// <summary>

+ 1 - 1
MediaBrowser.Model/Entities/DisplayPreferences.cs

@@ -32,7 +32,7 @@ namespace MediaBrowser.Model.Entities
         /// </summary>
         /// <value>The user id.</value>
         [ProtoMember(1)]
-        public Guid UserId { get; set; }
+        public Guid Id { get; set; }
         /// <summary>
         /// Gets or sets the type of the view.
         /// </summary>

+ 99 - 0
MediaBrowser.Server.Implementations/Library/DisplayPreferencesManager.cs

@@ -0,0 +1,99 @@
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+using System;
+using System.Collections.Concurrent;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Server.Implementations.Library
+{
+    /// <summary>
+    /// Class DisplayPreferencesManager
+    /// </summary>
+    public class DisplayPreferencesManager : IDisplayPreferencesManager
+    {
+        /// <summary>
+        /// The _logger
+        /// </summary>
+        private readonly ILogger _logger;
+
+        /// <summary>
+        /// The _display preferences
+        /// </summary>
+        private readonly ConcurrentDictionary<Guid, Task<DisplayPreferences>> _displayPreferences = new ConcurrentDictionary<Guid, Task<DisplayPreferences>>();
+
+        /// <summary>
+        /// Gets the active user repository
+        /// </summary>
+        /// <value>The display preferences repository.</value>
+        public IDisplayPreferencesRepository Repository { get; set; }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="DisplayPreferencesManager"/> class.
+        /// </summary>
+        /// <param name="logger">The logger.</param>
+        public DisplayPreferencesManager(ILogger logger)
+        {
+            _logger = logger;
+        }
+
+        /// <summary>
+        /// Gets the display preferences.
+        /// </summary>
+        /// <param name="displayPreferencesId">The display preferences id.</param>
+        /// <returns>DisplayPreferences.</returns>
+        public Task<DisplayPreferences> GetDisplayPreferences(Guid displayPreferencesId)
+        {
+            return _displayPreferences.GetOrAdd(displayPreferencesId, keyName => RetrieveDisplayPreferences(displayPreferencesId));
+        }
+
+        /// <summary>
+        /// Retrieves the display preferences.
+        /// </summary>
+        /// <param name="displayPreferencesId">The display preferences id.</param>
+        /// <returns>DisplayPreferences.</returns>
+        private async Task<DisplayPreferences> RetrieveDisplayPreferences(Guid displayPreferencesId)
+        {
+            var displayPreferences = await Repository.GetDisplayPreferences(displayPreferencesId).ConfigureAwait(false);
+
+            return displayPreferences ?? new DisplayPreferences { Id = displayPreferencesId };
+        }
+
+        /// <summary>
+        /// Saves display preferences for an item
+        /// </summary>
+        /// <param name="displayPreferences">The display preferences.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        public async Task SaveDisplayPreferences(DisplayPreferences displayPreferences, CancellationToken cancellationToken)
+        {
+            if (displayPreferences == null)
+            {
+                throw new ArgumentNullException("displayPreferences");
+            }
+            if (displayPreferences.Id == Guid.Empty)
+            {
+                throw new ArgumentNullException("displayPreferences.Id");
+            }
+
+            try
+            {
+                await Repository.SaveDisplayPreferences(displayPreferences,
+                                                                                        cancellationToken).ConfigureAwait(false);
+
+                var newValue = Task.FromResult(displayPreferences);
+
+                // Once it succeeds, put it into the dictionary to make it available to everyone else
+                _displayPreferences.AddOrUpdate(displayPreferences.Id, newValue, delegate { return newValue; });
+            }
+            catch (Exception ex)
+            {
+                _logger.ErrorException("Error saving display preferences", ex);
+
+                throw;
+            }
+        }
+    }
+}

+ 19 - 92
MediaBrowser.Server.Implementations/Library/UserManager.cs

@@ -5,7 +5,6 @@ using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Connectivity;
-using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using System;
 using System.Collections.Concurrent;
@@ -26,8 +25,8 @@ namespace MediaBrowser.Server.Implementations.Library
         /// <summary>
         /// The _active connections
         /// </summary>
-        private readonly List<ClientConnectionInfo> _activeConnections =
-            new List<ClientConnectionInfo>();
+        private readonly ConcurrentDictionary<string, ClientConnectionInfo> _activeConnections =
+            new ConcurrentDictionary<string, ClientConnectionInfo>(StringComparer.OrdinalIgnoreCase);
 
         /// <summary>
         /// The _users
@@ -70,7 +69,7 @@ namespace MediaBrowser.Server.Implementations.Library
         /// <value>All connections.</value>
         public IEnumerable<ClientConnectionInfo> AllConnections
         {
-            get { return _activeConnections.Where(c => GetUserById(c.UserId) != null).OrderByDescending(c => c.LastActivityDate); }
+            get { return _activeConnections.Values.OrderByDescending(c => c.LastActivityDate); }
         }
 
         /// <summary>
@@ -99,11 +98,6 @@ namespace MediaBrowser.Server.Implementations.Library
         /// <value>The configuration manager.</value>
         private IServerConfigurationManager ConfigurationManager { get; set; }
 
-        /// <summary>
-        /// The _user data
-        /// </summary>
-        private readonly ConcurrentDictionary<string, Task<DisplayPreferences>> _displayPreferences = new ConcurrentDictionary<string, Task<DisplayPreferences>>();
-
         private readonly ConcurrentDictionary<string, Task<UserItemData>> _userData = new ConcurrentDictionary<string, Task<UserItemData>>();
         
         /// <summary>
@@ -165,63 +159,6 @@ namespace MediaBrowser.Server.Implementations.Library
         }
         #endregion
 
-        /// <summary>
-        /// Gets the display preferences.
-        /// </summary>
-        /// <param name="userId">The user id.</param>
-        /// <param name="displayPreferencesId">The display preferences id.</param>
-        /// <returns>DisplayPreferences.</returns>
-        public Task<DisplayPreferences> GetDisplayPreferences(Guid userId, Guid displayPreferencesId)
-        {
-            var key = userId + displayPreferencesId.ToString();
-
-            return _displayPreferences.GetOrAdd(key, keyName => RetrieveDisplayPreferences(userId, displayPreferencesId));
-        }
-
-        /// <summary>
-        /// Retrieves the display preferences.
-        /// </summary>
-        /// <param name="userId">The user id.</param>
-        /// <param name="displayPreferencesId">The display preferences id.</param>
-        /// <returns>DisplayPreferences.</returns>
-        private async Task<DisplayPreferences> RetrieveDisplayPreferences(Guid userId, Guid displayPreferencesId)
-        {
-            var displayPreferences = await Kernel.Instance.DisplayPreferencesRepository.GetDisplayPreferences(userId, displayPreferencesId).ConfigureAwait(false);
-
-            return displayPreferences ?? new DisplayPreferences();
-        }
-
-        /// <summary>
-        /// Saves display preferences for an item
-        /// </summary>
-        /// <param name="userId">The user id.</param>
-        /// <param name="displayPreferencesId">The display preferences id.</param>
-        /// <param name="displayPreferences">The display preferences.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        public async Task SaveDisplayPreferences(Guid userId, Guid displayPreferencesId, DisplayPreferences displayPreferences, CancellationToken cancellationToken)
-        {
-            var key = userId + displayPreferencesId.ToString();
-           
-            try
-            {
-                await Kernel.Instance.DisplayPreferencesRepository.SaveDisplayPreferences(userId, displayPreferencesId,
-                                                                                        displayPreferences,
-                                                                                        cancellationToken).ConfigureAwait(false);
-
-                var newValue = Task.FromResult(displayPreferences);
-
-                // Once it succeeds, put it into the dictionary to make it available to everyone else
-                _displayPreferences.AddOrUpdate(key, newValue, delegate { return newValue; });
-            }
-            catch (Exception ex)
-            {
-                _logger.ErrorException("Error saving display preferences", ex);
-
-                throw;
-            }
-        }
-
         /// <summary>
         /// Gets a User by Id
         /// </summary>
@@ -232,7 +169,7 @@ namespace MediaBrowser.Server.Implementations.Library
         {
             if (id == Guid.Empty)
             {
-                throw new ArgumentNullException();
+                throw new ArgumentNullException("id");
             }
 
             return Users.FirstOrDefault(u => u.Id == id);
@@ -376,29 +313,19 @@ namespace MediaBrowser.Server.Implementations.Library
         /// <returns>ClientConnectionInfo.</returns>
         private ClientConnectionInfo GetConnection(Guid userId, string clientType, string deviceId, string deviceName)
         {
-            lock (_activeConnections)
-            {
-                var conn = _activeConnections.FirstOrDefault(c => string.Equals(c.Client, clientType, StringComparison.OrdinalIgnoreCase) && string.Equals(deviceId, c.DeviceId));
-
-                if (conn == null)
-                {
-                    conn = new ClientConnectionInfo
-                    {
-                        UserId = userId,
-                        Client = clientType,
-                        DeviceName = deviceName,
-                        DeviceId = deviceId
-                    };
+            var key = clientType + deviceId;
 
-                    _activeConnections.Add(conn);
-                }
-                else
-                {
-                    conn.UserId = userId;
-                }
+            var connection = _activeConnections.GetOrAdd(key, keyName => new ClientConnectionInfo
+            {
+                UserId = userId,
+                Client = clientType,
+                DeviceName = deviceName,
+                DeviceId = deviceId
+            });
 
-                return conn;
-            }
+            connection.UserId = userId;
+            
+            return connection;
         }
 
         /// <summary>
@@ -802,11 +729,11 @@ namespace MediaBrowser.Server.Implementations.Library
         }
 
         /// <summary>
-        /// Gets the display preferences.
+        /// Gets the user data.
         /// </summary>
         /// <param name="userId">The user id.</param>
         /// <param name="userDataId">The user data id.</param>
-        /// <returns>Task{DisplayPreferences}.</returns>
+        /// <returns>Task{UserItemData}.</returns>
         public Task<UserItemData> GetUserData(Guid userId, Guid userDataId)
         {
             var key = userId + userDataId.ToString();
@@ -815,11 +742,11 @@ namespace MediaBrowser.Server.Implementations.Library
         }
 
         /// <summary>
-        /// Retrieves the display preferences.
+        /// Retrieves the user data.
         /// </summary>
         /// <param name="userId">The user id.</param>
         /// <param name="userDataId">The user data id.</param>
-        /// <returns>DisplayPreferences.</returns>
+        /// <returns>Task{UserItemData}.</returns>
         private async Task<UserItemData> RetrieveUserData(Guid userId, Guid userDataId)
         {
             var userdata = await Kernel.Instance.UserDataRepository.GetUserData(userId, userDataId).ConfigureAwait(false);

+ 1 - 0
MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj

@@ -130,6 +130,7 @@
     <Compile Include="HttpServer\SwaggerService.cs" />
     <Compile Include="IO\DirectoryWatchers.cs" />
     <Compile Include="Library\CoreResolutionIgnoreRule.cs" />
+    <Compile Include="Library\DisplayPreferencesManager.cs" />
     <Compile Include="Library\LibraryManager.cs" />
     <Compile Include="Library\LuceneSearchEngine.cs" />
     <Compile Include="Library\ResolverHelper.cs" />

+ 1 - 1
MediaBrowser.Server.Implementations/Providers/ProviderManager.cs

@@ -104,7 +104,7 @@ namespace MediaBrowser.Server.Implementations.Providers
         /// <param name="providers">The providers.</param>
         public void AddMetadataProviders(IEnumerable<BaseMetadataProvider> providers)
         {
-            MetadataProviders = providers.ToArray();
+            MetadataProviders = providers.OrderBy(e => e.Priority).ToArray();
         }
 
         /// <summary>

+ 48 - 34
MediaBrowser.Server.Implementations/Sqlite/SQLiteDisplayPreferencesRepository.cs

@@ -33,6 +33,18 @@ namespace MediaBrowser.Server.Implementations.Sqlite
             }
         }
 
+        /// <summary>
+        /// Gets a value indicating whether [enable delayed commands].
+        /// </summary>
+        /// <value><c>true</c> if [enable delayed commands]; otherwise, <c>false</c>.</value>
+        protected override bool EnableDelayedCommands
+        {
+            get
+            {
+                return false;
+            }
+        }
+
         /// <summary>
         /// The _protobuf serializer
         /// </summary>
@@ -78,8 +90,8 @@ namespace MediaBrowser.Server.Implementations.Sqlite
 
             string[] queries = {
 
-                                "create table if not exists displaypreferences (id GUID, userId GUID, data BLOB)",
-                                "create unique index if not exists displaypreferencesindex on displaypreferences (id, userId)",
+                                "create table if not exists displaypreferences (id GUID, data BLOB)",
+                                "create unique index if not exists displaypreferencesindex on displaypreferences (id)",
                                 "create table if not exists schema_version (table_name primary key, version)",
                                 //pragmas
                                 "pragma temp_store = memory"
@@ -91,75 +103,77 @@ namespace MediaBrowser.Server.Implementations.Sqlite
         /// <summary>
         /// Save the display preferences associated with an item in the repo
         /// </summary>
-        /// <param name="userId">The user id.</param>
-        /// <param name="displayPreferencesId">The display preferences id.</param>
         /// <param name="displayPreferences">The display preferences.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         /// <exception cref="System.ArgumentNullException">item</exception>
-        public Task SaveDisplayPreferences(Guid userId, Guid displayPreferencesId, DisplayPreferences displayPreferences, CancellationToken cancellationToken)
+        public async Task SaveDisplayPreferences(DisplayPreferences displayPreferences, CancellationToken cancellationToken)
         {
             if (displayPreferences == null)
             {
                 throw new ArgumentNullException("displayPreferences");
             }
-            if (cancellationToken == null)
-            {
-                throw new ArgumentNullException("cancellationToken");
-            }
-            if (userId == Guid.Empty)
+            if (displayPreferences.Id == Guid.Empty)
             {
-                throw new ArgumentNullException("userId");
+                throw new ArgumentNullException("displayPreferences.Id");
             }
-            if (displayPreferencesId == Guid.Empty)
+            if (cancellationToken == null)
             {
-                throw new ArgumentNullException("displayPreferencesId");
+                throw new ArgumentNullException("cancellationToken");
             }
 
             cancellationToken.ThrowIfCancellationRequested();
-            
-            return Task.Run(() =>
+
+            var serialized = _protobufSerializer.SerializeToBytes(displayPreferences);
+
+            cancellationToken.ThrowIfCancellationRequested();
+
+            var cmd = connection.CreateCommand();
+            cmd.CommandText = "replace into displaypreferences (id, data) values (@1, @2)";
+            cmd.AddParam("@1", displayPreferences.Id);
+            cmd.AddParam("@2", serialized);
+
+            using (var tran = connection.BeginTransaction())
             {
-                var serialized = _protobufSerializer.SerializeToBytes(displayPreferences);
+                try
+                {
+                    cmd.Transaction = tran;
 
-                cancellationToken.ThrowIfCancellationRequested();
+                    await cmd.ExecuteNonQueryAsync(cancellationToken);
 
-                var cmd = connection.CreateCommand();
-                cmd.CommandText = "replace into displaypreferences (id, userId, data) values (@1, @2, @3)";
-                cmd.AddParam("@1", displayPreferencesId);
-                cmd.AddParam("@2", userId);
-                cmd.AddParam("@3", serialized);
-                QueueCommand(cmd);
-            });
+                    tran.Commit();
+                }
+                catch (OperationCanceledException)
+                {
+                    tran.Rollback();
+                }
+                catch (Exception e)
+                {
+                    Logger.ErrorException("Failed to commit transaction.", e);
+                    tran.Rollback();
+                }
+            }
         }
 
         /// <summary>
         /// Gets the display preferences.
         /// </summary>
-        /// <param name="userId">The user id.</param>
         /// <param name="displayPreferencesId">The display preferences id.</param>
         /// <returns>Task{DisplayPreferences}.</returns>
         /// <exception cref="System.ArgumentNullException">item</exception>
-        public async Task<DisplayPreferences> GetDisplayPreferences(Guid userId, Guid displayPreferencesId)
+        public async Task<DisplayPreferences> GetDisplayPreferences(Guid displayPreferencesId)
         {
-            if (userId == Guid.Empty)
-            {
-                throw new ArgumentNullException("userId");
-            }
             if (displayPreferencesId == Guid.Empty)
             {
                 throw new ArgumentNullException("displayPreferencesId");
             }
 
             var cmd = connection.CreateCommand();
-            cmd.CommandText = "select data from displaypreferences where id = @id and userId=@userId";
+            cmd.CommandText = "select data from displaypreferences where id = @id";
             
             var idParam = cmd.Parameters.Add("@id", DbType.Guid);
             idParam.Value = displayPreferencesId;
 
-            var userIdParam = cmd.Parameters.Add("@userId", DbType.Guid);
-            userIdParam.Value = userId;
-
             using (var reader = await cmd.ExecuteReaderAsync(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow).ConfigureAwait(false))
             {
                 if (reader.Read())

+ 38 - 12
MediaBrowser.Server.Implementations/Sqlite/SQLiteRepository.cs

@@ -30,7 +30,7 @@ namespace MediaBrowser.Server.Implementations.Sqlite
         /// <summary>
         /// The flush interval
         /// </summary>
-        private const int FlushInterval = 5000;
+        private const int FlushInterval = 2000;
 
         /// <summary>
         /// The flush timer
@@ -43,6 +43,18 @@ namespace MediaBrowser.Server.Implementations.Sqlite
         /// <value>The logger.</value>
         protected ILogger Logger { get; private set; }
 
+        /// <summary>
+        /// Gets a value indicating whether [enable delayed commands].
+        /// </summary>
+        /// <value><c>true</c> if [enable delayed commands]; otherwise, <c>false</c>.</value>
+        protected virtual bool EnableDelayedCommands
+        {
+            get
+            {
+                return true;
+            }
+        }
+
         /// <summary>
         /// Initializes a new instance of the <see cref="SqliteRepository" /> class.
         /// </summary>
@@ -85,8 +97,11 @@ namespace MediaBrowser.Server.Implementations.Sqlite
 
             await connection.OpenAsync().ConfigureAwait(false);
 
-            // Run once
-            FlushTimer = new Timer(Flush, null, TimeSpan.FromMilliseconds(FlushInterval), TimeSpan.FromMilliseconds(-1));
+            if (EnableDelayedCommands)
+            {
+                // Run once
+                FlushTimer = new Timer(Flush, null, TimeSpan.FromMilliseconds(FlushInterval), TimeSpan.FromMilliseconds(-1));
+            }
         }
 
         /// <summary>
@@ -147,16 +162,9 @@ namespace MediaBrowser.Server.Implementations.Sqlite
                 {
                     if (connection != null)
                     {
-                        // If we're not already flushing, do it now
-                        if (!IsFlushing)
-                        {
-                            Flush(null);
-                        }
-
-                        // Don't dispose in the middle of a flush
-                        while (IsFlushing)
+                        if (EnableDelayedCommands)
                         {
-                            Thread.Sleep(25);
+                            FlushOnDispose();
                         }
                         
                         if (connection.IsOpen())
@@ -181,6 +189,24 @@ namespace MediaBrowser.Server.Implementations.Sqlite
             }
         }
 
+        /// <summary>
+        /// Flushes the on dispose.
+        /// </summary>
+        private void FlushOnDispose()
+        {
+            // If we're not already flushing, do it now
+            if (!IsFlushing)
+            {
+                Flush(null);
+            }
+
+            // Don't dispose in the middle of a flush
+            while (IsFlushing)
+            {
+                Thread.Sleep(25);
+            }
+        }
+
         /// <summary>
         /// Queues the command.
         /// </summary>

+ 40 - 11
MediaBrowser.Server.Implementations/Sqlite/SQLiteUserDataRepository.cs

@@ -34,6 +34,18 @@ namespace MediaBrowser.Server.Implementations.Sqlite
             }
         }
 
+        /// <summary>
+        /// Gets a value indicating whether [enable delayed commands].
+        /// </summary>
+        /// <value><c>true</c> if [enable delayed commands]; otherwise, <c>false</c>.</value>
+        protected override bool EnableDelayedCommands
+        {
+            get
+            {
+                return false;
+            }
+        }
+        
         /// <summary>
         /// The _protobuf serializer
         /// </summary>
@@ -106,7 +118,7 @@ namespace MediaBrowser.Server.Implementations.Sqlite
         /// or
         /// userDataId
         /// </exception>
-        public Task SaveUserData(Guid userId, Guid userDataId, UserItemData userData, CancellationToken cancellationToken)
+        public async Task SaveUserData(Guid userId, Guid userDataId, UserItemData userData, CancellationToken cancellationToken)
         {
             if (userData == null)
             {
@@ -127,19 +139,36 @@ namespace MediaBrowser.Server.Implementations.Sqlite
 
             cancellationToken.ThrowIfCancellationRequested();
 
-            return Task.Run(() =>
+            var serialized = _protobufSerializer.SerializeToBytes(userData);
+
+            cancellationToken.ThrowIfCancellationRequested();
+
+            var cmd = connection.CreateCommand();
+            cmd.CommandText = "replace into userdata (id, userId, data) values (@1, @2, @3)";
+            cmd.AddParam("@1", userDataId);
+            cmd.AddParam("@2", userId);
+            cmd.AddParam("@3", serialized);
+
+            using (var tran = connection.BeginTransaction())
             {
-                var serialized = _protobufSerializer.SerializeToBytes(userData);
+                try
+                {
+                    cmd.Transaction = tran;
 
-                cancellationToken.ThrowIfCancellationRequested();
+                    await cmd.ExecuteNonQueryAsync(cancellationToken);
 
-                var cmd = connection.CreateCommand();
-                cmd.CommandText = "replace into userdata (id, userId, data) values (@1, @2, @3)";
-                cmd.AddParam("@1", userDataId);
-                cmd.AddParam("@2", userId);
-                cmd.AddParam("@3", serialized);
-                QueueCommand(cmd);
-            });
+                    tran.Commit();
+                }
+                catch (OperationCanceledException)
+                {
+                    tran.Rollback();
+                }
+                catch (Exception e)
+                {
+                    Logger.ErrorException("Failed to commit transaction.", e);
+                    tran.Rollback();
+                }
+            }
         }
 
         /// <summary>

+ 41 - 12
MediaBrowser.Server.Implementations/Sqlite/SQLiteUserRepository.cs

@@ -45,6 +45,18 @@ namespace MediaBrowser.Server.Implementations.Sqlite
         /// </summary>
         private readonly IApplicationPaths _appPaths;
 
+        /// <summary>
+        /// Gets a value indicating whether [enable delayed commands].
+        /// </summary>
+        /// <value><c>true</c> if [enable delayed commands]; otherwise, <c>false</c>.</value>
+        protected override bool EnableDelayedCommands
+        {
+            get
+            {
+                return false;
+            }
+        }
+
         /// <summary>
         /// Initializes a new instance of the <see cref="SQLiteUserDataRepository" /> class.
         /// </summary>
@@ -97,7 +109,7 @@ namespace MediaBrowser.Server.Implementations.Sqlite
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         /// <exception cref="System.ArgumentNullException">user</exception>
-        public Task SaveUser(User user, CancellationToken cancellationToken)
+        public async Task SaveUser(User user, CancellationToken cancellationToken)
         {
             if (user == null)
             {
@@ -109,20 +121,37 @@ namespace MediaBrowser.Server.Implementations.Sqlite
                 throw new ArgumentNullException("cancellationToken");
             }
 
-            return Task.Run(() =>
-            {
-                cancellationToken.ThrowIfCancellationRequested();
+            cancellationToken.ThrowIfCancellationRequested();
 
-                var serialized = _jsonSerializer.SerializeToBytes(user);
+            var serialized = _jsonSerializer.SerializeToBytes(user);
 
-                cancellationToken.ThrowIfCancellationRequested();
+            cancellationToken.ThrowIfCancellationRequested();
 
-                var cmd = connection.CreateCommand();
-                cmd.CommandText = "replace into users (guid, data) values (@1, @2)";
-                cmd.AddParam("@1", user.Id);
-                cmd.AddParam("@2", serialized);
-                QueueCommand(cmd);
-            });
+            var cmd = connection.CreateCommand();
+            cmd.CommandText = "replace into users (guid, data) values (@1, @2)";
+            cmd.AddParam("@1", user.Id);
+            cmd.AddParam("@2", serialized);
+
+            using (var tran = connection.BeginTransaction())
+            {
+                try
+                {
+                    cmd.Transaction = tran;
+
+                    await cmd.ExecuteNonQueryAsync(cancellationToken);
+
+                    tran.Commit();
+                }
+                catch (OperationCanceledException)
+                {
+                    tran.Rollback();
+                }
+                catch (Exception e)
+                {
+                    Logger.ErrorException("Failed to commit transaction.", e);
+                    tran.Rollback();
+                }
+            }
         }
 
         /// <summary>

+ 1 - 1
MediaBrowser.ServerApplication/App.xaml.cs

@@ -165,7 +165,7 @@ namespace MediaBrowser.ServerApplication
 
                 await CompositionRoot.Init();
 
-                var win = new MainWindow(CompositionRoot.LogManager, CompositionRoot, CompositionRoot.ServerConfigurationManager, CompositionRoot.UserManager, CompositionRoot.LibraryManager, CompositionRoot.JsonSerializer);
+                var win = new MainWindow(CompositionRoot.LogManager, CompositionRoot, CompositionRoot.ServerConfigurationManager, CompositionRoot.UserManager, CompositionRoot.LibraryManager, CompositionRoot.JsonSerializer, CompositionRoot.DisplayPreferencesManager);
 
                 win.Show();
             }

+ 47 - 10
MediaBrowser.ServerApplication/ApplicationHost.cs

@@ -1,8 +1,4 @@
-using System.Diagnostics;
-using System.Net.Cache;
-using System.Net.Http;
-using System.Net.Sockets;
-using MediaBrowser.Api;
+using MediaBrowser.Api;
 using MediaBrowser.Common;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Constants;
@@ -46,8 +42,10 @@ using MediaBrowser.ServerApplication.Implementations;
 using MediaBrowser.WebDashboard.Api;
 using System;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.IO;
 using System.Linq;
+using System.Net.Sockets;
 using System.Reflection;
 using System.Threading;
 using System.Threading.Tasks;
@@ -140,6 +138,11 @@ namespace MediaBrowser.ServerApplication
         /// </summary>
         /// <value>The UDP server.</value>
         private UdpServer UdpServer { get; set; }
+        /// <summary>
+        /// Gets or sets the display preferences manager.
+        /// </summary>
+        /// <value>The display preferences manager.</value>
+        internal IDisplayPreferencesManager DisplayPreferencesManager { get; set; }
 
         /// <summary>
         /// The full path to our startmenu shortcut
@@ -212,8 +215,12 @@ namespace MediaBrowser.ServerApplication
             ProviderManager = new ProviderManager(HttpClient, ServerConfigurationManager, DirectoryWatchers, LogManager);
             RegisterSingleInstance(ProviderManager);
 
+            DisplayPreferencesManager = new DisplayPreferencesManager(LogManager.GetLogger("DisplayPreferencesManager"));
+            RegisterSingleInstance(DisplayPreferencesManager);
+
             RegisterSingleInstance<ILibrarySearchEngine>(() => new LuceneSearchEngine());
-            
+
+            await ConfigureRepositories().ConfigureAwait(false);
             SetKernelProperties();
             SetStaticProperties();
         }
@@ -229,7 +236,6 @@ namespace MediaBrowser.ServerApplication
             Parallel.Invoke(
                 () => ServerKernel.UserDataRepositories = GetExports<IUserDataRepository>(),
                 () => ServerKernel.UserRepositories = GetExports<IUserRepository>(),
-                () => ServerKernel.DisplayPreferencesRepositories = GetExports<IDisplayPreferencesRepository>(),
                 () => ServerKernel.ItemRepositories = GetExports<IItemRepository>(),
                 () => ServerKernel.WeatherProviders = GetExports<IWeatherProvider>(),
                 () => ServerKernel.ImageEnhancers = GetExports<IImageEnhancer>().OrderBy(e => e.Priority).ToArray(),
@@ -237,6 +243,21 @@ namespace MediaBrowser.ServerApplication
                 );
         }
 
+        /// <summary>
+        /// Configures the repositories.
+        /// </summary>
+        /// <returns>Task.</returns>
+        private async Task ConfigureRepositories()
+        {
+            var displayPreferencesRepositories = GetExports<IDisplayPreferencesRepository>();
+
+            var repo = GetRepository(displayPreferencesRepositories, ServerConfigurationManager.Configuration.DisplayPreferencesRepository);
+
+            await repo.Initialize().ConfigureAwait(false);
+
+            ((DisplayPreferencesManager)DisplayPreferencesManager).Repository = repo;
+        }
+
         /// <summary>
         /// Dirty hacks
         /// </summary>
@@ -277,7 +298,7 @@ namespace MediaBrowser.ServerApplication
 
                 () => LibraryManager.AddParts(GetExports<IResolverIgnoreRule>(), GetExports<IVirtualFolderCreator>(), GetExports<IItemResolver>(), GetExports<IIntroProvider>(), GetExports<IBaseItemComparer>()),
 
-                () => ProviderManager.AddMetadataProviders(GetExports<BaseMetadataProvider>().OrderBy(e => e.Priority).ToArray())
+                () => ProviderManager.AddMetadataProviders(GetExports<BaseMetadataProvider>().ToArray())
                 );
 
             UdpServer = new UdpServer(Logger, NetworkManager, ServerConfigurationManager);
@@ -409,8 +430,8 @@ namespace MediaBrowser.ServerApplication
         public override void Shutdown()
         {
             App.Instance.Dispatcher.Invoke(App.Instance.Shutdown);
-        }			        
-        
+        }
+
         /// <summary>
         /// Registers the server with administrator access.
         /// </summary>
@@ -450,5 +471,21 @@ namespace MediaBrowser.ServerApplication
                 process.WaitForExit();
             }
         }
+
+        /// <summary>
+        /// Gets the repository.
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="repositories">The repositories.</param>
+        /// <param name="name">The name.</param>
+        /// <returns>``0.</returns>
+        private T GetRepository<T>(IEnumerable<T> repositories, string name)
+            where T : class, IRepository
+        {
+            var enumerable = repositories as T[] ?? repositories.ToArray();
+
+            return enumerable.FirstOrDefault(r => string.Equals(r.Name, name, StringComparison.OrdinalIgnoreCase)) ??
+                   enumerable.FirstOrDefault();
+        }
     }
 }

+ 15 - 9
MediaBrowser.ServerApplication/LibraryExplorer.xaml.cs

@@ -33,7 +33,7 @@ namespace MediaBrowser.ServerApplication
 
         private readonly IJsonSerializer _jsonSerializer;
         private readonly ILibraryManager _libraryManager;
-        private readonly IUserManager _userManager;
+        private readonly IDisplayPreferencesManager _displayPreferencesManager;
 
         /// <summary>
         /// The current user
@@ -42,12 +42,18 @@ namespace MediaBrowser.ServerApplication
         /// <summary>
         /// Initializes a new instance of the <see cref="LibraryExplorer" /> class.
         /// </summary>
-        public LibraryExplorer(IJsonSerializer jsonSerializer, ILogger logger, IApplicationHost appHost, IUserManager userManager, ILibraryManager libraryManager)
+        /// <param name="jsonSerializer">The json serializer.</param>
+        /// <param name="logger">The logger.</param>
+        /// <param name="appHost">The app host.</param>
+        /// <param name="userManager">The user manager.</param>
+        /// <param name="libraryManager">The library manager.</param>
+        /// <param name="displayPreferencesManager">The display preferences manager.</param>
+        public LibraryExplorer(IJsonSerializer jsonSerializer, ILogger logger, IApplicationHost appHost, IUserManager userManager, ILibraryManager libraryManager, IDisplayPreferencesManager displayPreferencesManager)
         {
             _logger = logger;
             _jsonSerializer = jsonSerializer;
             _libraryManager = libraryManager;
-            _userManager = userManager;
+            _displayPreferencesManager = displayPreferencesManager;
 
             InitializeComponent();
             lblVersion.Content = "Version: " + appHost.ApplicationVersion;
@@ -91,7 +97,7 @@ namespace MediaBrowser.ServerApplication
                                         var currentFolder = folder;
                                        Task.Factory.StartNew(() =>
                                         {
-                                            var prefs = ddlProfile.SelectedItem != null ? _userManager.GetDisplayPreferences((ddlProfile.SelectedItem as User).Id, currentFolder.DisplayPreferencesId).Result ?? new DisplayPreferences { SortBy = ItemSortBy.SortName } : new DisplayPreferences { SortBy = ItemSortBy.SortName };
+                                            var prefs = ddlProfile.SelectedItem != null ? _displayPreferencesManager.GetDisplayPreferences(currentFolder.GetDisplayPreferencesId((ddlProfile.SelectedItem as User).Id)).Result ?? new DisplayPreferences { SortBy = ItemSortBy.SortName } : new DisplayPreferences { SortBy = ItemSortBy.SortName };
                                             var node = new TreeViewItem { Tag = currentFolder };
 
                                             var subChildren = currentFolder.GetChildren(CurrentUser, prefs.IndexBy);
@@ -144,7 +150,7 @@ namespace MediaBrowser.ServerApplication
                 var subFolder = item as Folder;
                 if (subFolder != null)
                 {
-                    var prefs = _userManager.GetDisplayPreferences(user.Id, subFolder.DisplayPreferencesId).Result;
+                    var prefs = _displayPreferencesManager.GetDisplayPreferences(subFolder.GetDisplayPreferencesId(user.Id)).Result;
                     
                     AddChildren(node, OrderBy(subFolder.GetChildren(user), user, prefs.SortBy), user);
                     node.Header = item.Name + " (" + node.Items.Count + ")";
@@ -201,8 +207,8 @@ namespace MediaBrowser.ServerApplication
 
                     var prefs =
                         await
-                        _userManager.GetDisplayPreferences((ddlProfile.SelectedItem as User).Id,
-                                                           folder.DisplayPreferencesId);
+                        _displayPreferencesManager.GetDisplayPreferences(folder.GetDisplayPreferencesId((ddlProfile.SelectedItem as User).Id));
+
                     ddlIndexBy.SelectedItem = prefs != null
                                                   ? prefs.IndexBy ?? LocalizedStrings.Instance.GetString("NoneDispPref")
                                                   : LocalizedStrings.Instance.GetString("NoneDispPref");
@@ -360,7 +366,7 @@ namespace MediaBrowser.ServerApplication
                 var folder = treeItem != null
                                  ? treeItem.Tag as Folder
                                  : null;
-                var prefs = folder != null ? _userManager.GetDisplayPreferences(CurrentUser.Id, folder.DisplayPreferencesId).Result : new DisplayPreferences {SortBy = ItemSortBy.SortName};
+                var prefs = folder != null ? _displayPreferencesManager.GetDisplayPreferences(folder.GetDisplayPreferencesId(CurrentUser.Id)).Result : new DisplayPreferences { SortBy = ItemSortBy.SortName };
                 if (folder != null && prefs.IndexBy != ddlIndexBy.SelectedItem as string)
                 {
                     //grab UI context so we can update within the below task
@@ -401,7 +407,7 @@ namespace MediaBrowser.ServerApplication
                 var folder = treeItem != null
                                  ? treeItem.Tag as Folder
                                  : null;
-                var prefs = folder != null ? _userManager.GetDisplayPreferences(CurrentUser.Id, folder.DisplayPreferencesId).Result : new DisplayPreferences();
+                var prefs = folder != null ? _displayPreferencesManager.GetDisplayPreferences(folder.GetDisplayPreferencesId(CurrentUser.Id)).Result : new DisplayPreferences();
                 if (folder != null && prefs.SortBy != ddlSortBy.SelectedItem as string)
                 {
                     //grab UI context so we can update within the below task

+ 4 - 2
MediaBrowser.ServerApplication/MainWindow.xaml.cs

@@ -43,6 +43,7 @@ namespace MediaBrowser.ServerApplication
         private readonly IUserManager _userManager;
         private readonly ILibraryManager _libraryManager;
         private readonly IJsonSerializer _jsonSerializer;
+        private readonly IDisplayPreferencesManager _displayPreferencesManager;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="MainWindow" /> class.
@@ -51,7 +52,7 @@ namespace MediaBrowser.ServerApplication
         /// <param name="logger">The logger.</param>
         /// <param name="appHost">The app host.</param>
         /// <exception cref="System.ArgumentNullException">logger</exception>
-        public MainWindow(ILogManager logManager, IApplicationHost appHost, IServerConfigurationManager configurationManager, IUserManager userManager, ILibraryManager libraryManager, IJsonSerializer jsonSerializer)
+        public MainWindow(ILogManager logManager, IApplicationHost appHost, IServerConfigurationManager configurationManager, IUserManager userManager, ILibraryManager libraryManager, IJsonSerializer jsonSerializer, IDisplayPreferencesManager displayPreferencesManager)
         {
             if (logManager == null)
             {
@@ -73,6 +74,7 @@ namespace MediaBrowser.ServerApplication
             _userManager = userManager;
             _libraryManager = libraryManager;
             _jsonSerializer = jsonSerializer;
+            _displayPreferencesManager = displayPreferencesManager;
 
             InitializeComponent();
 
@@ -223,7 +225,7 @@ namespace MediaBrowser.ServerApplication
         /// <param name="e">The <see cref="RoutedEventArgs" /> instance containing the event data.</param>
         private void cmOpenExplorer_click(object sender, RoutedEventArgs e)
         {
-            new LibraryExplorer(_jsonSerializer, _logger, _appHost, _userManager, _libraryManager).Show();
+            new LibraryExplorer(_jsonSerializer, _logger, _appHost, _userManager, _libraryManager, _displayPreferencesManager).Show();
         }
 
         /// <summary>

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

@@ -436,6 +436,6 @@ del "$(SolutionDir)..\Deploy\MBServer.zip"
     <GetAssemblyIdentity AssemblyFiles="$(TargetPath)">
       <Output TaskParameter="Assemblies" ItemName="CurrentAssembly" />
     </GetAssemblyIdentity>
-    <Exec Command="copy $(SolutionDir)..\Deploy\MBServer.zip  $(SolutionDir)..\Deploy\MBServer_%(CurrentAssembly.Version).zip /y" Condition="'$(ConfigurationName)' == 'Release'" />
+    <Exec Command="copy &quot;$(SolutionDir)..\Deploy\MBServer.zip&quot;  &quot;$(SolutionDir)..\Deploy\MBServer_%(CurrentAssembly.Version).zip&quot; /y" Condition="'$(ConfigurationName)' == 'Release'" />
   </Target>
 </Project>

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

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Common.Internal</id>
-        <version>3.0.66</version>
+        <version>3.0.67</version>
         <title>MediaBrowser.Common.Internal</title>
         <authors>Luke</authors>
         <owners>ebr,Luke,scottisafool</owners>
@@ -12,7 +12,7 @@
         <description>Contains common components shared by Media Browser Theatre and Media Browser Server. Not intended for plugin developer consumption.</description>
         <copyright>Copyright © Media Browser 2013</copyright>
         <dependencies>
-            <dependency id="MediaBrowser.Common" version="3.0.66" />
+            <dependency id="MediaBrowser.Common" version="3.0.67" />
             <dependency id="NLog" version="2.0.0.2000" />
             <dependency id="ServiceStack.Text" version="3.9.38" />
             <dependency id="protobuf-net" version="2.0.0.621" />

+ 1 - 1
Nuget/MediaBrowser.Common.nuspec

@@ -2,7 +2,7 @@
 <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
     <metadata>
         <id>MediaBrowser.Common</id>
-        <version>3.0.66</version>
+        <version>3.0.67</version>
         <title>MediaBrowser.Common</title>
         <authors>Media Browser Team</authors>
         <owners>ebr,Luke,scottisafool</owners>

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

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