Procházet zdrojové kódy

start using user policy

Luke Pulverenti před 10 roky
rodič
revize
8807e80d0a
66 změnil soubory, kde provedl 546 přidání a 323 odebrání
  1. 2 2
      MediaBrowser.Api/Library/LibraryService.cs
  2. 1 1
      MediaBrowser.Api/LiveTv/LiveTvService.cs
  3. 1 1
      MediaBrowser.Api/NotificationsService.cs
  4. 38 10
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  5. 1 1
      MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
  6. 1 1
      MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
  7. 1 1
      MediaBrowser.Api/Playback/Hls/MpegDashService.cs
  8. 1 1
      MediaBrowser.Api/Playback/Progressive/AudioService.cs
  9. 1 1
      MediaBrowser.Api/Playback/Progressive/VideoService.cs
  10. 2 2
      MediaBrowser.Api/Session/SessionsService.cs
  11. 40 32
      MediaBrowser.Api/UserService.cs
  12. 1 1
      MediaBrowser.Controller/Channels/Channel.cs
  13. 3 2
      MediaBrowser.Controller/Channels/ChannelAudioItem.cs
  14. 2 1
      MediaBrowser.Controller/Channels/ChannelFolderItem.cs
  15. 2 1
      MediaBrowser.Controller/Channels/ChannelVideoItem.cs
  16. 2 1
      MediaBrowser.Controller/Entities/Audio/Audio.cs
  17. 2 1
      MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
  18. 2 1
      MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
  19. 6 5
      MediaBrowser.Controller/Entities/BaseItem.cs
  20. 2 1
      MediaBrowser.Controller/Entities/Book.cs
  21. 2 2
      MediaBrowser.Controller/Entities/Folder.cs
  22. 2 1
      MediaBrowser.Controller/Entities/Game.cs
  23. 2 1
      MediaBrowser.Controller/Entities/GameSystem.cs
  24. 2 1
      MediaBrowser.Controller/Entities/Movies/BoxSet.cs
  25. 2 1
      MediaBrowser.Controller/Entities/Movies/Movie.cs
  26. 2 1
      MediaBrowser.Controller/Entities/MusicVideo.cs
  27. 3 2
      MediaBrowser.Controller/Entities/Photo.cs
  28. 3 2
      MediaBrowser.Controller/Entities/PhotoAlbum.cs
  29. 2 1
      MediaBrowser.Controller/Entities/TV/Episode.cs
  30. 2 1
      MediaBrowser.Controller/Entities/TV/Season.cs
  31. 2 1
      MediaBrowser.Controller/Entities/TV/Series.cs
  32. 2 1
      MediaBrowser.Controller/Entities/Trailer.cs
  33. 16 56
      MediaBrowser.Controller/Entities/User.cs
  34. 2 1
      MediaBrowser.Controller/Entities/UserView.cs
  35. 15 7
      MediaBrowser.Controller/Library/IUserManager.cs
  36. 2 1
      MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs
  37. 2 1
      MediaBrowser.Controller/LiveTv/LiveTvChannel.cs
  38. 2 1
      MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
  39. 2 1
      MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs
  40. 2 1
      MediaBrowser.Controller/LiveTv/RecordingGroup.cs
  41. 3 0
      MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
  42. 3 0
      MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
  43. 7 6
      MediaBrowser.Model/Configuration/UserConfiguration.cs
  44. 1 0
      MediaBrowser.Model/MediaBrowser.Model.csproj
  45. 62 1
      MediaBrowser.Model/Users/UserPolicy.cs
  46. 1 1
      MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs
  47. 17 18
      MediaBrowser.Server.Implementations/Connect/ConnectManager.cs
  48. 2 2
      MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs
  49. 4 4
      MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs
  50. 7 11
      MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
  51. 7 11
      MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs
  52. 1 49
      MediaBrowser.Server.Implementations/Library/Resolvers/FolderResolver.cs
  53. 0 6
      MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
  54. 11 1
      MediaBrowser.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs
  55. 79 0
      MediaBrowser.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs
  56. 7 9
      MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
  57. 131 32
      MediaBrowser.Server.Implementations/Library/UserManager.cs
  58. 2 2
      MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
  59. 6 3
      MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json
  60. 0 1
      MediaBrowser.Server.Implementations/Localization/Server/server.json
  61. 1 0
      MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
  62. 1 1
      MediaBrowser.Server.Implementations/Notifications/NotificationManager.cs
  63. 2 1
      MediaBrowser.Server.Implementations/Sync/SyncManager.cs
  64. 10 3
      MediaBrowser.Server.Implementations/Sync/SyncRepository.cs
  65. 1 1
      MediaBrowser.Server.Startup.Common/ApplicationHost.cs
  66. 0 9
      MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj

+ 2 - 2
MediaBrowser.Api/Library/LibraryService.cs

@@ -479,14 +479,14 @@ namespace MediaBrowser.Api.Library
             }
             else if (item is ILiveTvRecording)
             {
-                if (!user.Configuration.EnableLiveTvManagement)
+                if (!user.Policy.EnableLiveTvManagement)
                 {
                     throw new UnauthorizedAccessException();
                 }
             }
             else
             {
-                if (!user.Configuration.EnableContentDeletion)
+                if (!user.Policy.EnableContentDeletion)
                 {
                     throw new UnauthorizedAccessException();
                 }

+ 1 - 1
MediaBrowser.Api/LiveTv/LiveTvService.cs

@@ -319,7 +319,7 @@ namespace MediaBrowser.Api.LiveTv
                 throw new UnauthorizedAccessException("Anonymous live tv management is not allowed.");
             }
 
-            if (!user.Configuration.EnableLiveTvManagement)
+            if (!user.Policy.EnableLiveTvManagement)
             {
                 throw new UnauthorizedAccessException("The current user does not have permission to manage live tv.");
             }

+ 1 - 1
MediaBrowser.Api/NotificationsService.cs

@@ -135,7 +135,7 @@ namespace MediaBrowser.Api
                 Level = request.Level,
                 Name = request.Name,
                 Url = request.Url,
-                UserIds = _userManager.Users.Where(i => i.Configuration.IsAdministrator).Select(i => i.Id.ToString("N")).ToList()
+                UserIds = _userManager.Users.Where(i => i.Policy.IsAdministrator).Select(i => i.Id.ToString("N")).ToList()
             };
 
             await _notificationManager.SendNotification(notification, CancellationToken.None).ConfigureAwait(false);

+ 38 - 10
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -202,6 +202,10 @@ namespace MediaBrowser.Api.Playback
             {
                 args += " -map -0:s";
             }
+            else if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
+            {
+                args += " -map 1:0 -sn";
+            }
 
             return args;
         }
@@ -273,7 +277,7 @@ namespace MediaBrowser.Api.Playback
                 // Recommended per docs
                 return Math.Max(Environment.ProcessorCount - 1, 2);
             }
-            
+
             // Use more when this is true. -re will keep cpu usage under control
             if (state.ReadInputAtNativeFramerate)
             {
@@ -666,9 +670,18 @@ namespace MediaBrowser.Api.Playback
                 videoSizeParam = string.Format(",scale={0}:{1}", state.VideoStream.Width.Value.ToString(UsCulture), state.VideoStream.Height.Value.ToString(UsCulture));
             }
 
-            return string.Format(" -filter_complex \"[0:{0}]format=yuva444p{3},lut=u=128:v=128:y=gammaval(.3)[sub] ; [0:{1}] [sub] overlay{2}\"",
-                state.SubtitleStream.Index,
-                state.VideoStream.Index,
+            var mapPrefix = state.SubtitleStream.IsExternal ?
+                1 :
+                0;
+
+            var subtitleStreamIndex = state.SubtitleStream.IsExternal
+                ? 0
+                : state.SubtitleStream.Index;
+
+            return string.Format(" -filter_complex \"[{0}:{1}]format=yuva444p{4},lut=u=128:v=128:y=gammaval(.3)[sub] ; [0:{2}] [sub] overlay{3}\"",
+                mapPrefix.ToString(UsCulture),
+                subtitleStreamIndex.ToString(UsCulture),
+                state.VideoStream.Index.ToString(UsCulture),
                 outputSizeParam,
                 videoSizeParam);
         }
@@ -812,6 +825,21 @@ namespace MediaBrowser.Api.Playback
         /// <param name="state">The state.</param>
         /// <returns>System.String.</returns>
         protected string GetInputArgument(string transcodingJobId, StreamState state)
+        {
+            var arg = "-i " + GetInputPathArgument(transcodingJobId, state);
+
+            if (state.SubtitleStream != null)
+            {
+                if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
+                {
+                    arg += " -i " + state.SubtitleStream.Path;
+                }
+            }
+
+            return arg;
+        }
+
+        private string GetInputPathArgument(string transcodingJobId, StreamState state)
         {
             if (state.InputProtocol == MediaProtocol.File &&
                state.RunTimeTicks.HasValue &&
@@ -883,7 +911,7 @@ namespace MediaBrowser.Api.Playback
                     state.InputProtocol = streamInfo.Protocol;
 
                     await Task.Delay(1500, cancellationTokenSource.Token).ConfigureAwait(false);
-                    
+
                     AttachMediaStreamInfo(state, streamInfo, state.VideoRequest, state.RequestedUrl);
                     checkCodecs = true;
                 }
@@ -913,8 +941,8 @@ namespace MediaBrowser.Api.Playback
         /// <param name="cancellationTokenSource">The cancellation token source.</param>
         /// <param name="workingDirectory">The working directory.</param>
         /// <returns>Task.</returns>
-        protected async Task<TranscodingJob> StartFfMpeg(StreamState state, 
-            string outputPath, 
+        protected async Task<TranscodingJob> StartFfMpeg(StreamState state,
+            string outputPath,
             CancellationTokenSource cancellationTokenSource,
             string workingDirectory = null)
         {
@@ -1103,7 +1131,7 @@ namespace MediaBrowser.Api.Playback
                     if (scale.HasValue)
                     {
                         long val;
-                        
+
                         if (long.TryParse(size, NumberStyles.Any, UsCulture, out val))
                         {
                             bytesTranscoded = val * scale.Value;
@@ -1642,7 +1670,7 @@ namespace MediaBrowser.Api.Playback
 
             if (string.IsNullOrEmpty(container))
             {
-                container = request.Static ? 
+                container = request.Static ?
                     state.InputContainer :
                     (Path.GetExtension(GetOutputFilePath(state)) ?? string.Empty).TrimStart('.');
             }
@@ -1717,7 +1745,7 @@ namespace MediaBrowser.Api.Playback
 
             AttachMediaStreamInfo(state, mediaSource.MediaStreams, videoRequest, requestedUrl);
         }
-        
+
         private void AttachMediaStreamInfo(StreamState state,
             List<MediaStream> mediaStreams,
             VideoStreamRequest videoRequest,

+ 1 - 1
MediaBrowser.Api/Playback/Hls/BaseHlsService.cs

@@ -239,7 +239,7 @@ namespace MediaBrowser.Api.Playback.Hls
                     "hls/" + Path.GetFileNameWithoutExtension(outputPath));
             }
 
-            var args = string.Format("{0} {1} -i {2} -map_metadata -1 -threads {3} {4} {5} -sc_threshold 0 {6} -hls_time {7} -start_number {8} -hls_list_size {9}{10} -y \"{11}\"",
+            var args = string.Format("{0} {1} {2} -map_metadata -1 -threads {3} {4} {5} -sc_threshold 0 {6} -hls_time {7} -start_number {8} -hls_list_size {9}{10} -y \"{11}\"",
                 itsOffset,
                 inputModifier,
                 GetInputArgument(transcodingJobId, state),

+ 1 - 1
MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs

@@ -677,7 +677,7 @@ namespace MediaBrowser.Api.Playback.Hls
             // If isEncoding is true we're actually starting ffmpeg
             var startNumberParam = isEncoding ? GetStartNumber(state).ToString(UsCulture) : "0";
 
-            var args = string.Format("{0} -i {1} -map_metadata -1 -threads {2} {3} {4} -copyts -flags -global_header {5} -hls_time {6} -start_number {7} -hls_list_size {8} -y \"{9}\"",
+            var args = string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -copyts -flags -global_header {5} -hls_time {6} -start_number {7} -hls_list_size {8} -y \"{9}\"",
                 inputModifier,
                 GetInputArgument(transcodingJobId, state),
                 threads,

+ 1 - 1
MediaBrowser.Api/Playback/Hls/MpegDashService.cs

@@ -627,7 +627,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
             var segmentFilename = Path.GetFileNameWithoutExtension(outputPath) + "%03d" + GetSegmentFileExtension(state);
 
-            var args = string.Format("{0} -i {1} -map_metadata -1 -threads {2} {3} {4} -copyts {5} -f ssegment -segment_time {6} -segment_list_size {8} -segment_list \"{9}\" {10}",
+            var args = string.Format("{0} {1} -map_metadata -1 -threads {2} {3} {4} -copyts {5} -f ssegment -segment_time {6} -segment_list_size {8} -segment_list \"{9}\" {10}",
                 inputModifier,
                 GetInputArgument(transcodingJobId, state),
                 threads,

+ 1 - 1
MediaBrowser.Api/Playback/Progressive/AudioService.cs

@@ -82,7 +82,7 @@ namespace MediaBrowser.Api.Playback.Progressive
 
             var inputModifier = GetInputModifier(state);
 
-            return string.Format("{0} -i {1} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1 -y \"{5}\"",
+            return string.Format("{0} {1} -threads {2}{3} {4} -id3v2_version 3 -write_id3v1 1 -y \"{5}\"",
                 inputModifier,
                 GetInputArgument(transcodingJobId, state),
                 threads,

+ 1 - 1
MediaBrowser.Api/Playback/Progressive/VideoService.cs

@@ -103,7 +103,7 @@ namespace MediaBrowser.Api.Playback.Progressive
 
             var inputModifier = GetInputModifier(state);
 
-            return string.Format("{0} -i {1}{2} {3} {4} -map_metadata -1 -threads {5} {6}{7} -y \"{8}\"",
+            return string.Format("{0} {1}{2} {3} {4} -map_metadata -1 -threads {5} {6}{7} -y \"{8}\"",
                 inputModifier,
                 GetInputArgument(transcodingJobId, state),
                 keyFrame,

+ 2 - 2
MediaBrowser.Api/Session/SessionsService.cs

@@ -373,12 +373,12 @@ namespace MediaBrowser.Api.Session
 
                 var user = _userManager.GetUserById(request.ControllableByUserId.Value);
 
-                if (!user.Configuration.EnableRemoteControlOfOtherUsers)
+                if (!user.Policy.EnableRemoteControlOfOtherUsers)
                 {
                     result = result.Where(i => i.ContainsUser(request.ControllableByUserId.Value));
                 }
 
-                if (!user.Configuration.EnableSharedDeviceControl)
+                if (!user.Policy.EnableSharedDeviceControl)
                 {
                     result = result.Where(i => !i.UserId.HasValue);
                 }

+ 40 - 32
MediaBrowser.Api/UserService.cs

@@ -264,12 +264,12 @@ namespace MediaBrowser.Api
 
             if (request.IsDisabled.HasValue)
             {
-                users = users.Where(i => i.Configuration.IsDisabled == request.IsDisabled.Value);
+                users = users.Where(i => i.Policy.IsDisabled == request.IsDisabled.Value);
             }
 
             if (request.IsHidden.HasValue)
             {
-                users = users.Where(i => i.Configuration.IsHidden == request.IsHidden.Value);
+                users = users.Where(i => i.Policy.IsHidden == request.IsHidden.Value);
             }
 
             if (request.IsGuest.HasValue)
@@ -445,39 +445,13 @@ namespace MediaBrowser.Api
 
             var user = _userManager.GetUserById(id);
 
-            // If removing admin access
-            if (!dtoUser.Configuration.IsAdministrator && user.Configuration.IsAdministrator)
-            {
-                if (_userManager.Users.Count(i => i.Configuration.IsAdministrator) == 1)
-                {
-                    throw new ArgumentException("There must be at least one user in the system with administrative access.");
-                }
-            }
-
-            // If disabling
-            if (dtoUser.Configuration.IsDisabled && user.Configuration.IsAdministrator)
-            {
-                throw new ArgumentException("Administrators cannot be disabled.");
-            }
-
-            // If disabling
-            if (dtoUser.Configuration.IsDisabled && !user.Configuration.IsDisabled)
-            {
-                if (_userManager.Users.Count(i => !i.Configuration.IsDisabled) == 1)
-                {
-                    throw new ArgumentException("There must be at least one enabled user in the system.");
-                }
-
-                await _sessionMananger.RevokeUserTokens(user.Id.ToString("N")).ConfigureAwait(false);
-            }
-
             var task = user.Name.Equals(dtoUser.Name, StringComparison.Ordinal) ?
                 _userManager.UpdateUser(user) :
                 _userManager.RenameUser(user, dtoUser.Name);
 
             await task.ConfigureAwait(false);
 
-            user.UpdateConfiguration(dtoUser.Configuration);
+            await _userManager.UpdateConfiguration(dtoUser.Id, dtoUser.Configuration);
         }
 
         /// <summary>
@@ -515,14 +489,48 @@ namespace MediaBrowser.Api
 
         public void Post(UpdateUserConfiguration request)
         {
-            var user = _userManager.GetUserById(request.Id);
-            user.UpdateConfiguration(request);
+            var task = _userManager.UpdateConfiguration(request.Id, request);
+
+            Task.WaitAll(task);
         }
 
         public void Post(UpdateUserPolicy request)
         {
-            var task = _userManager.UpdateUserPolicy(request.Id, request);
+            var task = UpdateUserPolicy(request);
             Task.WaitAll(task);
         }
+
+        private async Task UpdateUserPolicy(UpdateUserPolicy request)
+        {
+            var user = _userManager.GetUserById(request.Id);
+            
+            // If removing admin access
+            if (!request.IsAdministrator && user.Policy.IsAdministrator)
+            {
+                if (_userManager.Users.Count(i => i.Policy.IsAdministrator) == 1)
+                {
+                    throw new ArgumentException("There must be at least one user in the system with administrative access.");
+                }
+            }
+
+            // If disabling
+            if (request.IsDisabled && user.Policy.IsAdministrator)
+            {
+                throw new ArgumentException("Administrators cannot be disabled.");
+            }
+
+            // If disabling
+            if (request.IsDisabled && !user.Policy.IsDisabled)
+            {
+                if (_userManager.Users.Count(i => !i.Policy.IsDisabled) == 1)
+                {
+                    throw new ArgumentException("There must be at least one enabled user in the system.");
+                }
+
+                await _sessionMananger.RevokeUserTokens(user.Id.ToString("N")).ConfigureAwait(false);
+            }
+
+            await _userManager.UpdateUserPolicy(request.Id, request).ConfigureAwait(false);
+        }
     }
 }

+ 1 - 1
MediaBrowser.Controller/Channels/Channel.cs

@@ -14,7 +14,7 @@ namespace MediaBrowser.Controller.Channels
 
         public override bool IsVisible(User user)
         {
-            if (user.Configuration.BlockedChannels.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase))
+            if (user.Policy.BlockedChannels.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase))
             {
                 return false;
             }

+ 3 - 2
MediaBrowser.Controller/Channels/ChannelAudioItem.cs

@@ -6,6 +6,7 @@ using MediaBrowser.Model.Entities;
 using System.Collections.Generic;
 using System.Linq;
 using System.Threading;
+using MediaBrowser.Model.Users;
 
 namespace MediaBrowser.Controller.Channels
 {
@@ -25,8 +26,8 @@ namespace MediaBrowser.Controller.Channels
         public string OriginalImageUrl { get; set; }
 
         public List<ChannelMediaInfo> ChannelMediaSources { get; set; }
-        
-        protected override bool GetBlockUnratedValue(UserConfiguration config)
+
+        protected override bool GetBlockUnratedValue(UserPolicy config)
         {
             return config.BlockUnratedItems.Contains(UnratedItem.ChannelContent);
         }

+ 2 - 1
MediaBrowser.Controller/Channels/ChannelFolderItem.cs

@@ -5,6 +5,7 @@ using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Querying;
 using System.Threading;
 using System.Threading.Tasks;
+using MediaBrowser.Model.Users;
 
 namespace MediaBrowser.Controller.Channels
 {
@@ -20,7 +21,7 @@ namespace MediaBrowser.Controller.Channels
 
         public string OriginalImageUrl { get; set; }
 
-        protected override bool GetBlockUnratedValue(UserConfiguration config)
+        protected override bool GetBlockUnratedValue(UserPolicy config)
         {
             // Don't block. 
             return false;

+ 2 - 1
MediaBrowser.Controller/Channels/ChannelVideoItem.cs

@@ -8,6 +8,7 @@ using System.Collections.Generic;
 using System.Globalization;
 using System.Linq;
 using System.Threading;
+using MediaBrowser.Model.Users;
 
 namespace MediaBrowser.Controller.Channels
 {
@@ -51,7 +52,7 @@ namespace MediaBrowser.Controller.Channels
             return ExternalId;
         }
 
-        protected override bool GetBlockUnratedValue(UserConfiguration config)
+        protected override bool GetBlockUnratedValue(UserPolicy config)
         {
             return config.BlockUnratedItems.Contains(UnratedItem.ChannelContent);
         }

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

@@ -8,6 +8,7 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Runtime.Serialization;
+using MediaBrowser.Model.Users;
 
 namespace MediaBrowser.Controller.Entities.Audio
 {
@@ -173,7 +174,7 @@ namespace MediaBrowser.Controller.Entities.Audio
             return base.GetUserDataKey();
         }
 
-        protected override bool GetBlockUnratedValue(UserConfiguration config)
+        protected override bool GetBlockUnratedValue(UserPolicy config)
         {
             return config.BlockUnratedItems.Contains(UnratedItem.Music);
         }

+ 2 - 1
MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs

@@ -5,6 +5,7 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Runtime.Serialization;
+using MediaBrowser.Model.Users;
 
 namespace MediaBrowser.Controller.Entities.Audio
 {
@@ -154,7 +155,7 @@ namespace MediaBrowser.Controller.Entities.Audio
             return base.GetUserDataKey();
         }
 
-        protected override bool GetBlockUnratedValue(UserConfiguration config)
+        protected override bool GetBlockUnratedValue(UserPolicy config)
         {
             return config.BlockUnratedItems.Contains(UnratedItem.Music);
         }

+ 2 - 1
MediaBrowser.Controller/Entities/Audio/MusicArtist.cs

@@ -8,6 +8,7 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
+using MediaBrowser.Model.Users;
 
 namespace MediaBrowser.Controller.Entities.Audio
 {
@@ -114,7 +115,7 @@ namespace MediaBrowser.Controller.Entities.Audio
             return "Artist-" + item.Name;
         }
 
-        protected override bool GetBlockUnratedValue(UserConfiguration config)
+        protected override bool GetBlockUnratedValue(UserPolicy config)
         {
             return config.BlockUnratedItems.Contains(UnratedItem.Music);
         }

+ 6 - 5
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -21,6 +21,7 @@ using System.Linq;
 using System.Runtime.Serialization;
 using System.Threading;
 using System.Threading.Tasks;
+using MediaBrowser.Model.Users;
 
 namespace MediaBrowser.Controller.Entities
 {
@@ -595,7 +596,7 @@ namespace MediaBrowser.Controller.Entities
         /// <returns>PlayAccess.</returns>
         public PlayAccess GetPlayAccess(User user)
         {
-            if (!user.Configuration.EnableMediaPlayback)
+            if (!user.Policy.EnableMediaPlayback)
             {
                 return PlayAccess.None;
             }
@@ -987,7 +988,7 @@ namespace MediaBrowser.Controller.Entities
                 return false;
             }
 
-            var maxAllowedRating = user.Configuration.MaxParentalRating;
+            var maxAllowedRating = user.Policy.MaxParentalRating;
 
             if (maxAllowedRating == null)
             {
@@ -1003,7 +1004,7 @@ namespace MediaBrowser.Controller.Entities
 
             if (string.IsNullOrWhiteSpace(rating))
             {
-                return !GetBlockUnratedValue(user.Configuration);
+                return !GetBlockUnratedValue(user.Policy);
             }
 
             var value = LocalizationManager.GetRatingLevel(rating);
@@ -1023,7 +1024,7 @@ namespace MediaBrowser.Controller.Entities
 
             if (hasTags != null)
             {
-                if (user.Configuration.BlockedTags.Any(i => hasTags.Tags.Contains(i, StringComparer.OrdinalIgnoreCase)))
+                if (user.Policy.BlockedTags.Any(i => hasTags.Tags.Contains(i, StringComparer.OrdinalIgnoreCase)))
                 {
                     return false;
                 }
@@ -1037,7 +1038,7 @@ namespace MediaBrowser.Controller.Entities
         /// </summary>
         /// <param name="config">The configuration.</param>
         /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
-        protected virtual bool GetBlockUnratedValue(UserConfiguration config)
+        protected virtual bool GetBlockUnratedValue(UserPolicy config)
         {
             return config.BlockUnratedItems.Contains(UnratedItem.Other);
         }

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

@@ -2,6 +2,7 @@
 using MediaBrowser.Model.Configuration;
 using System.Collections.Generic;
 using System.Linq;
+using MediaBrowser.Model.Users;
 
 namespace MediaBrowser.Controller.Entities
 {
@@ -36,7 +37,7 @@ namespace MediaBrowser.Controller.Entities
             Tags = new List<string>();
         }
 
-        protected override bool GetBlockUnratedValue(UserConfiguration config)
+        protected override bool GetBlockUnratedValue(UserPolicy config)
         {
             return config.BlockUnratedItems.Contains(UnratedItem.Book);
         }

+ 2 - 2
MediaBrowser.Controller/Entities/Folder.cs

@@ -303,10 +303,10 @@ namespace MediaBrowser.Controller.Entities
         {
             if (this is ICollectionFolder)
             {
-                if (user.Configuration.BlockedMediaFolders.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase) ||
+                if (user.Policy.BlockedMediaFolders.Contains(Id.ToString("N"), StringComparer.OrdinalIgnoreCase) ||
 
                     // Backwards compatibility
-                    user.Configuration.BlockedMediaFolders.Contains(Name, StringComparer.OrdinalIgnoreCase))
+                    user.Policy.BlockedMediaFolders.Contains(Name, StringComparer.OrdinalIgnoreCase))
                 {
                     return false;
                 }

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

@@ -4,6 +4,7 @@ using MediaBrowser.Model.Entities;
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using MediaBrowser.Model.Users;
 
 namespace MediaBrowser.Controller.Entities
 {
@@ -108,7 +109,7 @@ namespace MediaBrowser.Controller.Entities
             return base.GetDeletePaths();
         }
 
-        protected override bool GetBlockUnratedValue(UserConfiguration config)
+        protected override bool GetBlockUnratedValue(UserPolicy config)
         {
             return config.BlockUnratedItems.Contains(UnratedItem.Game);
         }

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

@@ -2,6 +2,7 @@
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Configuration;
 using System;
+using MediaBrowser.Model.Users;
 
 namespace MediaBrowser.Controller.Entities
 {
@@ -43,7 +44,7 @@ namespace MediaBrowser.Controller.Entities
             return base.GetUserDataKey();
         }
 
-        protected override bool GetBlockUnratedValue(UserConfiguration config)
+        protected override bool GetBlockUnratedValue(UserPolicy config)
         {
             // Don't block. Determine by game
             return false;

+ 2 - 1
MediaBrowser.Controller/Entities/Movies/BoxSet.cs

@@ -9,6 +9,7 @@ using System.Linq;
 using System.Runtime.Serialization;
 using System.Threading;
 using System.Threading.Tasks;
+using MediaBrowser.Model.Users;
 
 namespace MediaBrowser.Controller.Entities.Movies
 {
@@ -67,7 +68,7 @@ namespace MediaBrowser.Controller.Entities.Movies
         /// <value>The display order.</value>
         public string DisplayOrder { get; set; }
 
-        protected override bool GetBlockUnratedValue(UserConfiguration config)
+        protected override bool GetBlockUnratedValue(UserPolicy config)
         {
             return config.BlockUnratedItems.Contains(UnratedItem.Movie);
         }

+ 2 - 1
MediaBrowser.Controller/Entities/Movies/Movie.cs

@@ -8,6 +8,7 @@ using System.Linq;
 using System.Runtime.Serialization;
 using System.Threading;
 using System.Threading.Tasks;
+using MediaBrowser.Model.Users;
 
 namespace MediaBrowser.Controller.Entities.Movies
 {
@@ -146,7 +147,7 @@ namespace MediaBrowser.Controller.Entities.Movies
             return itemsChanged;
         }
 
-        protected override bool GetBlockUnratedValue(UserConfiguration config)
+        protected override bool GetBlockUnratedValue(UserPolicy config)
         {
             return config.BlockUnratedItems.Contains(UnratedItem.Movie);
         }

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

@@ -6,6 +6,7 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Runtime.Serialization;
+using MediaBrowser.Model.Users;
 
 namespace MediaBrowser.Controller.Entities
 {
@@ -80,7 +81,7 @@ namespace MediaBrowser.Controller.Entities
             return this.GetProviderId(MetadataProviders.Tmdb) ?? this.GetProviderId(MetadataProviders.Imdb) ?? base.GetUserDataKey();
         }
 
-        protected override bool GetBlockUnratedValue(UserConfiguration config)
+        protected override bool GetBlockUnratedValue(UserPolicy config)
         {
             return config.BlockUnratedItems.Contains(UnratedItem.Music);
         }

+ 3 - 2
MediaBrowser.Controller/Entities/Photo.cs

@@ -3,6 +3,7 @@ using MediaBrowser.Model.Drawing;
 using System.Collections.Generic;
 using System.Linq;
 using System.Runtime.Serialization;
+using MediaBrowser.Model.Users;
 
 namespace MediaBrowser.Controller.Entities
 {
@@ -69,8 +70,8 @@ namespace MediaBrowser.Controller.Entities
         public double? Longitude { get; set; }
         public double? Altitude { get; set; }
         public int? IsoSpeedRating { get; set; }
-        
-        protected override bool GetBlockUnratedValue(UserConfiguration config)
+
+        protected override bool GetBlockUnratedValue(UserPolicy config)
         {
             return config.BlockUnratedItems.Contains(UnratedItem.Other);
         }

+ 3 - 2
MediaBrowser.Controller/Entities/PhotoAlbum.cs

@@ -1,6 +1,7 @@
 using MediaBrowser.Model.Configuration;
 using System.Linq;
 using System.Runtime.Serialization;
+using MediaBrowser.Model.Users;
 
 namespace MediaBrowser.Controller.Entities
 {
@@ -22,8 +23,8 @@ namespace MediaBrowser.Controller.Entities
                 return true;
             }
         }
-        
-        protected override bool GetBlockUnratedValue(UserConfiguration config)
+
+        protected override bool GetBlockUnratedValue(UserPolicy config)
         {
             return config.BlockUnratedItems.Contains(UnratedItem.Other);
         }

+ 2 - 1
MediaBrowser.Controller/Entities/TV/Episode.cs

@@ -5,6 +5,7 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Runtime.Serialization;
+using MediaBrowser.Model.Users;
 
 namespace MediaBrowser.Controller.Entities.TV
 {
@@ -274,7 +275,7 @@ namespace MediaBrowser.Controller.Entities.TV
             return new[] { Path };
         }
 
-        protected override bool GetBlockUnratedValue(UserConfiguration config)
+        protected override bool GetBlockUnratedValue(UserPolicy config)
         {
             return config.BlockUnratedItems.Contains(UnratedItem.Series);
         }

+ 2 - 1
MediaBrowser.Controller/Entities/TV/Season.cs

@@ -7,6 +7,7 @@ using MediaBrowser.Model.Querying;
 using System.Collections.Generic;
 using System.Linq;
 using System.Runtime.Serialization;
+using MediaBrowser.Model.Users;
 
 namespace MediaBrowser.Controller.Entities.TV
 {
@@ -249,7 +250,7 @@ namespace MediaBrowser.Controller.Entities.TV
             return GetEpisodes(user);
         }
 
-        protected override bool GetBlockUnratedValue(UserConfiguration config)
+        protected override bool GetBlockUnratedValue(UserPolicy config)
         {
             // Don't block. Let either the entire series rating or episode rating determine it
             return false;

+ 2 - 1
MediaBrowser.Controller/Entities/TV/Series.cs

@@ -7,6 +7,7 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Runtime.Serialization;
+using MediaBrowser.Model.Users;
 
 namespace MediaBrowser.Controller.Entities.TV
 {
@@ -246,7 +247,7 @@ namespace MediaBrowser.Controller.Entities.TV
             });
         }
 
-        protected override bool GetBlockUnratedValue(UserConfiguration config)
+        protected override bool GetBlockUnratedValue(UserPolicy config)
         {
             return config.BlockUnratedItems.Contains(UnratedItem.Series);
         }

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

@@ -6,6 +6,7 @@ using System.Collections.Generic;
 using System.Globalization;
 using System.Linq;
 using System.Runtime.Serialization;
+using MediaBrowser.Model.Users;
 
 namespace MediaBrowser.Controller.Entities
 {
@@ -98,7 +99,7 @@ namespace MediaBrowser.Controller.Entities
             return base.GetUserDataKey();
         }
 
-        protected override bool GetBlockUnratedValue(UserConfiguration config)
+        protected override bool GetBlockUnratedValue(UserPolicy config)
         {
             return config.BlockUnratedItems.Contains(UnratedItem.Trailer);
         }

+ 16 - 56
MediaBrowser.Controller/Entities/User.cs

@@ -1,5 +1,4 @@
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Connect;
@@ -107,37 +106,27 @@ namespace MediaBrowser.Controller.Entities
         /// <value>The last activity date.</value>
         public DateTime? LastActivityDate { get; set; }
 
-        /// <summary>
-        /// The _configuration
-        /// </summary>
-        private UserConfiguration _configuration;
-        /// <summary>
-        /// The _configuration initialized
-        /// </summary>
-        private bool _configurationInitialized;
-        /// <summary>
-        /// The _configuration sync lock
-        /// </summary>
-        private object _configurationSyncLock = new object();
-        /// <summary>
-        /// Gets the user's configuration
-        /// </summary>
-        /// <value>The configuration.</value>
+        private UserConfiguration _config;
+        private readonly object _configSyncLock = new object();
         [IgnoreDataMember]
         public UserConfiguration Configuration
         {
             get
             {
-                // Lazy load
-                LazyInitializer.EnsureInitialized(ref _configuration, ref _configurationInitialized, ref _configurationSyncLock, () => (UserConfiguration)ConfigurationHelper.GetXmlConfiguration(typeof(UserConfiguration), ConfigurationFilePath, XmlSerializer));
-                return _configuration;
-            }
-            private set
-            {
-                _configuration = value;
+                if (_config == null)
+                {
+                    lock (_configSyncLock)
+                    {
+                        if (_config == null)
+                        {
+                            _config = UserManager.GetUserConfiguration(this);
+                        }
+                    }
+                }
 
-                _configurationInitialized = value != null;
+                return _config;
             }
+            set { _config = value; }
         }
 
         private UserPolicy _policy;
@@ -256,35 +245,6 @@ namespace MediaBrowser.Controller.Entities
             return System.IO.Path.Combine(parentPath, Id.ToString("N"));
         }
 
-        /// <summary>
-        /// Gets the path to the user's configuration file
-        /// </summary>
-        /// <value>The configuration file path.</value>
-        [IgnoreDataMember]
-        public string ConfigurationFilePath
-        {
-            get
-            {
-                return System.IO.Path.Combine(ConfigurationDirectoryPath, "config.xml");
-            }
-        }
-
-        /// <summary>
-        /// Updates the configuration.
-        /// </summary>
-        /// <param name="config">The config.</param>
-        /// <exception cref="System.ArgumentNullException">config</exception>
-        public void UpdateConfiguration(UserConfiguration config)
-        {
-            if (config == null)
-            {
-                throw new ArgumentNullException("config");
-            }
-
-            Configuration = config;
-            UserManager.UpdateConfiguration(this, Configuration);
-        }
-
         public bool IsParentalScheduleAllowed()
         {
             return IsParentalScheduleAllowed(DateTime.UtcNow);
@@ -292,7 +252,7 @@ namespace MediaBrowser.Controller.Entities
 
         public bool IsParentalScheduleAllowed(DateTime date)
         {
-            var schedules = Configuration.AccessSchedules;
+            var schedules = Policy.AccessSchedules;
 
             if (schedules.Length == 0)
             {

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

@@ -63,7 +63,8 @@ namespace MediaBrowser.Controller.Entities
             {
                 CollectionType.Books,
                 CollectionType.HomeVideos,
-                CollectionType.Photos
+                CollectionType.Photos,
+                string.Empty
             };
 
             var collectionFolder = folder as ICollectionFolder;

+ 15 - 7
MediaBrowser.Controller/Library/IUserManager.cs

@@ -35,13 +35,6 @@ namespace MediaBrowser.Controller.Library
         event EventHandler<GenericEventArgs<User>> UserConfigurationUpdated;
         event EventHandler<GenericEventArgs<User>> UserPasswordChanged;
 
-        /// <summary>
-        /// Updates the configuration.
-        /// </summary>
-        /// <param name="user">The user.</param>
-        /// <param name="newConfiguration">The new configuration.</param>
-        void UpdateConfiguration(User user, UserConfiguration newConfiguration);
-        
         /// <summary>
         /// Gets a User by Id
         /// </summary>
@@ -172,6 +165,21 @@ namespace MediaBrowser.Controller.Library
         /// <returns>UserPolicy.</returns>
         UserPolicy GetUserPolicy(User user);
 
+        /// <summary>
+        /// Gets the user configuration.
+        /// </summary>
+        /// <param name="user">The user.</param>
+        /// <returns>UserConfiguration.</returns>
+        UserConfiguration GetUserConfiguration(User user);
+
+        /// <summary>
+        /// Updates the configuration.
+        /// </summary>
+        /// <param name="userId">The user identifier.</param>
+        /// <param name="newConfiguration">The new configuration.</param>
+        /// <returns>Task.</returns>
+        Task UpdateConfiguration(string userId, UserConfiguration newConfiguration);
+
         /// <summary>
         /// Updates the user policy.
         /// </summary>

+ 2 - 1
MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs

@@ -2,6 +2,7 @@
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using System.Linq;
+using MediaBrowser.Model.Users;
 
 namespace MediaBrowser.Controller.LiveTv
 {
@@ -78,7 +79,7 @@ namespace MediaBrowser.Controller.LiveTv
             }
         }
 
-        protected override bool GetBlockUnratedValue(UserConfiguration config)
+        protected override bool GetBlockUnratedValue(UserPolicy config)
         {
             return config.BlockUnratedItems.Contains(UnratedItem.LiveTvProgram);
         }

+ 2 - 1
MediaBrowser.Controller/LiveTv/LiveTvChannel.cs

@@ -6,6 +6,7 @@ using MediaBrowser.Model.LiveTv;
 using MediaBrowser.Model.MediaInfo;
 using System.Collections.Generic;
 using System.Linq;
+using MediaBrowser.Model.Users;
 
 namespace MediaBrowser.Controller.LiveTv
 {
@@ -33,7 +34,7 @@ namespace MediaBrowser.Controller.LiveTv
             }
         }
 
-        protected override bool GetBlockUnratedValue(UserConfiguration config)
+        protected override bool GetBlockUnratedValue(UserPolicy config)
         {
             return config.BlockUnratedItems.Contains(UnratedItem.LiveTvChannel);
         }

+ 2 - 1
MediaBrowser.Controller/LiveTv/LiveTvProgram.cs

@@ -6,6 +6,7 @@ using System;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Linq;
+using MediaBrowser.Model.Users;
 
 namespace MediaBrowser.Controller.LiveTv
 {
@@ -199,7 +200,7 @@ namespace MediaBrowser.Controller.LiveTv
             return ItemRepository.SaveItem(this, cancellationToken);
         }
 
-        protected override bool GetBlockUnratedValue(UserConfiguration config)
+        protected override bool GetBlockUnratedValue(UserPolicy config)
         {
             return config.BlockUnratedItems.Contains(UnratedItem.LiveTvProgram);
         }

+ 2 - 1
MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs

@@ -2,6 +2,7 @@
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using System.Linq;
+using MediaBrowser.Model.Users;
 
 namespace MediaBrowser.Controller.LiveTv
 {
@@ -78,7 +79,7 @@ namespace MediaBrowser.Controller.LiveTv
             }
         }
 
-        protected override bool GetBlockUnratedValue(UserConfiguration config)
+        protected override bool GetBlockUnratedValue(UserPolicy config)
         {
             return config.BlockUnratedItems.Contains(UnratedItem.LiveTvProgram);
         }

+ 2 - 1
MediaBrowser.Controller/LiveTv/RecordingGroup.cs

@@ -1,11 +1,12 @@
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Users;
 
 namespace MediaBrowser.Controller.LiveTv
 {
     public class RecordingGroup : Folder
     {
-        protected override bool GetBlockUnratedValue(UserConfiguration config)
+        protected override bool GetBlockUnratedValue(UserPolicy config)
         {
             // Don't block. 
             return false;

+ 3 - 0
MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj

@@ -1034,6 +1034,9 @@
     <Compile Include="..\MediaBrowser.Model\Sync\SyncDialogOptions.cs">
       <Link>Sync\SyncDialogOptions.cs</Link>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\Sync\SyncItem.cs">
+      <Link>Sync\SyncItem.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\Sync\SyncJob.cs">
       <Link>Sync\SyncJob.cs</Link>
     </Compile>

+ 3 - 0
MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj

@@ -993,6 +993,9 @@
     <Compile Include="..\MediaBrowser.Model\Sync\SyncDialogOptions.cs">
       <Link>Sync\SyncDialogOptions.cs</Link>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\Sync\SyncItem.cs">
+      <Link>Sync\SyncItem.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\Sync\SyncJob.cs">
       <Link>Sync\SyncJob.cs</Link>
     </Compile>

+ 7 - 6
MediaBrowser.Model/Configuration/UserConfiguration.cs

@@ -42,6 +42,10 @@ namespace MediaBrowser.Model.Configuration
         /// <value><c>true</c> if this instance is hidden; otherwise, <c>false</c>.</value>
         public bool IsHidden { get; set; }
 
+        /// <summary>
+        /// Gets or sets a value indicating whether this instance is disabled.
+        /// </summary>
+        /// <value><c>true</c> if this instance is disabled; otherwise, <c>false</c>.</value>
         public bool IsDisabled { get; set; }
 
         public bool DisplayMissingEpisodes { get; set; }
@@ -74,9 +78,6 @@ namespace MediaBrowser.Model.Configuration
 
         public string[] OrderedViews { get; set; }
 
-        public bool SyncConnectName { get; set; }
-        public bool SyncConnectImage { get; set; }
-
         public bool IncludeTrailersInSuggestions { get; set; }
 
         public bool EnableCinemaMode { get; set; }
@@ -87,7 +88,9 @@ namespace MediaBrowser.Model.Configuration
 
         public string[] LatestItemsExcludes { get; set; }
         public string[] BlockedTags { get; set; }
-   
+
+        public bool ValuesMigratedToPolicy { get; set; }
+
         /// <summary>
         /// Initializes a new instance of the <see cref="UserConfiguration" /> class.
         /// </summary>
@@ -110,8 +113,6 @@ namespace MediaBrowser.Model.Configuration
             ExcludeFoldersFromGrouping = new string[] { };
             DisplayCollectionsView = true;
 
-            SyncConnectName = true;
-            SyncConnectImage = true;
             IncludeTrailersInSuggestions = true;
             EnableCinemaMode = true;
             EnableUserPreferenceAccess = true;

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

@@ -366,6 +366,7 @@
     <Compile Include="Sync\SyncCategory.cs" />
     <Compile Include="Sync\SyncDialogOptions.cs" />
     <Compile Include="Sync\SyncHelper.cs" />
+    <Compile Include="Sync\SyncItem.cs" />
     <Compile Include="Sync\SyncJob.cs" />
     <Compile Include="Sync\SyncJobCreationResult.cs" />
     <Compile Include="Sync\SyncJobItem.cs" />

+ 62 - 1
MediaBrowser.Model/Users/UserPolicy.cs

@@ -1,8 +1,69 @@
-
+using MediaBrowser.Model.Configuration;
+
 namespace MediaBrowser.Model.Users
 {
     public class UserPolicy
     {
+        /// <summary>
+        /// Gets or sets a value indicating whether this instance is administrator.
+        /// </summary>
+        /// <value><c>true</c> if this instance is administrator; otherwise, <c>false</c>.</value>
+        public bool IsAdministrator { get; set; }
+
+        /// <summary>
+        /// Gets or sets a value indicating whether this instance is hidden.
+        /// </summary>
+        /// <value><c>true</c> if this instance is hidden; otherwise, <c>false</c>.</value>
+        public bool IsHidden { get; set; }
+
+        /// <summary>
+        /// Gets or sets a value indicating whether this instance is disabled.
+        /// </summary>
+        /// <value><c>true</c> if this instance is disabled; otherwise, <c>false</c>.</value>
+        public bool IsDisabled { get; set; }
+
+        /// <summary>
+        /// Gets or sets the max parental rating.
+        /// </summary>
+        /// <value>The max parental rating.</value>
+        public int? MaxParentalRating { get; set; }
+
+        public string[] BlockedTags { get; set; }
+        public bool EnableUserPreferenceAccess { get; set; }
+        public AccessSchedule[] AccessSchedules { get; set; }
+        public UnratedItem[] BlockUnratedItems { get; set; }
+        public string[] BlockedMediaFolders { get; set; }
+        public string[] BlockedChannels { get; set; }
+        public bool EnableRemoteControlOfOtherUsers { get; set; }
+        public bool EnableSharedDeviceControl { get; set; }
+
+        public bool EnableLiveTvManagement { get; set; }
+        public bool EnableLiveTvAccess { get; set; }
+
+        public bool EnableMediaPlayback { get; set; }
+        public bool EnableContentDeletion { get; set; }
+        
+        /// <summary>
+        /// Gets or sets a value indicating whether [enable synchronize].
+        /// </summary>
+        /// <value><c>true</c> if [enable synchronize]; otherwise, <c>false</c>.</value>
         public bool EnableSync { get; set; }
+
+        public UserPolicy()
+        {
+            EnableLiveTvManagement = true;
+            EnableMediaPlayback = true;
+            EnableLiveTvAccess = true;
+            EnableSharedDeviceControl = true;
+
+            BlockedMediaFolders = new string[] { };
+            BlockedTags = new string[] { };
+            BlockedChannels = new string[] { };
+            BlockUnratedItems = new UnratedItem[] { };
+
+            EnableUserPreferenceAccess = true;
+
+            AccessSchedules = new AccessSchedule[] { };
+        }
     }
 }

+ 1 - 1
MediaBrowser.Server.Implementations/Channels/ChannelDownloadScheduledTask.cs

@@ -95,7 +95,7 @@ namespace MediaBrowser.Server.Implementations.Channels
 
         public static string GetUserDistinctValue(User user)
         {
-            var channels = user.Configuration.BlockedChannels
+            var channels = user.Policy.BlockedChannels
                 .OrderBy(i => i)
                 .ToList();
 

+ 17 - 18
MediaBrowser.Server.Implementations/Connect/ConnectManager.cs

@@ -432,9 +432,7 @@ namespace MediaBrowser.Server.Implementations.Connect
 
             await user.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
 
-            user.Configuration.SyncConnectImage = false;
-            user.Configuration.SyncConnectName = false;
-            _userManager.UpdateConfiguration(user, user.Configuration);
+            await _userManager.UpdateConfiguration(user.Id.ToString("N"), user.Configuration);
 
             await RefreshAuthorizationsInternal(false, CancellationToken.None).ConfigureAwait(false);
 
@@ -800,23 +798,21 @@ namespace MediaBrowser.Server.Implementations.Connect
 
                             await _userManager.UpdateUser(user).ConfigureAwait(false);
 
-                            user.Configuration.SyncConnectImage = true;
-                            user.Configuration.SyncConnectName = true;
-                            user.Configuration.IsHidden = true;
-                            user.Configuration.EnableLiveTvManagement = false;
-                            user.Configuration.EnableContentDeletion = false;
-                            user.Configuration.EnableRemoteControlOfOtherUsers = false;
-                            user.Configuration.EnableSharedDeviceControl = false;
-                            user.Configuration.IsAdministrator = false;
+                            user.Policy.IsHidden = true;
+                            user.Policy.EnableLiveTvManagement = false;
+                            user.Policy.EnableContentDeletion = false;
+                            user.Policy.EnableRemoteControlOfOtherUsers = false;
+                            user.Policy.EnableSharedDeviceControl = false;
+                            user.Policy.IsAdministrator = false;
 
                             if (currentPendingEntry != null)
                             {
-                                user.Configuration.EnableLiveTvAccess = currentPendingEntry.EnableLiveTv;
-                                user.Configuration.BlockedMediaFolders = currentPendingEntry.ExcludedLibraries;
-                                user.Configuration.BlockedChannels = currentPendingEntry.ExcludedChannels;
+                                user.Policy.EnableLiveTvAccess = currentPendingEntry.EnableLiveTv;
+                                user.Policy.BlockedMediaFolders = currentPendingEntry.ExcludedLibraries;
+                                user.Policy.BlockedChannels = currentPendingEntry.ExcludedChannels;
                             }
 
-                            _userManager.UpdateConfiguration(user, user.Configuration);
+                            await _userManager.UpdateConfiguration(user.Id.ToString("N"), user.Configuration);
                         }
                     }
                     else if (string.Equals(connectEntry.AcceptStatus, "waiting", StringComparison.OrdinalIgnoreCase))
@@ -844,7 +840,7 @@ namespace MediaBrowser.Server.Implementations.Connect
         {
             var users = _userManager.Users
                 .Where(i => !string.IsNullOrEmpty(i.ConnectUserId) &&
-                    (i.Configuration.SyncConnectImage || i.Configuration.SyncConnectName))
+                    (i.ConnectLinkType.HasValue && i.ConnectLinkType.Value == UserLinkType.Guest))
                     .ToList();
 
             foreach (var user in users)
@@ -857,7 +853,10 @@ namespace MediaBrowser.Server.Implementations.Connect
                     continue;
                 }
 
-                if (user.Configuration.SyncConnectName)
+                var syncConnectName = true;
+                var syncConnectImage = true;
+
+                if (syncConnectName)
                 {
                     var changed = !string.Equals(authorization.UserName, user.Name, StringComparison.OrdinalIgnoreCase);
 
@@ -867,7 +866,7 @@ namespace MediaBrowser.Server.Implementations.Connect
                     }
                 }
 
-                if (user.Configuration.SyncConnectImage)
+                if (syncConnectImage)
                 {
                     var imageUrl = authorization.UserImageUrl;
 

+ 2 - 2
MediaBrowser.Server.Implementations/HttpServer/HttpListenerHost.cs

@@ -206,8 +206,8 @@ namespace MediaBrowser.Server.Implementations.HttpServer
             HostContext.Config.HandlerFactoryPath = ListenerRequest.GetHandlerPathIfAny(UrlPrefixes.First());
 
             _listener = _supportsNativeWebSocket && NativeWebSocket.IsSupported
-                ? _listener = new HttpListenerServer(_logger, OnRequestReceived)
-                //? _listener = new WebSocketSharpListener(_logger, OnRequestReceived)
+                //? _listener = new HttpListenerServer(_logger, OnRequestReceived)
+                ? _listener = new WebSocketSharpListener(_logger, OnRequestReceived)
                 : _listener = new WebSocketSharpListener(_logger, OnRequestReceived);
 
             _listener.WebSocketHandler = WebSocketHandler;

+ 4 - 4
MediaBrowser.Server.Implementations/HttpServer/Security/AuthService.cs

@@ -68,7 +68,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
 
             if (user != null)
             {
-                if (user.Configuration.IsDisabled)
+                if (user.Policy.IsDisabled)
                 {
                     throw new SecurityException("User account has been disabled.")
                     {
@@ -76,7 +76,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
                     };
                 }
 
-                if (!user.Configuration.IsAdministrator &&
+                if (!user.Policy.IsAdministrator &&
                     !authAttribtues.EscapeParentalControl &&
                     !user.IsParentalScheduleAllowed())
                 {
@@ -135,7 +135,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
         {
             if (roles.Contains("admin", StringComparer.OrdinalIgnoreCase))
             {
-                if (user == null || !user.Configuration.IsAdministrator)
+                if (user == null || !user.Policy.IsAdministrator)
                 {
                     throw new SecurityException("User does not have admin access.")
                     {
@@ -145,7 +145,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer.Security
             }
             if (roles.Contains("delete", StringComparer.OrdinalIgnoreCase))
             {
-                if (user == null || !user.Configuration.EnableContentDeletion)
+                if (user == null || !user.Policy.EnableContentDeletion)
                 {
                     throw new SecurityException("User does not have delete access.")
                     {

+ 7 - 11
MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs

@@ -37,7 +37,11 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio
         /// <value>The priority.</value>
         public override ResolverPriority Priority
         {
-            get { return ResolverPriority.Third; } // we need to be ahead of the generic folder resolver but behind the movie one
+            get
+            {
+                // Behind special folder resolver
+                return ResolverPriority.Second;
+            } 
         }
 
         /// <summary>
@@ -49,21 +53,13 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio
         {
             if (!args.IsDirectory) return null;
 
-            //Avoid mis-identifying top folders
-            if (args.Parent == null) return null;
+            // Avoid mis-identifying top folders
             if (args.Parent.IsRoot) return null;
             if (args.HasParent<MusicAlbum>()) return null;
 
-            // Optimization
-            if (args.HasParent<BoxSet>() || args.HasParent<Series>() || args.HasParent<Season>())
-            {
-                return null;
-            }
-
             var collectionType = args.GetCollectionType();
 
-            var isMusicMediaFolder = string.Equals(collectionType, CollectionType.Music,
-                StringComparison.OrdinalIgnoreCase);
+            var isMusicMediaFolder = string.Equals(collectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase);
 
             // If there's a collection type and it's not music, don't allow it.
             if (!isMusicMediaFolder)

+ 7 - 11
MediaBrowser.Server.Implementations/Library/Resolvers/Audio/MusicArtistResolver.cs

@@ -34,7 +34,11 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio
         /// <value>The priority.</value>
         public override ResolverPriority Priority
         {
-            get { return ResolverPriority.Third; } // we need to be ahead of the generic folder resolver but behind the movie one
+            get
+            {
+                // Behind special folder resolver
+                return ResolverPriority.Second;
+            } 
         }
 
         /// <summary>
@@ -46,8 +50,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio
         {
             if (!args.IsDirectory) return null;
 
-            //Avoid mis-identifying top folders
-            if (args.Parent == null) return null;
+            // Avoid mis-identifying top folders
             if (args.Parent.IsRoot) return null;
 
             // Don't allow nested artists
@@ -56,16 +59,9 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Audio
                 return null;
             }
 
-            // Optimization
-            if (args.HasParent<BoxSet>() || args.HasParent<Series>() || args.HasParent<Season>())
-            {
-                return null;
-            }
-
             var collectionType = args.GetCollectionType();
 
-            var isMusicMediaFolder = string.Equals(collectionType, CollectionType.Music,
-                StringComparison.OrdinalIgnoreCase);
+            var isMusicMediaFolder = string.Equals(collectionType, CollectionType.Music, StringComparison.OrdinalIgnoreCase);
 
             // If there's a collection type and it's not music, it can't be a series
             if (!isMusicMediaFolder)

+ 1 - 49
MediaBrowser.Server.Implementations/Library/Resolvers/FolderResolver.cs

@@ -1,10 +1,6 @@
-using MediaBrowser.Common.IO;
-using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Resolvers;
-using System;
-using System.IO;
-using System.Linq;
 
 namespace MediaBrowser.Server.Implementations.Library.Resolvers
 {
@@ -13,13 +9,6 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers
     /// </summary>
     public class FolderResolver : FolderResolver<Folder>
     {
-        private readonly IFileSystem _fileSystem;
-
-        public FolderResolver(IFileSystem fileSystem)
-        {
-            _fileSystem = fileSystem;
-        }
-
         /// <summary>
         /// Gets the priority.
         /// </summary>
@@ -38,48 +27,11 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers
         {
             if (args.IsDirectory)
             {
-                if (args.IsPhysicalRoot)
-                {
-                    return new AggregateFolder();
-                }
-                if (args.IsRoot)
-                {
-                    return new UserRootFolder();  //if we got here and still a root - must be user root
-                }
-                if (args.IsVf)
-                {
-                    return new CollectionFolder
-                    {
-                        CollectionType = GetCollectionType(args)
-                    };
-                }
-
                 return new Folder();
             }
 
             return null;
         }
-
-        private string GetCollectionType(ItemResolveArgs args)
-        {
-            return args.FileSystemChildren
-                .Where(i =>
-                {
-
-                    try
-                    {
-                        return (i.Attributes & FileAttributes.Directory) != FileAttributes.Directory &&
-                               string.Equals(".collection", i.Extension, StringComparison.OrdinalIgnoreCase);
-                    }
-                    catch (IOException)
-                    {
-                        return false;
-                    }
-
-                })
-                .Select(i => _fileSystem.GetFileNameWithoutExtension(i))
-                .FirstOrDefault();
-        }
     }
 
     /// <summary>

+ 0 - 6
MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs

@@ -183,12 +183,6 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
                         return FindMovie<Video>(args.Path, args.Parent, args.FileSystemChildren.ToList(), args.DirectoryService, collectionType);
                     }
 
-                    // Since the looping is expensive, this is an optimization to help us avoid it
-                    if (args.ContainsMetaFileByName("series.xml"))
-                    {
-                        return null;
-                    }
-
                     return FindMovie<Movie>(args.Path, args.Parent, args.FileSystemChildren.ToList(), args.DirectoryService, collectionType);
                 }
 

+ 11 - 1
MediaBrowser.Server.Implementations/Library/Resolvers/PhotoAlbumResolver.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Resolvers;
 using MediaBrowser.Model.Entities;
 using System;
 using System.IO;
@@ -17,7 +18,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers
         protected override PhotoAlbum Resolve(ItemResolveArgs args)
         {
             // Must be an image file within a photo collection
-            if (!args.IsRoot && args.IsDirectory && string.Equals(args.GetCollectionType(), CollectionType.Photos, StringComparison.OrdinalIgnoreCase))
+            if (args.IsDirectory && string.Equals(args.GetCollectionType(), CollectionType.Photos, StringComparison.OrdinalIgnoreCase))
             {
                 if (HasPhotos(args))
                 {
@@ -35,5 +36,14 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers
         {
             return args.FileSystemChildren.Any(i => ((i.Attributes & FileAttributes.Directory) != FileAttributes.Directory) && PhotoResolver.IsImageFile(i.FullName));
         }
+
+        public override ResolverPriority Priority
+        {
+            get
+            {
+                // Behind special folder resolver
+                return ResolverPriority.Second;
+            }
+        }
     }
 }

+ 79 - 0
MediaBrowser.Server.Implementations/Library/Resolvers/SpecialFolderResolver.cs

@@ -0,0 +1,79 @@
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Resolvers;
+using System;
+using System.IO;
+using System.Linq;
+
+namespace MediaBrowser.Server.Implementations.Library.Resolvers
+{
+    class SpecialFolderResolver : FolderResolver<Folder>
+    {
+        private readonly IFileSystem _fileSystem;
+
+        public SpecialFolderResolver(IFileSystem fileSystem)
+        {
+            _fileSystem = fileSystem;
+        }
+
+        /// <summary>
+        /// Gets the priority.
+        /// </summary>
+        /// <value>The priority.</value>
+        public override ResolverPriority Priority
+        {
+            get { return ResolverPriority.First; }
+        }
+
+        /// <summary>
+        /// Resolves the specified args.
+        /// </summary>
+        /// <param name="args">The args.</param>
+        /// <returns>Folder.</returns>
+        protected override Folder Resolve(ItemResolveArgs args)
+        {
+            if (args.IsDirectory)
+            {
+                if (args.IsPhysicalRoot)
+                {
+                    return new AggregateFolder();
+                }
+                if (args.IsRoot)
+                {
+                    return new UserRootFolder();  //if we got here and still a root - must be user root
+                }
+                if (args.IsVf)
+                {
+                    return new CollectionFolder
+                    {
+                        CollectionType = GetCollectionType(args)
+                    };
+                }
+            }
+
+            return null;
+        }
+
+        private string GetCollectionType(ItemResolveArgs args)
+        {
+            return args.FileSystemChildren
+                .Where(i =>
+                {
+
+                    try
+                    {
+                        return (i.Attributes & FileAttributes.Directory) != FileAttributes.Directory &&
+                               string.Equals(".collection", i.Extension, StringComparison.OrdinalIgnoreCase);
+                    }
+                    catch (IOException)
+                    {
+                        return false;
+                    }
+
+                })
+                .Select(i => _fileSystem.GetFileNameWithoutExtension(i))
+                .FirstOrDefault();
+        }
+    }
+}

+ 7 - 9
MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs

@@ -53,13 +53,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
             if (args.IsDirectory)
             {
                 // Avoid expensive tests against VF's and all their children by not allowing this
-                if (args.Parent == null || args.Parent.IsRoot)
-                {
-                    return null;
-                }
-                
-                // Optimization to avoid running these tests against Seasons
-                if (args.HasParent<Series>() || args.HasParent<Season>() || args.HasParent<MusicArtist>() || args.HasParent<MusicAlbum>())
+                if (args.Parent.IsRoot)
                 {
                     return null;
                 }
@@ -69,8 +63,12 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
                 var isTvShowsFolder = string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase);
 
                 // If there's a collection type and it's not tv, it can't be a series
-                if (!string.IsNullOrEmpty(collectionType) &&
-                    !isTvShowsFolder)
+                if (!isTvShowsFolder)
+                {
+                    return null;
+                }
+
+                if (args.HasParent<Series>() || args.HasParent<Season>())
                 {
                     return null;
                 }

+ 131 - 32
MediaBrowser.Server.Implementations/Library/UserManager.cs

@@ -1,5 +1,4 @@
-using System.Collections.Concurrent;
-using MediaBrowser.Common.Events;
+using MediaBrowser.Common.Events;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller;
@@ -63,6 +62,7 @@ namespace MediaBrowser.Server.Implementations.Library
         public event EventHandler<GenericEventArgs<User>> UserPasswordChanged;
 
         private readonly IXmlSerializer _xmlSerializer;
+        private readonly IJsonSerializer _jsonSerializer;
 
         private readonly INetworkManager _networkManager;
 
@@ -71,13 +71,7 @@ namespace MediaBrowser.Server.Implementations.Library
         private readonly Func<IConnectManager> _connectFactory;
         private readonly IServerApplicationHost _appHost;
 
-        /// <summary>
-        /// Initializes a new instance of the <see cref="UserManager" /> class.
-        /// </summary>
-        /// <param name="logger">The logger.</param>
-        /// <param name="configurationManager">The configuration manager.</param>
-        /// <param name="userRepository">The user repository.</param>
-        public UserManager(ILogger logger, IServerConfigurationManager configurationManager, IUserRepository userRepository, IXmlSerializer xmlSerializer, INetworkManager networkManager, Func<IImageProcessor> imageProcessorFactory, Func<IDtoService> dtoServiceFactory, Func<IConnectManager> connectFactory, IServerApplicationHost appHost)
+        public UserManager(ILogger logger, IServerConfigurationManager configurationManager, IUserRepository userRepository, IXmlSerializer xmlSerializer, INetworkManager networkManager, Func<IImageProcessor> imageProcessorFactory, Func<IDtoService> dtoServiceFactory, Func<IConnectManager> connectFactory, IServerApplicationHost appHost, IJsonSerializer jsonSerializer)
         {
             _logger = logger;
             UserRepository = userRepository;
@@ -87,6 +81,7 @@ namespace MediaBrowser.Server.Implementations.Library
             _dtoServiceFactory = dtoServiceFactory;
             _connectFactory = connectFactory;
             _appHost = appHost;
+            _jsonSerializer = jsonSerializer;
             ConfigurationManager = configurationManager;
             Users = new List<User>();
 
@@ -164,6 +159,11 @@ namespace MediaBrowser.Server.Implementations.Library
         public async Task Initialize()
         {
             Users = await LoadUsers().ConfigureAwait(false);
+
+            foreach (var user in Users.ToList())
+            {
+                await DoPolicyMigration(user).ConfigureAwait(false);
+            }
         }
 
         public Task<bool> AuthenticateUser(string username, string passwordSha1, string remoteEndPoint)
@@ -185,7 +185,7 @@ namespace MediaBrowser.Server.Implementations.Library
                 throw new SecurityException("Invalid username or password entered.");
             }
 
-            if (user.Configuration.IsDisabled)
+            if (user.Policy.IsDisabled)
             {
                 throw new SecurityException(string.Format("The {0} account is currently disabled. Please consult with your administrator.", user.Name));
             }
@@ -283,14 +283,42 @@ namespace MediaBrowser.Server.Implementations.Library
 
                 users.Add(user);
 
-                user.Configuration.IsAdministrator = true;
-                user.Configuration.EnableRemoteControlOfOtherUsers = true;
-                UpdateConfiguration(user, user.Configuration);
+                user.Policy.IsAdministrator = true;
+                user.Policy.EnableRemoteControlOfOtherUsers = true;
+                await UpdateUserPolicy(user, user.Policy, false).ConfigureAwait(false);
             }
 
             return users;
         }
 
+        private async Task DoPolicyMigration(User user)
+        {
+            if (!user.Configuration.ValuesMigratedToPolicy)
+            {
+                user.Policy.AccessSchedules = user.Configuration.AccessSchedules;
+                user.Policy.BlockedChannels = user.Configuration.BlockedChannels;
+                user.Policy.BlockedMediaFolders = user.Configuration.BlockedMediaFolders;
+                user.Policy.BlockedTags = user.Configuration.BlockedTags;
+                user.Policy.BlockUnratedItems = user.Configuration.BlockUnratedItems;
+                user.Policy.EnableContentDeletion = user.Configuration.EnableContentDeletion;
+                user.Policy.EnableLiveTvAccess = user.Configuration.EnableLiveTvAccess;
+                user.Policy.EnableLiveTvManagement = user.Configuration.EnableLiveTvManagement;
+                user.Policy.EnableMediaPlayback = user.Configuration.EnableMediaPlayback;
+                user.Policy.EnableRemoteControlOfOtherUsers = user.Configuration.EnableRemoteControlOfOtherUsers;
+                user.Policy.EnableSharedDeviceControl = user.Configuration.EnableSharedDeviceControl;
+                user.Policy.EnableUserPreferenceAccess = user.Configuration.EnableUserPreferenceAccess;
+                user.Policy.IsAdministrator = user.Configuration.IsAdministrator;
+                user.Policy.IsDisabled = user.Configuration.IsDisabled;
+                user.Policy.IsHidden = user.Configuration.IsHidden;
+                user.Policy.MaxParentalRating = user.Configuration.MaxParentalRating;
+
+                await UpdateUserPolicy(user.Id.ToString("N"), user.Policy);
+
+                user.Configuration.ValuesMigratedToPolicy = true;
+                await UpdateConfiguration(user, user.Configuration, true).ConfigureAwait(false);
+            }
+        }
+
         public UserDto GetUserDto(User user, string remoteEndPoint = null)
         {
             if (user == null)
@@ -509,7 +537,7 @@ namespace MediaBrowser.Server.Implementations.Library
                 throw new ArgumentException(string.Format("The user '{0}' cannot be deleted because there must be at least one user in the system.", user.Name));
             }
 
-            if (user.Configuration.IsAdministrator && allUsers.Count(i => i.Configuration.IsAdministrator) == 1)
+            if (user.Policy.IsAdministrator && allUsers.Count(i => i.Policy.IsAdministrator) == 1)
             {
                 throw new ArgumentException(string.Format("The user '{0}' cannot be deleted because there must be at least one admin user in the system.", user.Name));
             }
@@ -518,17 +546,17 @@ namespace MediaBrowser.Server.Implementations.Library
 
             try
             {
-                await UserRepository.DeleteUser(user, CancellationToken.None).ConfigureAwait(false);
+                var configPath = GetConfigurationFilePath(user);
 
-                var path = user.ConfigurationFilePath;
+                await UserRepository.DeleteUser(user, CancellationToken.None).ConfigureAwait(false);
 
                 try
                 {
-                    File.Delete(path);
+                    File.Delete(configPath);
                 }
                 catch (IOException ex)
                 {
-                    _logger.ErrorException("Error deleting file {0}", ex, path);
+                    _logger.ErrorException("Error deleting file {0}", ex, configPath);
                 }
 
                 DeleteUserPolicy(user);
@@ -613,15 +641,6 @@ namespace MediaBrowser.Server.Implementations.Library
             };
         }
 
-        public void UpdateConfiguration(User user, UserConfiguration newConfiguration)
-        {
-            var xmlPath = user.ConfigurationFilePath;
-            Directory.CreateDirectory(Path.GetDirectoryName(xmlPath));
-            _xmlSerializer.SerializeToFile(newConfiguration, xmlPath);
-
-            EventHelper.FireEventIfNotNull(UserConfigurationUpdated, this, new GenericEventArgs<User> { Argument = user }, _logger);
-        }
-
         private string PasswordResetFile
         {
             get { return Path.Combine(ConfigurationManager.ApplicationPaths.ProgramDataPath, "passwordreset.txt"); }
@@ -689,7 +708,7 @@ namespace MediaBrowser.Server.Implementations.Library
             string pinFile = null;
             DateTime? expirationDate = null;
 
-            if (user != null && !user.Configuration.IsAdministrator)
+            if (user != null && !user.Policy.IsAdministrator)
             {
                 action = ForgotPasswordAction.ContactAdmin;
             }
@@ -781,7 +800,7 @@ namespace MediaBrowser.Server.Implementations.Library
             {
                 lock (_policySyncLock)
                 {
-                    return (UserPolicy) _xmlSerializer.DeserializeFromFile(typeof (UserPolicy), path);
+                    return (UserPolicy)_jsonSerializer.DeserializeFromFile(typeof(UserPolicy), path);
                 }
             }
             catch (FileNotFoundException)
@@ -805,16 +824,38 @@ namespace MediaBrowser.Server.Implementations.Library
         }
 
         private readonly object _policySyncLock = new object();
-        public async Task UpdateUserPolicy(string userId, UserPolicy userPolicy)
+        public Task UpdateUserPolicy(string userId, UserPolicy userPolicy)
         {
             var user = GetUserById(userId);
+            return UpdateUserPolicy(user, userPolicy, true);
+        }
+
+        private async Task UpdateUserPolicy(User user, UserPolicy userPolicy, bool fireEvent)
+        {
+            var updateConfig = user.Policy.IsAdministrator != userPolicy.IsAdministrator ||
+                user.Policy.EnableLiveTvManagement != userPolicy.EnableLiveTvManagement ||
+                user.Policy.EnableLiveTvAccess != userPolicy.EnableLiveTvAccess ||
+                user.Policy.EnableMediaPlayback != userPolicy.EnableMediaPlayback ||
+                user.Policy.EnableContentDeletion != userPolicy.EnableContentDeletion;
+            
             var path = GetPolifyFilePath(user);
 
             lock (_policySyncLock)
             {
-                _xmlSerializer.SerializeToFile(userPolicy, path);
+                _jsonSerializer.SerializeToFile(userPolicy, path);
                 user.Policy = userPolicy;
             }
+
+            if (updateConfig)
+            {
+                user.Configuration.IsAdministrator = user.Policy.IsAdministrator;
+                user.Configuration.EnableLiveTvManagement = user.Policy.EnableLiveTvManagement;
+                user.Configuration.EnableLiveTvAccess = user.Policy.EnableLiveTvAccess;
+                user.Configuration.EnableMediaPlayback = user.Policy.EnableMediaPlayback;
+                user.Configuration.EnableContentDeletion = user.Policy.EnableContentDeletion;
+
+                await UpdateConfiguration(user, user.Configuration, true).ConfigureAwait(false);
+            }
         }
 
         private void DeleteUserPolicy(User user)
@@ -840,7 +881,65 @@ namespace MediaBrowser.Server.Implementations.Library
 
         private string GetPolifyFilePath(User user)
         {
-            return Path.Combine(user.ConfigurationDirectoryPath, "policy.xml");
+            return Path.Combine(user.ConfigurationDirectoryPath, "policy.json");
+        }
+
+        private string GetConfigurationFilePath(User user)
+        {
+            return Path.Combine(user.ConfigurationDirectoryPath, "config.xml");
+        }
+
+        public UserConfiguration GetUserConfiguration(User user)
+        {
+            var path = GetConfigurationFilePath(user);
+
+            try
+            {
+                lock (_configSyncLock)
+                {
+                    return (UserConfiguration)_xmlSerializer.DeserializeFromFile(typeof(UserConfiguration), path);
+                }
+            }
+            catch (FileNotFoundException)
+            {
+                return new UserConfiguration();
+            }
+            catch (Exception ex)
+            {
+                _logger.ErrorException("Error reading policy file: {0}", ex, path);
+
+                return new UserConfiguration();
+            }
+        }
+
+        private readonly object _configSyncLock = new object();
+        public Task UpdateConfiguration(string userId, UserConfiguration config)
+        {
+            var user = GetUserById(userId);
+            return UpdateConfiguration(user, config, true);
+        }
+
+        private async Task UpdateConfiguration(User user, UserConfiguration config, bool fireEvent)
+        {
+            var path = GetConfigurationFilePath(user);
+
+            // The xml serializer will output differently if the type is not exact
+            if (config.GetType() != typeof (UserConfiguration))
+            {
+                var json = _jsonSerializer.SerializeToString(config);
+                config = _jsonSerializer.DeserializeFromString<UserConfiguration>(json);
+            }
+
+            lock (_configSyncLock)
+            {
+                _xmlSerializer.SerializeToFile(config, path);
+                user.Configuration = config;
+            }
+
+            if (fireEvent)
+            {
+                EventHelper.FireEventIfNotNull(UserConfigurationUpdated, this, new GenericEventArgs<User> { Argument = user }, _logger);
+            }
         }
     }
 }

+ 2 - 2
MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs

@@ -1846,7 +1846,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
         private bool IsLiveTvEnabled(User user)
         {
-            return user.Configuration.EnableLiveTvAccess && ActiveService != null;
+            return user.Policy.EnableLiveTvAccess && ActiveService != null;
         }
 
         public IEnumerable<User> GetEnabledUsers()
@@ -1854,7 +1854,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
             var service = ActiveService;
 
             return _userManager.Users
-                .Where(i => i.Configuration.EnableLiveTvAccess && service != null);
+                .Where(i => i.Policy.EnableLiveTvAccess && service != null);
         }
 
         /// <summary>

+ 6 - 3
MediaBrowser.Server.Implementations/Localization/JavaScript/javascript.json

@@ -418,7 +418,7 @@
     "HeaderMediaLocations": "Media Locations",
     "LabelFolderTypeValue": "Folder type: {0}",
     "LabelPathSubstitutionHelp": "Optional: Path substitution can map server paths to network shares that clients can access for direct playback.",
-    "FolderTypeMixed": "Mixed movies & tv",
+    "FolderTypeMixed": "Mixed videos",
     "FolderTypeMovies": "Movies",
     "FolderTypeMusic": "Music",
     "FolderTypeAdultVideos": "Adult videos",
@@ -427,7 +427,7 @@
     "FolderTypeHomeVideos": "Home videos",
     "FolderTypeGames": "Games",
     "FolderTypeBooks": "Books",
-    "FolderTypeTvShows": "TV shows",
+    "FolderTypeTvShows": "TV",
     "TabMovies": "Movies",
     "TabSeries": "Series",
     "TabEpisodes": "Episodes",
@@ -654,5 +654,8 @@
     "OptionSyncUnwatchedVideosOnly": "Sync unwatched videos only",
     "OptionSyncUnwatchedVideosOnlyHelp": "Only unwatched videos will be synced, and videos will be removed from the device as they are watched.",
     "LabelItemLimit": "Item limit:",
-    "LabelItemLimitHelp": "Optional. Set a limit to the number of items that will be synced."
+    "LabelItemLimitHelp": "Optional. Set a limit to the number of items that will be synced.",
+    "MessageBookPluginRequired": "Requires installation of the Bookshelf plugin",
+    "MessageGamePluginRequired": "Requires installation of the GameBrowser plugin",
+    "MessageMixedContentHelp":  "Content will be displayed with as a plain folder structure"
 }

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

@@ -40,7 +40,6 @@
     "HeaderSetupLibrary": "Setup your media library",
     "ButtonAddMediaFolder": "Add media folder",
     "LabelFolderType": "Folder type:",
-    "MediaFolderHelpPluginRequired": "* Requires the use of a plugin, e.g. GameBrowser or MB Bookshelf.",
     "ReferToMediaLibraryWiki": "Refer to the media library wiki.",
     "LabelCountry": "Country:",
     "LabelLanguage": "Language:",

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

@@ -183,6 +183,7 @@
     <Compile Include="Library\LocalTrailerPostScanTask.cs" />
     <Compile Include="Library\MusicManager.cs" />
     <Compile Include="Library\PathExtensions.cs" />
+    <Compile Include="Library\Resolvers\SpecialFolderResolver.cs" />
     <Compile Include="Library\Resolvers\BaseVideoResolver.cs" />
     <Compile Include="Library\Resolvers\PhotoAlbumResolver.cs" />
     <Compile Include="Library\Resolvers\PhotoResolver.cs" />

+ 1 - 1
MediaBrowser.Server.Implementations/Notifications/NotificationManager.cs

@@ -78,7 +78,7 @@ namespace MediaBrowser.Server.Implementations.Notifications
                 switch (request.SendToUserMode.Value)
                 {
                     case SendToUserType.Admins:
-                        return _userManager.Users.Where(i => i.Configuration.IsAdministrator)
+                        return _userManager.Users.Where(i => i.Policy.IsAdministrator)
                                 .Select(i => i.Id.ToString("N"));
                     case SendToUserType.All:
                         return _userManager.Users.Select(i => i.Id.ToString("N"));

+ 2 - 1
MediaBrowser.Server.Implementations/Sync/SyncManager.cs

@@ -4,6 +4,7 @@ using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.Sync;
 using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Entities;
@@ -242,7 +243,7 @@ namespace MediaBrowser.Server.Implementations.Sync
                 return true;
             }
 
-            return item.LocationType == LocationType.FileSystem || item is Season;
+            return item.LocationType == LocationType.FileSystem || item is Season || item is ILiveTvRecording;
         }
 
         private string GetDefaultName(BaseItem item)

+ 10 - 3
MediaBrowser.Server.Implementations/Sync/SyncRepository.cs

@@ -24,6 +24,7 @@ namespace MediaBrowser.Server.Implementations.Sync
         private readonly CultureInfo _usCulture = new CultureInfo("en-US");
 
         private IDbCommand _deleteJobCommand;
+        private IDbCommand _deleteJobItemsCommand;
         private IDbCommand _saveJobCommand;
         private IDbCommand _saveJobItemCommand;
 
@@ -61,9 +62,13 @@ namespace MediaBrowser.Server.Implementations.Sync
         private void PrepareStatements()
         {
             _deleteJobCommand = _connection.CreateCommand();
-            _deleteJobCommand.CommandText = "delete from SyncJobs where Id=@Id; delete from SyncJobItems where JobId=@Id";
+            _deleteJobCommand.CommandText = "delete from SyncJobs where Id=@Id";
             _deleteJobCommand.Parameters.Add(_deleteJobCommand, "@Id");
 
+            _deleteJobItemsCommand = _connection.CreateCommand();
+            _deleteJobItemsCommand.CommandText = "delete from SyncJobItems where JobId=@JobId";
+            _deleteJobItemsCommand.Parameters.Add(_deleteJobItemsCommand, "@JobId");
+            
             _saveJobCommand = _connection.CreateCommand();
             _saveJobCommand.CommandText = "replace into SyncJobs (Id, TargetId, Name, Quality, Status, Progress, UserId, ItemIds, Category, ParentId, UnwatchedOnly, ItemLimit, SyncNewContent, DateCreated, DateLastModified, ItemCount) values (@Id, @TargetId, @Name, @Quality, @Status, @Progress, @UserId, @ItemIds, @Category, @ParentId, @UnwatchedOnly, @ItemLimit, @SyncNewContent, @DateCreated, @DateLastModified, @ItemCount)";
 
@@ -289,11 +294,13 @@ namespace MediaBrowser.Server.Implementations.Sync
                 var index = 0;
 
                 _deleteJobCommand.GetParameter(index++).Value = new Guid(id);
-
                 _deleteJobCommand.Transaction = transaction;
-
                 _deleteJobCommand.ExecuteNonQuery();
 
+                _deleteJobItemsCommand.GetParameter(index++).Value = new Guid(id);
+                _deleteJobItemsCommand.Transaction = transaction;
+                _deleteJobItemsCommand.ExecuteNonQuery();
+                
                 transaction.Commit();
             }
             catch (OperationCanceledException)

+ 1 - 1
MediaBrowser.Server.Startup.Common/ApplicationHost.cs

@@ -419,7 +419,7 @@ namespace MediaBrowser.Server.Startup.Common
             SyncRepository = await GetSyncRepository().ConfigureAwait(false);
             RegisterSingleInstance(SyncRepository);
 
-            UserManager = new UserManager(LogManager.GetLogger("UserManager"), ServerConfigurationManager, UserRepository, XmlSerializer, NetworkManager, () => ImageProcessor, () => DtoService, () => ConnectManager, this);
+            UserManager = new UserManager(LogManager.GetLogger("UserManager"), ServerConfigurationManager, UserRepository, XmlSerializer, NetworkManager, () => ImageProcessor, () => DtoService, () => ConnectManager, this, JsonSerializer);
             RegisterSingleInstance(UserManager);
 
             LibraryManager = new LibraryManager(Logger, TaskManager, UserManager, ServerConfigurationManager, UserDataManager, () => LibraryMonitor, FileSystemManager, () => ProviderManager);

+ 0 - 9
MediaBrowser.WebDashboard/MediaBrowser.WebDashboard.csproj

@@ -210,9 +210,6 @@
     <Content Include="dashboard-ui\css\images\favicon.ico">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
-    <Content Include="dashboard-ui\css\images\headersearch.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
     <Content Include="dashboard-ui\css\images\icons\ellipsis-v.png">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
@@ -228,9 +225,6 @@
     <Content Include="dashboard-ui\css\images\items\folders\report.png">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
-    <Content Include="dashboard-ui\css\images\items\folders\settings.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
     <Content Include="dashboard-ui\css\images\icons\audiocd.png">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
@@ -594,9 +588,6 @@
     <Content Include="dashboard-ui\css\images\rotten.png">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>
-    <Content Include="dashboard-ui\css\images\currentuserdefaultwhite.png">
-      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-    </Content>
     <Content Include="dashboard-ui\css\images\items\detail\person.png">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </Content>