Преглед изворни кода

Merge pull request #965 from MediaBrowser/dev

3.0.5482.0
Luke пре 10 година
родитељ
комит
c5ff30f66e
100 измењених фајлова са 1196 додато и 683 уклоњено
  1. 19 7
      MediaBrowser.Api/ApiEntryPoint.cs
  2. 1 1
      MediaBrowser.Api/ConfigurationService.cs
  3. 2 6
      MediaBrowser.Api/Dlna/DlnaServerService.cs
  4. 2 12
      MediaBrowser.Api/IHasDtoOptions.cs
  5. 7 6
      MediaBrowser.Api/Images/ImageService.cs
  6. 158 2
      MediaBrowser.Api/ItemUpdateService.cs
  7. 16 31
      MediaBrowser.Api/Library/LibraryService.cs
  8. 1 1
      MediaBrowser.Api/LiveTv/LiveTvService.cs
  9. 4 1
      MediaBrowser.Api/Movies/CollectionService.cs
  10. 16 12
      MediaBrowser.Api/Movies/MoviesService.cs
  11. 1 1
      MediaBrowser.Api/NotificationsService.cs
  12. 71 22
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  13. 21 8
      MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
  14. 16 6
      MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs
  15. 3 2
      MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs
  16. 14 4
      MediaBrowser.Api/Playback/Hls/MpegDashService.cs
  17. 14 6
      MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
  18. 1 1
      MediaBrowser.Api/Playback/Progressive/AudioService.cs
  19. 8 6
      MediaBrowser.Api/Playback/Progressive/VideoService.cs
  20. 1 0
      MediaBrowser.Api/Playback/StreamState.cs
  21. 1 0
      MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs
  22. 1 0
      MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs
  23. 22 4
      MediaBrowser.Api/Session/SessionsService.cs
  24. 4 3
      MediaBrowser.Api/Subtitles/SubtitleService.cs
  25. 82 10
      MediaBrowser.Api/Sync/SyncService.cs
  26. 2 2
      MediaBrowser.Api/System/ActivityLogWebSocketListener.cs
  27. 1 0
      MediaBrowser.Api/System/SystemInfoWebSocketListener.cs
  28. 4 5
      MediaBrowser.Api/UserLibrary/ArtistsService.cs
  29. 3 4
      MediaBrowser.Api/UserLibrary/GameGenresService.cs
  30. 3 4
      MediaBrowser.Api/UserLibrary/GenresService.cs
  31. 3 4
      MediaBrowser.Api/UserLibrary/MusicGenresService.cs
  32. 3 4
      MediaBrowser.Api/UserLibrary/PersonsService.cs
  33. 3 4
      MediaBrowser.Api/UserLibrary/StudiosService.cs
  34. 21 24
      MediaBrowser.Api/UserLibrary/UserLibraryService.cs
  35. 3 4
      MediaBrowser.Api/UserLibrary/YearsService.cs
  36. 99 51
      MediaBrowser.Api/UserService.cs
  37. 7 9
      MediaBrowser.Api/VideosService.cs
  38. 1 1
      MediaBrowser.Common.Implementations/BaseApplicationHost.cs
  39. 46 5
      MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs
  40. 1 16
      MediaBrowser.Common.Implementations/Configuration/ConfigurationHelper.cs
  41. 4 1
      MediaBrowser.Common.Implementations/Devices/DeviceId.cs
  42. 1 3
      MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
  43. 1 0
      MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj
  44. 4 0
      MediaBrowser.Common.Implementations/Security/MBLicenseFile.cs
  45. 14 3
      MediaBrowser.Common.Implementations/Serialization/XmlSerializer.cs
  46. 0 1
      MediaBrowser.Common.Implementations/packages.config
  47. 5 0
      MediaBrowser.Common/Configuration/IConfigurationFactory.cs
  48. 11 0
      MediaBrowser.Common/Extensions/BaseExtensions.cs
  49. 0 8
      MediaBrowser.Common/MediaBrowser.Common.csproj
  50. 32 11
      MediaBrowser.Common/Plugins/BasePlugin.cs
  51. 1 1
      MediaBrowser.Controller/Channels/Channel.cs
  52. 3 2
      MediaBrowser.Controller/Channels/ChannelAudioItem.cs
  53. 2 1
      MediaBrowser.Controller/Channels/ChannelFolderItem.cs
  54. 2 1
      MediaBrowser.Controller/Channels/ChannelVideoItem.cs
  55. 8 0
      MediaBrowser.Controller/Devices/IDeviceManager.cs
  56. 17 2
      MediaBrowser.Controller/Dto/DtoOptions.cs
  57. 17 1
      MediaBrowser.Controller/Entities/Audio/Audio.cs
  58. 2 1
      MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
  59. 12 25
      MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
  60. 13 5
      MediaBrowser.Controller/Entities/BaseItem.cs
  61. 2 1
      MediaBrowser.Controller/Entities/Book.cs
  62. 16 0
      MediaBrowser.Controller/Entities/CollectionFolder.cs
  63. 4 4
      MediaBrowser.Controller/Entities/Folder.cs
  64. 2 1
      MediaBrowser.Controller/Entities/Game.cs
  65. 2 1
      MediaBrowser.Controller/Entities/GameSystem.cs
  66. 2 2
      MediaBrowser.Controller/Entities/IHasMediaSources.cs
  67. 9 5
      MediaBrowser.Controller/Entities/Movies/BoxSet.cs
  68. 10 2
      MediaBrowser.Controller/Entities/Movies/Movie.cs
  69. 2 1
      MediaBrowser.Controller/Entities/MusicVideo.cs
  70. 3 2
      MediaBrowser.Controller/Entities/Photo.cs
  71. 3 2
      MediaBrowser.Controller/Entities/PhotoAlbum.cs
  72. 14 47
      MediaBrowser.Controller/Entities/TV/Episode.cs
  73. 52 27
      MediaBrowser.Controller/Entities/TV/Season.cs
  74. 13 2
      MediaBrowser.Controller/Entities/TV/Series.cs
  75. 2 1
      MediaBrowser.Controller/Entities/Trailer.cs
  76. 16 56
      MediaBrowser.Controller/Entities/User.cs
  77. 2 1
      MediaBrowser.Controller/Entities/UserView.cs
  78. 16 1
      MediaBrowser.Controller/Entities/Video.cs
  79. 14 23
      MediaBrowser.Controller/Library/ILibraryManager.cs
  80. 4 0
      MediaBrowser.Controller/Library/IUserDataManager.cs
  81. 22 7
      MediaBrowser.Controller/Library/IUserManager.cs
  82. 2 1
      MediaBrowser.Controller/LiveTv/LiveTvAudioRecording.cs
  83. 2 1
      MediaBrowser.Controller/LiveTv/LiveTvChannel.cs
  84. 2 1
      MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
  85. 2 1
      MediaBrowser.Controller/LiveTv/LiveTvVideoRecording.cs
  86. 2 1
      MediaBrowser.Controller/LiveTv/RecordingGroup.cs
  87. 8 3
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  88. 91 0
      MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs
  89. 0 80
      MediaBrowser.Controller/MediaEncoding/EncodingOptions.cs
  90. 0 13
      MediaBrowser.Controller/MediaEncoding/EncodingResult.cs
  91. 22 0
      MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
  92. 5 7
      MediaBrowser.Controller/MediaEncoding/MediaStreamSelector.cs
  93. 0 26
      MediaBrowser.Controller/MediaEncoding/VideoEncodingOptions.cs
  94. 3 2
      MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
  95. 1 1
      MediaBrowser.Controller/Net/IWebSocket.cs
  96. 1 1
      MediaBrowser.Controller/Net/IWebSocketConnection.cs
  97. 3 2
      MediaBrowser.Controller/Net/IWebSocketListener.cs
  98. 1 1
      MediaBrowser.Controller/Net/WebSocketConnectEventArgs.cs
  99. 1 1
      MediaBrowser.Controller/Net/WebSocketMessageInfo.cs
  100. 5 0
      MediaBrowser.Controller/Providers/DirectoryService.cs

+ 19 - 7
MediaBrowser.Api/ApiEntryPoint.cs

@@ -1,7 +1,10 @@
 using MediaBrowser.Api.Playback;
-using MediaBrowser.Controller;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Plugins;
 using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.Configuration;
+using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Session;
 using System;
@@ -33,7 +36,7 @@ namespace MediaBrowser.Api
         /// <summary>
         /// The application paths
         /// </summary>
-        private readonly IServerApplicationPaths _appPaths;
+        private readonly IServerConfigurationManager _config;
 
         private readonly ISessionManager _sessionManager;
 
@@ -43,13 +46,13 @@ namespace MediaBrowser.Api
         /// Initializes a new instance of the <see cref="ApiEntryPoint" /> class.
         /// </summary>
         /// <param name="logger">The logger.</param>
-        /// <param name="appPaths">The application paths.</param>
         /// <param name="sessionManager">The session manager.</param>
-        public ApiEntryPoint(ILogger logger, IServerApplicationPaths appPaths, ISessionManager sessionManager)
+        /// <param name="config">The configuration.</param>
+        public ApiEntryPoint(ILogger logger, ISessionManager sessionManager, IServerConfigurationManager config)
         {
             Logger = logger;
-            _appPaths = appPaths;
             _sessionManager = sessionManager;
+            _config = config;
 
             Instance = this;
         }
@@ -73,12 +76,19 @@ namespace MediaBrowser.Api
             }
         }
 
+        public EncodingOptions GetEncodingOptions()
+        {
+            return _config.GetConfiguration<EncodingOptions>("encoding");
+        }
+
         /// <summary>
         /// Deletes the encoded media cache.
         /// </summary>
         private void DeleteEncodedMediaCache()
         {
-            foreach (var file in Directory.EnumerateFiles(_appPaths.TranscodingTempPath, "*", SearchOption.AllDirectories)
+            var path = Path.Combine(_config.ApplicationPaths.TranscodingTempPath, EncodingContext.Streaming.ToString().ToLower());
+
+            foreach (var file in Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories)
                 .ToList())
             {
                 File.Delete(file);
@@ -185,7 +195,9 @@ namespace MediaBrowser.Api
                     CompletionPercentage = percentComplete,
                     Width = state.OutputWidth,
                     Height = state.OutputHeight,
-                    AudioChannels = state.OutputAudioChannels
+                    AudioChannels = state.OutputAudioChannels,
+                    IsAudioDirect = string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase),
+                    IsVideoDirect = string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)
                 });
             }
         }

+ 1 - 1
MediaBrowser.Api/ConfigurationService.cs

@@ -123,7 +123,7 @@ namespace MediaBrowser.Api
 
         public void Post(AutoSetMetadataOptions request)
         {
-            _configurationManager.DisableMetadataService("Media Browser Legacy Xml");
+            _configurationManager.DisableMetadataService("Media Browser Xml");
             _configurationManager.SaveConfiguration();
         }
 

+ 2 - 6
MediaBrowser.Api/Dlna/DlnaServerService.cs

@@ -1,5 +1,4 @@
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Controller.Dlna;
+using MediaBrowser.Controller.Dlna;
 using ServiceStack;
 using ServiceStack.Text.Controller;
 using ServiceStack.Web;
@@ -77,14 +76,11 @@ namespace MediaBrowser.Api.Dlna
         private readonly IContentDirectory _contentDirectory;
         private readonly IConnectionManager _connectionManager;
 
-        private readonly IConfigurationManager _config;
-
-        public DlnaServerService(IDlnaManager dlnaManager, IContentDirectory contentDirectory, IConnectionManager connectionManager, IConfigurationManager config)
+        public DlnaServerService(IDlnaManager dlnaManager, IContentDirectory contentDirectory, IConnectionManager connectionManager)
         {
             _dlnaManager = dlnaManager;
             _contentDirectory = contentDirectory;
             _connectionManager = connectionManager;
-            _config = config;
         }
 
         public object Get(GetDescriptionXml request)

+ 2 - 12
MediaBrowser.Api/IHasDtoOptions.cs

@@ -1,4 +1,4 @@
-using MediaBrowser.Model.Dto;
+using MediaBrowser.Controller.Dto;
 using MediaBrowser.Model.Entities;
 using System;
 using System.Linq;
@@ -28,17 +28,7 @@ namespace MediaBrowser.Api
                 options.ImageTypeLimit = request.ImageTypeLimit.Value;
             }
 
-            if (string.IsNullOrWhiteSpace(request.EnableImageTypes))
-            {
-                if (options.EnableImages)
-                {
-                    // Get everything
-                    options.ImageTypes = Enum.GetNames(typeof(ImageType))
-                        .Select(i => (ImageType)Enum.Parse(typeof(ImageType), i, true))
-                        .ToList();
-                }
-            }
-            else
+            if (!string.IsNullOrWhiteSpace(request.EnableImageTypes))
             {
                 options.ImageTypes = (request.EnableImageTypes ?? string.Empty).Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).Select(v => (ImageType)Enum.Parse(typeof(ImageType), v, true)).ToList();
             }

+ 7 - 6
MediaBrowser.Api/Images/ImageService.cs

@@ -18,6 +18,7 @@ using System.IO;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
+using MimeTypes = MediaBrowser.Model.Net.MimeTypes;
 
 namespace MediaBrowser.Api.Images
 {
@@ -668,26 +669,26 @@ namespace MediaBrowser.Api.Images
         {
             if (format == ImageFormat.Bmp)
             {
-                return Common.Net.MimeTypes.GetMimeType("i.bmp");
+                return MimeTypes.GetMimeType("i.bmp");
             }
             if (format == ImageFormat.Gif)
             {
-                return Common.Net.MimeTypes.GetMimeType("i.gif");
+                return MimeTypes.GetMimeType("i.gif");
             }
             if (format == ImageFormat.Jpg)
             {
-                return Common.Net.MimeTypes.GetMimeType("i.jpg");
+                return MimeTypes.GetMimeType("i.jpg");
             }
             if (format == ImageFormat.Png)
             {
-                return Common.Net.MimeTypes.GetMimeType("i.png");
+                return MimeTypes.GetMimeType("i.png");
             }
             if (format == ImageFormat.Webp)
             {
-                return Common.Net.MimeTypes.GetMimeType("i.webp");
+                return MimeTypes.GetMimeType("i.webp");
             }
 
-            return Common.Net.MimeTypes.GetMimeType(path);
+            return MimeTypes.GetMimeType(path);
         }
 
         /// <summary>

+ 158 - 2
MediaBrowser.Api/ItemUpdateService.cs

@@ -1,9 +1,14 @@
-using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Configuration;
+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.Localization;
 using MediaBrowser.Controller.Net;
+using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
 using ServiceStack;
 using System;
 using System.Collections.Generic;
@@ -20,14 +25,165 @@ namespace MediaBrowser.Api
         public string ItemId { get; set; }
     }
 
+    [Route("/Items/{ItemId}/MetadataEditor", "GET", Summary = "Gets metadata editor info for an item")]
+    public class GetMetadataEditorInfo : IReturn<MetadataEditorInfo>
+    {
+        [ApiMember(Name = "ItemId", Description = "The id of the item", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+        public string ItemId { get; set; }
+    }
+
+    [Route("/Items/{ItemId}/ContentType", "POST", Summary = "Updates an item's content type")]
+    public class UpdateItemContentType : IReturnVoid
+    {
+        [ApiMember(Name = "ItemId", Description = "The id of the item", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
+        public string ItemId { get; set; }
+
+        [ApiMember(Name = "ContentType", Description = "The content type of the item", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "POST")]
+        public string ContentType { get; set; }
+    }
+    
     [Authenticated]
     public class ItemUpdateService : BaseApiService
     {
         private readonly ILibraryManager _libraryManager;
+        private readonly IProviderManager _providerManager;
+        private readonly ILocalizationManager _localizationManager;
+        private readonly IServerConfigurationManager _config;
 
-        public ItemUpdateService(ILibraryManager libraryManager)
+        public ItemUpdateService(ILibraryManager libraryManager, IProviderManager providerManager, ILocalizationManager localizationManager, IServerConfigurationManager config)
         {
             _libraryManager = libraryManager;
+            _providerManager = providerManager;
+            _localizationManager = localizationManager;
+            _config = config;
+        }
+
+        public object Get(GetMetadataEditorInfo request)
+        {
+            var item = _libraryManager.GetItemById(request.ItemId);
+            
+            var info = new MetadataEditorInfo
+            {
+                ParentalRatingOptions = _localizationManager.GetParentalRatings().ToList(),
+                ExternalIdInfos = _providerManager.GetExternalIdInfos(item).ToList(),
+                Countries = _localizationManager.GetCountries().ToList(),
+                Cultures = _localizationManager.GetCultures().ToList()
+            };
+
+            var locationType = item.LocationType;
+            if (locationType == LocationType.FileSystem ||
+                locationType == LocationType.Offline)
+            {
+                if (!(item is ICollectionFolder) && !(item is UserView) && !(item is AggregateFolder) && !(item is LiveTvChannel) && !(item is IItemByName))
+                {
+                    var collectionType = _libraryManager.GetInheritedContentType(item);
+                    if (string.IsNullOrWhiteSpace(collectionType))
+                    {
+                        info.ContentTypeOptions = GetContentTypeOptions(true);
+                        info.ContentType = _libraryManager.GetContentType(item);
+                    }
+                }
+            }
+
+            return ToOptimizedResult(info);
+        }
+
+        public void Post(UpdateItemContentType request)
+        {
+            var item = _libraryManager.GetItemById(request.ItemId);
+            var path = item.ContainingFolderPath;
+
+            var types = _config.Configuration.ContentTypes
+                .Where(i => !string.Equals(i.Name, path, StringComparison.OrdinalIgnoreCase))
+                .ToList();
+
+            if (!string.IsNullOrWhiteSpace(request.ContentType))
+            {
+                types.Add(new NameValuePair
+                {
+                    Name = path,
+                    Value = request.ContentType
+                });
+            }
+
+            _config.Configuration.ContentTypes = types.ToArray();
+            _config.SaveConfiguration();
+        }
+
+        private List<NameValuePair> GetContentTypeOptions(bool isForItem)
+        {
+            var list = new List<NameValuePair>();
+
+            if (isForItem)
+            {
+                list.Add(new NameValuePair
+                {
+                    Name = "FolderTypeInherit",
+                    Value = ""
+                });
+            }
+            
+            list.Add(new NameValuePair
+            {
+                Name = "FolderTypeMovies",
+                Value = "movies"
+            });
+            list.Add(new NameValuePair
+            {
+                Name = "FolderTypeMusic",
+                Value = "music"
+            });
+            list.Add(new NameValuePair
+            {
+                Name = "FolderTypeTvShows",
+                Value = "tvshows"
+            });
+
+            if (!isForItem)
+            {
+                list.Add(new NameValuePair
+                {
+                    Name = "FolderTypeBooks",
+                    Value = "books"
+                });
+                list.Add(new NameValuePair
+                {
+                    Name = "FolderTypeGames",
+                    Value = "games"
+                });
+            }
+
+            list.Add(new NameValuePair
+            {
+                Name = "FolderTypeHomeVideos",
+                Value = "homevideos"
+            });
+            list.Add(new NameValuePair
+            {
+                Name = "FolderTypeMusicVideos",
+                Value = "musicvideos"
+            });
+            list.Add(new NameValuePair
+            {
+                Name = "FolderTypePhotos",
+                Value = "photos"
+            });
+
+            if (!isForItem)
+            {
+                list.Add(new NameValuePair
+                {
+                    Name = "FolderTypeMixed",
+                    Value = ""
+                });
+            }
+
+            foreach (var val in list)
+            {
+                val.Name = _localizationManager.GetLocalizedString(val.Name);
+            }
+
+            return list;
         }
 
         public void Post(UpdateItem request)

+ 16 - 31
MediaBrowser.Api/Library/LibraryService.cs

@@ -272,16 +272,13 @@ namespace MediaBrowser.Api.Library
                 items = items.Where(i => i.IsHidden == val).ToList();
             }
 
-            // Get everything
-            var fields = Enum.GetNames(typeof(ItemFields))
-                    .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
-                    .ToList();
+            var dtoOptions = new DtoOptions();
             
             var result = new ItemsResult
             {
                 TotalRecordCount = items.Count,
 
-                Items = items.Select(i => _dtoService.GetBaseItemDto(i, fields)).ToArray()
+                Items = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions)).ToArray()
             };
 
             return ToOptimizedResult(result);
@@ -347,10 +344,7 @@ namespace MediaBrowser.Api.Library
 
             var user = request.UserId.HasValue ? _userManager.GetUserById(request.UserId.Value) : null;
 
-            // Get everything
-            var fields = Enum.GetNames(typeof(ItemFields))
-                    .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
-                    .ToList();
+            var dtoOptions = new DtoOptions();
 
             BaseItem parent = item.Parent;
             
@@ -361,7 +355,7 @@ namespace MediaBrowser.Api.Library
                     parent = TranslateParentItem(parent, user);
                 }
 
-                baseItemDtos.Add(_dtoService.GetBaseItemDto(parent, fields, user));
+                baseItemDtos.Add(_dtoService.GetBaseItemDto(parent, dtoOptions, user));
 
                 parent = parent.Parent;
             }
@@ -473,20 +467,20 @@ namespace MediaBrowser.Api.Library
             var auth = _authContext.GetAuthorizationInfo(Request);
             var user = _userManager.GetUserById(auth.UserId);
 
-            if (item is Playlist)
+            if (item is Playlist || item is BoxSet)
             {
                 // For now this is allowed if user can see the playlist
             }
             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();
                 }
@@ -583,11 +577,6 @@ namespace MediaBrowser.Api.Library
                 item = item.Parent;
             }
 
-            // Get everything
-            var fields = Enum.GetNames(typeof(ItemFields))
-                    .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
-                    .ToList();
-
             var themeSongIds = GetThemeSongIds(item);
 
             if (themeSongIds.Count == 0 && request.InheritFromParent)
@@ -607,10 +596,12 @@ namespace MediaBrowser.Api.Library
                     }
                 }
             }
-           
+
+            var dtoOptions = new DtoOptions();
+
             var dtos = themeSongIds.Select(_libraryManager.GetItemById)
                             .OrderBy(i => i.SortName)
-                            .Select(i => _dtoService.GetBaseItemDto(i, fields, user, item));
+                            .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item));
 
             var items = dtos.ToArray();
 
@@ -651,11 +642,6 @@ namespace MediaBrowser.Api.Library
                 item = item.Parent;
             }
 
-            // Get everything
-            var fields = Enum.GetNames(typeof(ItemFields))
-                    .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
-                    .ToList();
-
             var themeVideoIds = GetThemeVideoIds(item);
 
             if (themeVideoIds.Count == 0 && request.InheritFromParent)
@@ -681,9 +667,11 @@ namespace MediaBrowser.Api.Library
                 }
             }
 
+            var dtoOptions = new DtoOptions();
+
             var dtos = themeVideoIds.Select(_libraryManager.GetItemById)
                             .OrderBy(i => i.SortName)
-                            .Select(i => _dtoService.GetBaseItemDto(i, fields, user, item));
+                            .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item));
 
             var items = dtos.ToArray();
 
@@ -754,10 +742,7 @@ namespace MediaBrowser.Api.Library
                                   : (Folder)_libraryManager.RootFolder)
                            : _libraryManager.GetItemById(id);
 
-            // Get everything
-            var fields = Enum.GetNames(typeof(ItemFields))
-                    .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
-                    .ToList();
+            var dtoOptions = new DtoOptions();
 
             var dtos = GetSoundtrackSongIds(item, inheritFromParent)
                 .Select(_libraryManager.GetItemById)
@@ -765,7 +750,7 @@ namespace MediaBrowser.Api.Library
                 .SelectMany(i => i.RecursiveChildren)
                 .OfType<Audio>()
                 .OrderBy(i => i.SortName)
-                .Select(i => _dtoService.GetBaseItemDto(i, fields, user, item));
+                .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item));
 
             var items = dtos.ToArray();
 

+ 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.");
             }

+ 4 - 1
MediaBrowser.Api/Movies/CollectionService.cs

@@ -2,6 +2,7 @@
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Model.Collections;
+using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Querying;
 using ServiceStack;
 using System;
@@ -70,7 +71,9 @@ namespace MediaBrowser.Api.Movies
 
             }).ConfigureAwait(false);
 
-            var dto = _dtoService.GetBaseItemDto(item, new List<ItemFields>());
+            var dtoOptions = new DtoOptions();
+
+            var dto = _dtoService.GetBaseItemDto(item, dtoOptions);
 
             return ToOptimizedResult(new CollectionCreationResult
             {

+ 16 - 12
MediaBrowser.Api/Movies/MoviesService.cs

@@ -157,7 +157,11 @@ namespace MediaBrowser.Api.Movies
                 .DistinctBy(i => i.GetProviderId(MetadataProviders.Imdb) ?? Guid.NewGuid().ToString(), StringComparer.OrdinalIgnoreCase)
                 .ToList();
 
-            var result = GetRecommendationCategories(user, listEligibleForCategories, listEligibleForSuggestion, request.CategoryLimit, request.ItemLimit, request.GetItemFields().ToList());
+            var dtoOptions = new DtoOptions();
+
+            dtoOptions.Fields = request.GetItemFields().ToList();
+            
+            var result = GetRecommendationCategories(user, listEligibleForCategories, listEligibleForSuggestion, request.CategoryLimit, request.ItemLimit, dtoOptions);
 
             return ToOptimizedResult(result);
         }
@@ -232,7 +236,7 @@ namespace MediaBrowser.Api.Movies
             return result;
         }
 
-        private IEnumerable<RecommendationDto> GetRecommendationCategories(User user, List<BaseItem> allMoviesForCategories, List<BaseItem> allMovies, int categoryLimit, int itemLimit, List<ItemFields> fields)
+        private IEnumerable<RecommendationDto> GetRecommendationCategories(User user, List<BaseItem> allMoviesForCategories, List<BaseItem> allMovies, int categoryLimit, int itemLimit, DtoOptions dtoOptions)
         {
             var categories = new List<RecommendationDto>();
 
@@ -282,11 +286,11 @@ namespace MediaBrowser.Api.Movies
                 .OrderBy(i => Guid.NewGuid())
                 .ToList();
 
-            var similarToRecentlyPlayed = GetSimilarTo(user, allMovies, recentlyPlayedMovies.Take(7).OrderBy(i => Guid.NewGuid()), itemLimit, fields, RecommendationType.SimilarToRecentlyPlayed).GetEnumerator();
-            var similarToLiked = GetSimilarTo(user, allMovies, likedMovies, itemLimit, fields, RecommendationType.SimilarToLikedItem).GetEnumerator();
+            var similarToRecentlyPlayed = GetSimilarTo(user, allMovies, recentlyPlayedMovies.Take(7).OrderBy(i => Guid.NewGuid()), itemLimit, dtoOptions, RecommendationType.SimilarToRecentlyPlayed).GetEnumerator();
+            var similarToLiked = GetSimilarTo(user, allMovies, likedMovies, itemLimit, dtoOptions, RecommendationType.SimilarToLikedItem).GetEnumerator();
 
-            var hasDirectorFromRecentlyPlayed = GetWithDirector(user, allMovies, recentDirectors, itemLimit, fields, RecommendationType.HasDirectorFromRecentlyPlayed).GetEnumerator();
-            var hasActorFromRecentlyPlayed = GetWithActor(user, allMovies, recentActors, itemLimit, fields, RecommendationType.HasActorFromRecentlyPlayed).GetEnumerator();
+            var hasDirectorFromRecentlyPlayed = GetWithDirector(user, allMovies, recentDirectors, itemLimit, dtoOptions, RecommendationType.HasDirectorFromRecentlyPlayed).GetEnumerator();
+            var hasActorFromRecentlyPlayed = GetWithActor(user, allMovies, recentActors, itemLimit, dtoOptions, RecommendationType.HasActorFromRecentlyPlayed).GetEnumerator();
 
             var categoryTypes = new List<IEnumerator<RecommendationDto>>
             {
@@ -329,7 +333,7 @@ namespace MediaBrowser.Api.Movies
             return categories.OrderBy(i => i.RecommendationType).ThenBy(i => Guid.NewGuid());
         }
 
-        private IEnumerable<RecommendationDto> GetWithDirector(User user, List<BaseItem> allMovies, IEnumerable<string> directors, int itemLimit, List<ItemFields> fields, RecommendationType type)
+        private IEnumerable<RecommendationDto> GetWithDirector(User user, List<BaseItem> allMovies, IEnumerable<string> directors, int itemLimit, DtoOptions dtoOptions, RecommendationType type)
         {
             var userId = user.Id;
 
@@ -347,13 +351,13 @@ namespace MediaBrowser.Api.Movies
                         BaselineItemName = director,
                         CategoryId = director.GetMD5().ToString("N"),
                         RecommendationType = type,
-                        Items = items.Select(i => _dtoService.GetBaseItemDto(i, fields, user)).ToArray()
+                        Items = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user)).ToArray()
                     };
                 }
             }
         }
 
-        private IEnumerable<RecommendationDto> GetWithActor(User user, List<BaseItem> allMovies, IEnumerable<string> names, int itemLimit, List<ItemFields> fields, RecommendationType type)
+        private IEnumerable<RecommendationDto> GetWithActor(User user, List<BaseItem> allMovies, IEnumerable<string> names, int itemLimit, DtoOptions dtoOptions, RecommendationType type)
         {
             var userId = user.Id;
 
@@ -371,13 +375,13 @@ namespace MediaBrowser.Api.Movies
                         BaselineItemName = name,
                         CategoryId = name.GetMD5().ToString("N"),
                         RecommendationType = type,
-                        Items = items.Select(i => _dtoService.GetBaseItemDto(i, fields, user)).ToArray()
+                        Items = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user)).ToArray()
                     };
                 }
             }
         }
 
-        private IEnumerable<RecommendationDto> GetSimilarTo(User user, List<BaseItem> allMovies, IEnumerable<BaseItem> baselineItems, int itemLimit, List<ItemFields> fields, RecommendationType type)
+        private IEnumerable<RecommendationDto> GetSimilarTo(User user, List<BaseItem> allMovies, IEnumerable<BaseItem> baselineItems, int itemLimit, DtoOptions dtoOptions, RecommendationType type)
         {
             var userId = user.Id;
 
@@ -395,7 +399,7 @@ namespace MediaBrowser.Api.Movies
                         BaselineItemName = item.Name,
                         CategoryId = item.Id.ToString("N"),
                         RecommendationType = type,
-                        Items = similar.Select(i => _dtoService.GetBaseItemDto(i, fields, user)).ToArray()
+                        Items = similar.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user)).ToArray()
                     };
                 }
             }

+ 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);

+ 71 - 22
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Configuration;
@@ -118,8 +119,8 @@ namespace MediaBrowser.Api.Playback
         /// <returns>System.String.</returns>
         private string GetOutputFilePath(StreamState state)
         {
-            var folder = ServerConfigurationManager.ApplicationPaths.TranscodingTempPath;
-
+            var folder = Path.Combine(ServerConfigurationManager.ApplicationPaths.TranscodingTempPath, EncodingContext.Streaming.ToString().ToLower());
+            
             var outputFileExtension = GetOutputFileExtension(state);
 
             var data = GetCommandLineArguments("dummy\\dummy", "dummyTranscodingId", state, false);
@@ -202,6 +203,10 @@ namespace MediaBrowser.Api.Playback
             {
                 args += " -map -0:s";
             }
+            else if (state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
+            {
+                args += " -map 1:0 -sn";
+            }
 
             return args;
         }
@@ -245,7 +250,7 @@ namespace MediaBrowser.Api.Playback
 
         protected EncodingQuality GetQualitySetting()
         {
-            var quality = ServerConfigurationManager.Configuration.MediaEncodingQuality;
+            var quality = ApiEntryPoint.Instance.GetEncodingOptions().EncodingQuality;
 
             if (quality == EncodingQuality.Auto)
             {
@@ -273,7 +278,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)
             {
@@ -302,6 +307,21 @@ namespace MediaBrowser.Api.Playback
             }
         }
 
+        protected string H264Encoder
+        {
+            get
+            {
+                var lib = ApiEntryPoint.Instance.GetEncodingOptions().H264Encoder;
+
+                if (!string.IsNullOrWhiteSpace(lib))
+                {
+                    return lib;
+                }
+
+                return "libx264";
+            }
+        }
+
         /// <summary>
         /// Gets the video bitrate to specify on the command line
         /// </summary>
@@ -318,7 +338,7 @@ namespace MediaBrowser.Api.Playback
 
             var qualitySetting = GetQualitySetting();
 
-            if (string.Equals(videoCodec, "libx264", StringComparison.OrdinalIgnoreCase))
+            if (string.Equals(videoCodec, H264Encoder, StringComparison.OrdinalIgnoreCase))
             {
                 switch (qualitySetting)
                 {
@@ -442,7 +462,7 @@ namespace MediaBrowser.Api.Playback
             {
                 if (state.AudioStream != null && state.AudioStream.Channels.HasValue && state.AudioStream.Channels.Value > 5)
                 {
-                    volParam = ",volume=" + ServerConfigurationManager.Configuration.DownMixAudioBoost.ToString(UsCulture);
+                    volParam = ",volume=" + ApiEntryPoint.Instance.GetEncodingOptions().DownMixAudioBoost.ToString(UsCulture);
                 }
             }
 
@@ -651,9 +671,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);
         }
@@ -700,7 +729,8 @@ namespace MediaBrowser.Api.Playback
                     return Math.Min(request.MaxAudioChannels.Value, audioStream.Channels.Value);
                 }
 
-                return request.MaxAudioChannels.Value;
+                // If we don't have any media info then limit it to 5 to prevent encoding errors due to asking for too many channels
+                return Math.Min(request.MaxAudioChannels.Value, 5);
             }
 
             return request.AudioChannels;
@@ -761,7 +791,7 @@ namespace MediaBrowser.Api.Playback
             {
                 if (string.Equals(codec, "h264", StringComparison.OrdinalIgnoreCase))
                 {
-                    return "libx264";
+                    return H264Encoder;
                 }
                 if (string.Equals(codec, "vpx", StringComparison.OrdinalIgnoreCase))
                 {
@@ -797,6 +827,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 &&
@@ -868,7 +913,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;
                 }
@@ -898,8 +943,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)
         {
@@ -910,7 +955,7 @@ namespace MediaBrowser.Api.Playback
             var transcodingId = Guid.NewGuid().ToString("N");
             var commandLineArgs = GetCommandLineArguments(outputPath, transcodingId, state, true);
 
-            if (ServerConfigurationManager.Configuration.EnableDebugEncodingLogging)
+            if (ApiEntryPoint.Instance.GetEncodingOptions().EnableDebugLogging)
             {
                 commandLineArgs = "-loglevel debug " + commandLineArgs;
             }
@@ -1088,7 +1133,7 @@ namespace MediaBrowser.Api.Playback
                     if (scale.HasValue)
                     {
                         long val;
-                        
+
                         if (long.TryParse(size, NumberStyles.Any, UsCulture, out val))
                         {
                             bytesTranscoded = val * scale.Value;
@@ -1562,9 +1607,6 @@ namespace MediaBrowser.Api.Playback
                 mediaStreams = new List<MediaStream>();
 
                 state.DeInterlace = true;
-                state.OutputAudioSync = "1000";
-                state.InputVideoSync = "-1";
-                state.InputAudioSync = "1";
 
                 // Just to prevent this from being null and causing other methods to fail
                 state.MediaPath = string.Empty;
@@ -1630,7 +1672,7 @@ namespace MediaBrowser.Api.Playback
 
             if (string.IsNullOrEmpty(container))
             {
-                container = request.Static ? 
+                container = request.Static ?
                     state.InputContainer :
                     (Path.GetExtension(GetOutputFilePath(state)) ?? string.Empty).TrimStart('.');
             }
@@ -1696,9 +1738,16 @@ namespace MediaBrowser.Api.Playback
             state.InputFileSize = mediaSource.Size;
             state.ReadInputAtNativeFramerate = mediaSource.ReadAtNativeFramerate;
 
+            if (state.ReadInputAtNativeFramerate)
+            {
+                state.OutputAudioSync = "1000";
+                state.InputVideoSync = "-1";
+                state.InputAudioSync = "1";
+            }
+
             AttachMediaStreamInfo(state, mediaSource.MediaStreams, videoRequest, requestedUrl);
         }
-        
+
         private void AttachMediaStreamInfo(StreamState state,
             List<MediaStream> mediaStreams,
             VideoStreamRequest videoRequest,

+ 21 - 8
MediaBrowser.Api/Playback/Hls/BaseHlsService.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.IO;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Channels;
 using MediaBrowser.Controller.Configuration;
@@ -6,7 +7,6 @@ using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.IO;
 using System;
 using System.Collections.Generic;
@@ -14,6 +14,7 @@ using System.IO;
 using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
+using MediaBrowser.Model.Net;
 
 namespace MediaBrowser.Api.Playback.Hls
 {
@@ -119,11 +120,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
             if (isLive)
             {
-                //var file = request.PlaylistId + Path.GetExtension(Request.PathInfo);
-
-                //file = Path.Combine(ServerConfigurationManager.ApplicationPaths.TranscodingTempPath, file);
-
-                return ResultFactory.GetStaticFileResult(Request, playlist, FileShare.ReadWrite);
+                return ResultFactory.GetResult(GetLivePlaylistText(playlist, state.SegmentLength), MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
             }
 
             var audioBitrate = state.OutputAudioBitrate ?? 0;
@@ -144,6 +141,22 @@ namespace MediaBrowser.Api.Playback.Hls
             return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
         }
 
+        private string GetLivePlaylistText(string path, int segmentLength)
+        {
+            using (var stream = FileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
+            {
+                using (var reader = new StreamReader(stream))
+                {
+                    var text = reader.ReadToEnd();
+
+                    var newDuration = "#EXT-X-TARGETDURATION:" + segmentLength.ToString(UsCulture) + Environment.NewLine + "#EXT-X-ALLOW-CACHE:NO";
+
+                    // ffmpeg pads the reported length by a full second
+                    return text.Replace("#EXT-X-TARGETDURATION:" + (segmentLength + 1).ToString(UsCulture), newDuration, StringComparison.OrdinalIgnoreCase);
+                }
+            }
+        }
+
         private string GetMasterPlaylistFileText(string firstPlaylist, int bitrate, bool includeBaselineStream, int baselineStreamBitrate)
         {
             var builder = new StringBuilder();
@@ -227,7 +240,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),

+ 16 - 6
MediaBrowser.Api/Playback/Hls/DynamicHlsService.cs

@@ -20,6 +20,7 @@ using System.Linq;
 using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
+using MimeTypes = MediaBrowser.Model.Net.MimeTypes;
 
 namespace MediaBrowser.Api.Playback.Hls
 {
@@ -387,7 +388,7 @@ namespace MediaBrowser.Api.Playback.Hls
                 playlistText = GetMasterPlaylistFileText(state, videoBitrate + audioBitrate);
             }
 
-            return ResultFactory.GetResult(playlistText, Common.Net.MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
+            return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
         }
 
         private string GetMasterPlaylistFileText(StreamState state, int totalBitrate)
@@ -603,7 +604,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
             var playlistText = builder.ToString();
 
-            return ResultFactory.GetResult(playlistText, Common.Net.MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
+            return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
         }
 
         protected override string GetAudioArguments(StreamState state)
@@ -640,10 +641,19 @@ namespace MediaBrowser.Api.Playback.Hls
         {
             var codec = state.OutputVideoCodec;
 
+            var args = "-codec:v:0 " + codec;
+
+            if (state.EnableMpegtsM2TsMode)
+            {
+                args += " -mpegts_m2ts_mode 1";
+            }
+
             // See if we can save come cpu cycles by avoiding encoding
-            if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
+            if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
             {
-                return state.VideoStream != null && IsH264(state.VideoStream) ? "-codec:v:0 copy -bsf:v h264_mp4toannexb" : "-codec:v:0 copy";
+                return state.VideoStream != null && IsH264(state.VideoStream) ?
+                    args + " -bsf:v h264_mp4toannexb" :
+                    args;
             }
 
             var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})",
@@ -651,7 +661,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
             var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream;
 
-            var args = "-codec:v:0 " + codec + " " + GetVideoQualityParam(state, "libx264", true) + keyFrameArg;
+            args += " " + GetVideoQualityParam(state, H264Encoder, true) + keyFrameArg;
 
             // Add resolution params, if specified
             if (!hasGraphicalSubs)
@@ -677,7 +687,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,

+ 3 - 2
MediaBrowser.Api/Playback/Hls/HlsSegmentService.cs

@@ -1,4 +1,5 @@
 using MediaBrowser.Controller;
+using MediaBrowser.Model.Dlna;
 using ServiceStack;
 using System;
 using System.IO;
@@ -65,7 +66,7 @@ namespace MediaBrowser.Api.Playback.Hls
         {
             var file = request.PlaylistId + Path.GetExtension(Request.PathInfo);
 
-            file = Path.Combine(_appPaths.TranscodingTempPath, file);
+            file = Path.Combine(_appPaths.TranscodingTempPath, EncodingContext.Streaming.ToString().ToLower(), file);
 
             return ResultFactory.GetStaticFileResult(Request, file, FileShare.ReadWrite);
         }
@@ -84,7 +85,7 @@ namespace MediaBrowser.Api.Playback.Hls
         {
             var file = request.SegmentId + Path.GetExtension(Request.PathInfo);
 
-            file = Path.Combine(_appPaths.TranscodingTempPath, file);
+            file = Path.Combine(_appPaths.TranscodingTempPath, EncodingContext.Streaming.ToString().ToLower(), file);
 
             return ResultFactory.GetStaticFileResult(Request, file, FileShare.ReadWrite);
         }

+ 14 - 4
MediaBrowser.Api/Playback/Hls/MpegDashService.cs

@@ -18,6 +18,7 @@ using System.Security;
 using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
+using MimeTypes = MediaBrowser.Model.Net.MimeTypes;
 
 namespace MediaBrowser.Api.Playback.Hls
 {
@@ -97,7 +98,7 @@ namespace MediaBrowser.Api.Playback.Hls
                 playlistText = GetManifestText(state);
             }
 
-            return ResultFactory.GetResult(playlistText, Common.Net.MimeTypes.GetMimeType("playlist.mpd"), new Dictionary<string, string>());
+            return ResultFactory.GetResult(playlistText, MimeTypes.GetMimeType("playlist.mpd"), new Dictionary<string, string>());
         }
 
         private string GetManifestText(StreamState state)
@@ -583,10 +584,19 @@ namespace MediaBrowser.Api.Playback.Hls
         {
             var codec = state.OutputVideoCodec;
 
+            var args = "-codec:v:0 " + codec;
+
+            if (state.EnableMpegtsM2TsMode)
+            {
+                args += " -mpegts_m2ts_mode 1";
+            }
+
             // See if we can save come cpu cycles by avoiding encoding
             if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
             {
-                return IsH264(state.VideoStream) ? "-codec:v:0 copy -bsf:v h264_mp4toannexb" : "-codec:v:0 copy";
+                return state.VideoStream != null && IsH264(state.VideoStream) ?
+                    args + " -bsf:v h264_mp4toannexb" :
+                    args;
             }
 
             var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})",
@@ -594,7 +604,7 @@ namespace MediaBrowser.Api.Playback.Hls
 
             var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream;
 
-            var args = "-codec:v:0 " + codec + " " + GetVideoQualityParam(state, "libx264", true) + keyFrameArg;
+            args+= " " + GetVideoQualityParam(state, H264Encoder, true) + keyFrameArg;
 
             args += " -r 24 -g 24";
 
@@ -627,7 +637,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,

+ 14 - 6
MediaBrowser.Api/Playback/Hls/VideoHlsService.cs

@@ -5,12 +5,11 @@ using MediaBrowser.Controller.Dlna;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.Dlna;
 using MediaBrowser.Model.IO;
 using ServiceStack;
 using System;
 using System.IO;
-using System.Linq;
-using System.Threading.Tasks;
 
 namespace MediaBrowser.Api.Playback.Hls
 {
@@ -72,7 +71,7 @@ namespace MediaBrowser.Api.Playback.Hls
         {
             var file = request.SegmentId + Path.GetExtension(Request.PathInfo);
 
-            file = Path.Combine(ServerConfigurationManager.ApplicationPaths.TranscodingTempPath, file);
+            file = Path.Combine(ServerConfigurationManager.ApplicationPaths.TranscodingTempPath, EncodingContext.Streaming.ToString().ToLower(), file);
 
             return ResultFactory.GetStaticFileResult(Request, file);
         }
@@ -136,18 +135,27 @@ namespace MediaBrowser.Api.Playback.Hls
         {
             var codec = state.OutputVideoCodec;
 
+            var args = "-codec:v:0 " + codec;
+
+            if (state.EnableMpegtsM2TsMode)
+            {
+                args += " -mpegts_m2ts_mode 1";
+            }
+
             // See if we can save come cpu cycles by avoiding encoding
             if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
             {
-                return state.VideoStream != null && IsH264(state.VideoStream) ? "-codec:v:0 copy -bsf:v h264_mp4toannexb" : "-codec:v:0 copy";
+                return state.VideoStream != null && IsH264(state.VideoStream) ?
+                    args + " -bsf:v h264_mp4toannexb" :
+                    args;
             }
-
+            
             var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})",
                 state.SegmentLength.ToString(UsCulture));
 
             var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream;
 
-            var args = "-codec:v:0 " + codec + " " + GetVideoQualityParam(state, "libx264", true) + keyFrameArg;
+            args += " " + GetVideoQualityParam(state, H264Encoder, true) + keyFrameArg;
 
             // Add resolution params, if specified
             if (!hasGraphicalSubs)

+ 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,

+ 8 - 6
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,
@@ -124,7 +124,7 @@ namespace MediaBrowser.Api.Playback.Progressive
         /// <returns>System.String.</returns>
         private string GetVideoArguments(StreamState state, string codec)
         {
-            var args = "-vcodec " + codec;
+            var args = "-codec:v:0 " + codec;
 
             if (state.EnableMpegtsM2TsMode)
             {
@@ -134,7 +134,9 @@ namespace MediaBrowser.Api.Playback.Progressive
             // See if we can save come cpu cycles by avoiding encoding
             if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
             {
-                return state.VideoStream != null && IsH264(state.VideoStream) ? args + " -bsf:v h264_mp4toannexb" : args;
+                return state.VideoStream != null && IsH264(state.VideoStream) && string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase) ?
+                    args + " -bsf:v h264_mp4toannexb" :
+                    args;
             }
 
             var keyFrameArg = string.Format(" -force_key_frames expr:gte(t,n_forced*{0})",
@@ -182,13 +184,13 @@ namespace MediaBrowser.Api.Playback.Progressive
             // Get the output codec name
             var codec = state.OutputAudioCodec;
 
+            var args = "-codec:a:0 " + codec;
+
             if (codec.Equals("copy", StringComparison.OrdinalIgnoreCase))
             {
-                return "-acodec copy";
+                return args;
             }
 
-            var args = "-acodec " + codec;
-
             // Add the number of audio channels
             var channels = state.OutputAudioChannels;
 

+ 1 - 0
MediaBrowser.Api/Playback/StreamState.cs

@@ -11,6 +11,7 @@ using System.Collections.Generic;
 using System.Globalization;
 using System.IO;
 using System.Threading;
+using MediaBrowser.Model.Net;
 
 namespace MediaBrowser.Api.Playback
 {

+ 1 - 0
MediaBrowser.Api/ScheduledTasks/ScheduledTasksWebSocketListener.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.ScheduledTasks;
+using MediaBrowser.Controller.Net;
 using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Tasks;

+ 1 - 0
MediaBrowser.Api/Session/SessionInfoWebSocketListener.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Session;

+ 22 - 4
MediaBrowser.Api/Session/SessionsService.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Devices;
+using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Security;
 using MediaBrowser.Controller.Session;
@@ -299,6 +300,7 @@ namespace MediaBrowser.Api.Session
         private readonly IUserManager _userManager;
         private readonly IAuthorizationContext _authContext;
         private readonly IAuthenticationRepository _authRepo;
+        private readonly IDeviceManager _deviceManager;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="SessionsService" /> class.
@@ -307,12 +309,13 @@ namespace MediaBrowser.Api.Session
         /// <param name="userManager">The user manager.</param>
         /// <param name="authContext">The authentication context.</param>
         /// <param name="authRepo">The authentication repo.</param>
-        public SessionsService(ISessionManager sessionManager, IUserManager userManager, IAuthorizationContext authContext, IAuthenticationRepository authRepo)
+        public SessionsService(ISessionManager sessionManager, IUserManager userManager, IAuthorizationContext authContext, IAuthenticationRepository authRepo, IDeviceManager deviceManager)
         {
             _sessionManager = sessionManager;
             _userManager = userManager;
             _authContext = authContext;
             _authRepo = authRepo;
+            _deviceManager = deviceManager;
         }
 
         public void Delete(RevokeKey request)
@@ -373,15 +376,30 @@ 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);
                 }
+
+                result = result.Where(i =>
+                {
+                    var deviceId = i.DeviceId;
+
+                    if (!string.IsNullOrWhiteSpace(deviceId))
+                    {
+                        if (!_deviceManager.CanAccessDevice(user.Id.ToString("N"), deviceId))
+                        {
+                            return false;
+                        }
+                    }
+
+                    return true;
+                });
             }
 
             return ToOptimizedResult(result.Select(_sessionManager.GetSessionInfoDto).ToList());

+ 4 - 3
MediaBrowser.Api/Subtitles/SubtitleService.cs

@@ -15,6 +15,7 @@ using System.Linq;
 using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
+using MimeTypes = MediaBrowser.Model.Net.MimeTypes;
 
 namespace MediaBrowser.Api.Subtitles
 {
@@ -175,7 +176,7 @@ namespace MediaBrowser.Api.Subtitles
 
             builder.AppendLine("#EXT-X-ENDLIST");
 
-            return ResultFactory.GetResult(builder.ToString(), Common.Net.MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
+            return ResultFactory.GetResult(builder.ToString(), MimeTypes.GetMimeType("playlist.m3u8"), new Dictionary<string, string>());
         }
 
         public object Get(GetSubtitle request)
@@ -199,7 +200,7 @@ namespace MediaBrowser.Api.Subtitles
 
             var stream = GetSubtitles(request).Result;
 
-            return ResultFactory.GetResult(stream, Common.Net.MimeTypes.GetMimeType("file." + request.Format));
+            return ResultFactory.GetResult(stream, MimeTypes.GetMimeType("file." + request.Format));
         }
 
         private async Task<Stream> GetSubtitles(GetSubtitle request)
@@ -240,7 +241,7 @@ namespace MediaBrowser.Api.Subtitles
         {
             var result = _subtitleManager.GetRemoteSubtitles(request.Id, CancellationToken.None).Result;
 
-            return ResultFactory.GetResult(result.Stream, Common.Net.MimeTypes.GetMimeType("file." + result.Format));
+            return ResultFactory.GetResult(result.Stream, MimeTypes.GetMimeType("file." + result.Format));
         }
 
         public void Post(DownloadRemoteSubtitles request)

+ 82 - 10
MediaBrowser.Api/Sync/SyncService.cs

@@ -5,6 +5,7 @@ using MediaBrowser.Controller.Sync;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Querying;
 using MediaBrowser.Model.Sync;
+using MediaBrowser.Model.Users;
 using ServiceStack;
 using System;
 using System.Collections.Generic;
@@ -27,6 +28,11 @@ namespace MediaBrowser.Api.Sync
         public string Id { get; set; }
     }
 
+    [Route("/Sync/Jobs/{Id}", "POST", Summary = "Updates a sync job.")]
+    public class UpdateSyncJob : SyncJob, IReturnVoid
+    {
+    }
+
     [Route("/Sync/JobItems", "GET", Summary = "Gets sync job items.")]
     public class GetSyncJobItems : SyncJobItemQuery, IReturn<QueryResult<SyncJobItem>>
     {
@@ -55,8 +61,14 @@ namespace MediaBrowser.Api.Sync
         [ApiMember(Name = "UserId", Description = "UserId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
         public string UserId { get; set; }
 
-        [ApiMember(Name = "ItemIds", Description = "ItemIds", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
+        [ApiMember(Name = "ItemIds", Description = "ItemIds", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
         public string ItemIds { get; set; }
+
+        [ApiMember(Name = "ParentId", Description = "ParentId", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string ParentId { get; set; }
+
+        [ApiMember(Name = "Category", Description = "Category", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public SyncCategory? Category { get; set; }
     }
 
     [Route("/Sync/JobItems/{Id}/Transferred", "POST", Summary = "Reports that a sync job item has successfully been transferred.")]
@@ -73,6 +85,23 @@ namespace MediaBrowser.Api.Sync
         public string Id { get; set; }
     }
 
+    [Route("/Sync/OfflineActions", "POST", Summary = "Reports an action that occurred while offline.")]
+    public class ReportOfflineActions : List<UserAction>, IReturnVoid
+    {
+    }
+
+    [Route("/Sync/Items/Ready", "GET", Summary = "Gets ready to download sync items.")]
+    public class GetReadySyncItems : IReturn<List<SyncedItem>>
+    {
+        [ApiMember(Name = "TargetId", Description = "TargetId", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string TargetId { get; set; }
+    }
+
+    [Route("/Sync/Data", "POST", Summary = "Syncs data between device and server")]
+    public class SyncData : SyncDataRequest, IReturn<SyncDataResponse>
+    {
+    }
+
     [Authenticated]
     public class SyncService : BaseApiService
     {
@@ -94,9 +123,9 @@ namespace MediaBrowser.Api.Sync
             return ToOptimizedResult(result);
         }
 
-        public object Get(GetSyncJobs request)
+        public async Task<object> Get(GetSyncJobs request)
         {
-            var result = _syncManager.GetJobs(request);
+            var result = await _syncManager.GetJobs(request).ConfigureAwait(false);
 
             return ToOptimizedResult(result);
         }
@@ -155,21 +184,64 @@ namespace MediaBrowser.Api.Sync
             result.Targets = _syncManager.GetSyncTargets(request.UserId)
                 .ToList();
 
-            var dtos = request.ItemIds.Split(',')
-                .Select(_libraryManager.GetItemById)
-                .Where(i => i != null)
-                .Select(i => _dtoService.GetBaseItemDto(i, new DtoOptions
+            if (request.Category.HasValue)
+            {
+                result.Options = SyncHelper.GetSyncOptions(request.Category.Value);
+            }
+            else
+            {
+                var dtoOptions = new DtoOptions
                 {
                     Fields = new List<ItemFields>
                     {
                         ItemFields.SyncInfo
                     }
-                }))
-                .ToList();
+                };
 
-            result.Options = SyncHelper.GetSyncOptions(dtos);
+                var dtos = request.ItemIds.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
+                    .Select(_libraryManager.GetItemById)
+                    .Where(i => i != null)
+                    .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions))
+                    .ToList();
+
+                result.Options = SyncHelper.GetSyncOptions(dtos);
+            }
 
             return ToOptimizedResult(result);
         }
+
+        public void Post(ReportOfflineActions request)
+        {
+            var task = PostAsync(request);
+
+            Task.WaitAll(task);
+        }
+
+        public async Task PostAsync(ReportOfflineActions request)
+        {
+            foreach (var action in request)
+            {
+                await _syncManager.ReportOfflineAction(action).ConfigureAwait(false);
+            }
+        }
+
+        public object Get(GetReadySyncItems request)
+        {
+            return ToOptimizedResult(_syncManager.GetReadySyncItems(request.TargetId));
+        }
+
+        public async Task<object> Post(SyncData request)
+        {
+            var response = await _syncManager.SyncData(request).ConfigureAwait(false);
+
+            return ToOptimizedResult(response);
+        }
+
+        public void Post(UpdateSyncJob request)
+        {
+            var task = _syncManager.UpdateJob(request);
+
+            Task.WaitAll(task);
+        }
     }
 }

+ 2 - 2
MediaBrowser.Api/System/ActivityLogWebSocketListener.cs

@@ -1,5 +1,5 @@
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Activity;
+using MediaBrowser.Controller.Activity;
+using MediaBrowser.Controller.Net;
 using MediaBrowser.Model.Activity;
 using MediaBrowser.Model.Events;
 using MediaBrowser.Model.Logging;

+ 1 - 0
MediaBrowser.Api/System/SystemInfoWebSocketListener.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller;
+using MediaBrowser.Controller.Net;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.System;
 using System.Threading.Tasks;

+ 4 - 5
MediaBrowser.Api/UserLibrary/ArtistsService.cs

@@ -83,17 +83,16 @@ namespace MediaBrowser.Api.UserLibrary
         {
             var item = GetArtist(request.Name, LibraryManager);
 
-            // Get everything
-            var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true));
+            var dtoOptions = new DtoOptions();
 
             if (request.UserId.HasValue)
             {
                 var user = UserManager.GetUserById(request.UserId.Value);
-                
-                return DtoService.GetBaseItemDto(item, fields.ToList(), user);
+
+                return DtoService.GetBaseItemDto(item, dtoOptions, user);
             }
 
-            return DtoService.GetBaseItemDto(item, fields.ToList());
+            return DtoService.GetBaseItemDto(item, dtoOptions);
         }
 
         /// <summary>

+ 3 - 4
MediaBrowser.Api/UserLibrary/GameGenresService.cs

@@ -69,17 +69,16 @@ namespace MediaBrowser.Api.UserLibrary
         {
             var item = GetGameGenre(request.Name, LibraryManager);
 
-            // Get everything
-            var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true));
+            var dtoOptions = new DtoOptions();
 
             if (request.UserId.HasValue)
             {
                 var user = UserManager.GetUserById(request.UserId.Value);
 
-                return DtoService.GetBaseItemDto(item, fields.ToList(), user);
+                return DtoService.GetBaseItemDto(item, dtoOptions, user);
             }
 
-            return DtoService.GetBaseItemDto(item, fields.ToList());
+            return DtoService.GetBaseItemDto(item, dtoOptions);
         }
 
         /// <summary>

+ 3 - 4
MediaBrowser.Api/UserLibrary/GenresService.cs

@@ -74,17 +74,16 @@ namespace MediaBrowser.Api.UserLibrary
         {
             var item = GetGenre(request.Name, LibraryManager);
 
-            // Get everything
-            var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true));
+            var dtoOptions = new DtoOptions();
 
             if (request.UserId.HasValue)
             {
                 var user = UserManager.GetUserById(request.UserId.Value);
 
-                return DtoService.GetBaseItemDto(item, fields.ToList(), user);
+                return DtoService.GetBaseItemDto(item, dtoOptions, user);
             }
 
-            return DtoService.GetBaseItemDto(item, fields.ToList());
+            return DtoService.GetBaseItemDto(item, dtoOptions);
         }
 
         /// <summary>

+ 3 - 4
MediaBrowser.Api/UserLibrary/MusicGenresService.cs

@@ -69,17 +69,16 @@ namespace MediaBrowser.Api.UserLibrary
         {
             var item = GetMusicGenre(request.Name, LibraryManager);
 
-            // Get everything
-            var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true));
+            var dtoOptions = new DtoOptions();
 
             if (request.UserId.HasValue)
             {
                 var user = UserManager.GetUserById(request.UserId.Value);
 
-                return DtoService.GetBaseItemDto(item, fields.ToList(), user);
+                return DtoService.GetBaseItemDto(item, dtoOptions, user);
             }
 
-            return DtoService.GetBaseItemDto(item, fields.ToList());
+            return DtoService.GetBaseItemDto(item, dtoOptions);
         }
 
         /// <summary>

+ 3 - 4
MediaBrowser.Api/UserLibrary/PersonsService.cs

@@ -86,17 +86,16 @@ namespace MediaBrowser.Api.UserLibrary
         {
             var item = GetPerson(request.Name, LibraryManager);
 
-            // Get everything
-            var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true));
+            var dtoOptions = new DtoOptions();
 
             if (request.UserId.HasValue)
             {
                 var user = UserManager.GetUserById(request.UserId.Value);
 
-                return DtoService.GetBaseItemDto(item, fields.ToList(), user);
+                return DtoService.GetBaseItemDto(item, dtoOptions, user);
             }
 
-            return DtoService.GetBaseItemDto(item, fields.ToList());
+            return DtoService.GetBaseItemDto(item, dtoOptions);
         }
 
         /// <summary>

+ 3 - 4
MediaBrowser.Api/UserLibrary/StudiosService.cs

@@ -73,17 +73,16 @@ namespace MediaBrowser.Api.UserLibrary
         {
             var item = GetStudio(request.Name, LibraryManager);
 
-            // Get everything
-            var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true));
+            var dtoOptions = new DtoOptions();
 
             if (request.UserId.HasValue)
             {
                 var user = UserManager.GetUserById(request.UserId.Value);
 
-                return DtoService.GetBaseItemDto(item, fields.ToList(), user);
+                return DtoService.GetBaseItemDto(item, dtoOptions, user);
             }
 
-            return DtoService.GetBaseItemDto(item, fields.ToList());
+            return DtoService.GetBaseItemDto(item, dtoOptions);
         }
 
         /// <summary>

+ 21 - 24
MediaBrowser.Api/UserLibrary/UserLibraryService.cs

@@ -407,9 +407,6 @@ namespace MediaBrowser.Api.UserLibrary
         {
             var user = _userManager.GetUserById(request.UserId);
 
-            // Get everything
-            var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)).ToList();
-
             var query = new UserViewQuery
             {
                 UserId = request.UserId
@@ -423,7 +420,9 @@ namespace MediaBrowser.Api.UserLibrary
 
             var folders = await _userViewManager.GetUserViews(query, CancellationToken.None).ConfigureAwait(false);
 
-            var dtos = folders.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
+            var dtoOptions = new DtoOptions();
+
+            var dtos = folders.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user))
                 .ToArray();
 
             var result = new QueryResult<BaseItemDto>
@@ -443,14 +442,16 @@ namespace MediaBrowser.Api.UserLibrary
                 user.RootFolder :
                 _libraryManager.GetItemById(request.Id);
 
-            // Get everything
-            var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)).ToList();
-
             var series = item as Series;
 
             // Get them from the child tree
             if (series != null)
             {
+                var dtoOptions = new DtoOptions();
+
+                // Avoid implicitly captured closure
+                var currentUser = user;
+
                 var dtos = series
                     .GetRecursiveChildren()
                     .Where(i => i is Episode && i.ParentIndexNumber.HasValue && i.ParentIndexNumber.Value == 0)
@@ -468,7 +469,7 @@ namespace MediaBrowser.Api.UserLibrary
                         return DateTime.MinValue;
                     })
                     .ThenBy(i => i.SortName)
-                    .Select(i => _dtoService.GetBaseItemDto(i, fields, user));
+                    .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, currentUser));
 
                 return dtos.ToList();
             }
@@ -478,10 +479,12 @@ namespace MediaBrowser.Api.UserLibrary
             // Get them from the db
             if (movie != null)
             {
+                var dtoOptions = new DtoOptions();
+
                 var dtos = movie.SpecialFeatureIds
                     .Select(_libraryManager.GetItemById)
                     .OrderBy(i => i.SortName)
-                    .Select(i => _dtoService.GetBaseItemDto(i, fields, user, item));
+                    .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item));
 
                 return dtos.ToList();
             }
@@ -507,9 +510,6 @@ namespace MediaBrowser.Api.UserLibrary
 
             var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : _libraryManager.GetItemById(request.Id);
 
-            // Get everything
-            var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)).ToList();
-
             var trailerIds = new List<Guid>();
 
             var hasTrailers = item as IHasTrailers;
@@ -518,10 +518,12 @@ namespace MediaBrowser.Api.UserLibrary
                 trailerIds = hasTrailers.GetTrailerIds();
             }
 
+            var dtoOptions = new DtoOptions();
+
             var dtos = trailerIds
                 .Select(_libraryManager.GetItemById)
                 .OrderBy(i => i.SortName)
-                .Select(i => _dtoService.GetBaseItemDto(i, fields, user, item));
+                .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, item));
 
             return dtos.ToList();
         }
@@ -537,10 +539,9 @@ namespace MediaBrowser.Api.UserLibrary
 
             var item = string.IsNullOrEmpty(request.Id) ? user.RootFolder : _libraryManager.GetItemById(request.Id);
 
-            // Get everything
-            var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)).ToList();
+            var dtoOptions = new DtoOptions();
 
-            var result = _dtoService.GetBaseItemDto(item, fields, user);
+            var result = _dtoService.GetBaseItemDto(item, dtoOptions, user);
 
             return ToOptimizedSerializedResultUsingCache(result);
         }
@@ -556,10 +557,9 @@ namespace MediaBrowser.Api.UserLibrary
 
             var item = user.RootFolder;
 
-            // Get everything
-            var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true)).ToList();
+            var dtoOptions = new DtoOptions();
 
-            var result = _dtoService.GetBaseItemDto(item, fields, user);
+            var result = _dtoService.GetBaseItemDto(item, dtoOptions, user);
 
             return ToOptimizedSerializedResultUsingCache(result);
         }
@@ -577,12 +577,9 @@ namespace MediaBrowser.Api.UserLibrary
 
             var items = await _libraryManager.GetIntros(item, user).ConfigureAwait(false);
 
-            // Get everything
-            var fields = Enum.GetNames(typeof(ItemFields))
-                .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
-                .ToList();
+            var dtoOptions = new DtoOptions();
 
-            var dtos = items.Select(i => _dtoService.GetBaseItemDto(i, fields, user))
+            var dtos = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user))
                 .ToArray();
 
             var result = new ItemsResult

+ 3 - 4
MediaBrowser.Api/UserLibrary/YearsService.cs

@@ -73,17 +73,16 @@ namespace MediaBrowser.Api.UserLibrary
         {
             var item = LibraryManager.GetYear(request.Year);
 
-            // Get everything
-            var fields = Enum.GetNames(typeof(ItemFields)).Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true));
+            var dtoOptions = new DtoOptions();
 
             if (request.UserId.HasValue)
             {
                 var user = UserManager.GetUserById(request.UserId.Value);
 
-                return DtoService.GetBaseItemDto(item, fields.ToList(), user);
+                return DtoService.GetBaseItemDto(item, dtoOptions, user);
             }
 
-            return DtoService.GetBaseItemDto(item, fields.ToList());
+            return DtoService.GetBaseItemDto(item, dtoOptions);
         }
 
         /// <summary>

+ 99 - 51
MediaBrowser.Api/UserService.cs

@@ -1,10 +1,12 @@
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Devices;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Connect;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Users;
@@ -51,7 +53,7 @@ namespace MediaBrowser.Api
         /// </summary>
         /// <value>The id.</value>
         [ApiMember(Name = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
-        public Guid Id { get; set; }
+        public string Id { get; set; }
     }
 
     /// <summary>
@@ -66,7 +68,7 @@ namespace MediaBrowser.Api
         /// </summary>
         /// <value>The id.</value>
         [ApiMember(Name = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "DELETE")]
-        public Guid Id { get; set; }
+        public string Id { get; set; }
     }
 
     /// <summary>
@@ -80,7 +82,7 @@ namespace MediaBrowser.Api
         /// </summary>
         /// <value>The id.</value>
         [ApiMember(Name = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
-        public Guid Id { get; set; }
+        public string Id { get; set; }
 
         /// <summary>
         /// Gets or sets the password.
@@ -125,7 +127,7 @@ namespace MediaBrowser.Api
         /// Gets or sets the id.
         /// </summary>
         /// <value>The id.</value>
-        public Guid Id { get; set; }
+        public string Id { get; set; }
 
         /// <summary>
         /// Gets or sets the password.
@@ -155,6 +157,28 @@ namespace MediaBrowser.Api
     {
     }
 
+    /// <summary>
+    /// Class UpdateUser
+    /// </summary>
+    [Route("/Users/{Id}/Policy", "POST", Summary = "Updates a user policy")]
+    [Authenticated(Roles = "admin")]
+    public class UpdateUserPolicy : UserPolicy, IReturnVoid
+    {
+        [ApiMember(Name = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
+        public string Id { get; set; }
+    }
+
+    /// <summary>
+    /// Class UpdateUser
+    /// </summary>
+    [Route("/Users/{Id}/Configuration", "POST", Summary = "Updates a user configuration")]
+    [Authenticated]
+    public class UpdateUserConfiguration : UserConfiguration, IReturnVoid
+    {
+        [ApiMember(Name = "User Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "POST")]
+        public string Id { get; set; }
+    }
+
     /// <summary>
     /// Class CreateUser
     /// </summary>
@@ -193,22 +217,18 @@ namespace MediaBrowser.Api
         private readonly ISessionManager _sessionMananger;
         private readonly IServerConfigurationManager _config;
         private readonly INetworkManager _networkManager;
+        private readonly IDeviceManager _deviceManager;
 
         public IAuthorizationContext AuthorizationContext { get; set; }
 
-        /// <summary>
-        /// Initializes a new instance of the <see cref="UserService" /> class.
-        /// </summary>
-        /// <param name="userManager">The user manager.</param>
-        /// <param name="dtoService">The dto service.</param>
-        /// <param name="sessionMananger">The session mananger.</param>
-        public UserService(IUserManager userManager, IDtoService dtoService, ISessionManager sessionMananger, IServerConfigurationManager config, INetworkManager networkManager)
+        public UserService(IUserManager userManager, IDtoService dtoService, ISessionManager sessionMananger, IServerConfigurationManager config, INetworkManager networkManager, IDeviceManager deviceManager)
         {
             _userManager = userManager;
             _dtoService = dtoService;
             _sessionMananger = sessionMananger;
             _config = config;
             _networkManager = networkManager;
+            _deviceManager = deviceManager;
         }
 
         public object Get(GetPublicUsers request)
@@ -222,18 +242,12 @@ namespace MediaBrowser.Api
                 });
             }
 
-            // TODO: Uncomment once clients can handle an empty user list (and below)
-            //if (Request.IsLocal || IsInLocalNetwork(Request.RemoteIp))
+            return Get(new GetUsers
             {
-                return Get(new GetUsers
-                {
-                    IsHidden = false,
-                    IsDisabled = false
-                });
-            }
+                IsHidden = false,
+                IsDisabled = false
 
-            //// Return empty when external
-            //return ToOptimizedResult(new List<UserDto>());
+            }, true);
         }
 
         /// <summary>
@@ -242,25 +256,39 @@ namespace MediaBrowser.Api
         /// <param name="request">The request.</param>
         /// <returns>System.Object.</returns>
         public object Get(GetUsers request)
+        {
+            return Get(request, false);
+        }
+
+        private object Get(GetUsers request, bool filterByDevice)
         {
             var users = _userManager.Users;
 
             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)
             {
-
                 users = users.Where(i => (i.ConnectLinkType.HasValue && i.ConnectLinkType.Value == UserLinkType.Guest) == request.IsGuest.Value);
             }
 
+            if (filterByDevice)
+            {
+                var deviceId = AuthorizationContext.GetAuthorizationInfo(Request).DeviceId;
+
+                if (!string.IsNullOrWhiteSpace(deviceId))
+                {
+                    users = users.Where(i => _deviceManager.CanAccessDevice(i.Id.ToString("N"), deviceId));
+                }
+            }
+
             var result = users
                 .OrderBy(u => u.Name)
                 .Select(i => _userManager.GetUserDto(i, Request.RemoteIp))
@@ -428,39 +456,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>
@@ -495,5 +497,51 @@ namespace MediaBrowser.Api
         {
             return _userManager.RedeemPasswordResetPin(request.Pin);
         }
+
+        public void Post(UpdateUserConfiguration request)
+        {
+            var task = _userManager.UpdateConfiguration(request.Id, request);
+
+            Task.WaitAll(task);
+        }
+
+        public void Post(UpdateUserPolicy 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);
+        }
     }
 }

+ 7 - 9
MediaBrowser.Api/VideosService.cs

@@ -5,6 +5,7 @@ using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Querying;
 using ServiceStack;
 using System;
@@ -79,15 +80,12 @@ namespace MediaBrowser.Api
                                   : _libraryManager.RootFolder)
                            : _libraryManager.GetItemById(request.Id);
 
-            // Get everything
-            var fields = Enum.GetNames(typeof(ItemFields))
-                    .Select(i => (ItemFields)Enum.Parse(typeof(ItemFields), i, true))
-                    .ToList();
+            var dtoOptions = new DtoOptions();
 
             var video = (Video)item;
 
             var items = video.GetAdditionalParts()
-                         .Select(i => _dtoService.GetBaseItemDto(i, fields, user, video))
+                         .Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user, video))
                          .ToArray();
 
             var result = new ItemsResult
@@ -114,11 +112,11 @@ namespace MediaBrowser.Api
             {
                 link.PrimaryVersionId = null;
 
-                await link.UpdateToRepository(ItemUpdateType.MetadataDownload, CancellationToken.None).ConfigureAwait(false);
+                await link.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
             }
 
             video.LinkedAlternateVersions.Clear();
-            await video.UpdateToRepository(ItemUpdateType.MetadataDownload, CancellationToken.None).ConfigureAwait(false);
+            await video.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
         }
 
         public void Post(MergeVersions request)
@@ -186,7 +184,7 @@ namespace MediaBrowser.Api
             {
                 item.PrimaryVersionId = primaryVersion.Id;
 
-                await item.UpdateToRepository(ItemUpdateType.MetadataDownload, CancellationToken.None).ConfigureAwait(false);
+                await item.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
 
                 primaryVersion.LinkedAlternateVersions.Add(new LinkedChild
                 {
@@ -195,7 +193,7 @@ namespace MediaBrowser.Api
                 });
             }
 
-            await primaryVersion.UpdateToRepository(ItemUpdateType.MetadataDownload, CancellationToken.None).ConfigureAwait(false);
+            await primaryVersion.UpdateToRepository(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
         }
     }
 }

+ 1 - 1
MediaBrowser.Common.Implementations/BaseApplicationHost.cs

@@ -466,7 +466,7 @@ namespace MediaBrowser.Common.Implementations
 
 			RegisterSingleInstance(FileSystemManager);
 
-			HttpClient = new HttpClientManager.HttpClientManager(ApplicationPaths, Logger, FileSystemManager, ConfigurationManager);
+			HttpClient = new HttpClientManager.HttpClientManager(ApplicationPaths, Logger, FileSystemManager);
 			RegisterSingleInstance(HttpClient);
 
 			NetworkManager = CreateNetworkManager(LogManager.GetLogger("NetworkManager"));

+ 46 - 5
MediaBrowser.Common.Implementations/Configuration/BaseConfigurationManager.cs

@@ -105,7 +105,7 @@ namespace MediaBrowser.Common.Implementations.Configuration
             UpdateCachePath();
         }
 
-        public void AddParts(IEnumerable<IConfigurationFactory> factories)
+        public virtual void AddParts(IEnumerable<IConfigurationFactory> factories)
         {
             _configurationFactories = factories.ToArray();
 
@@ -208,20 +208,51 @@ namespace MediaBrowser.Common.Implementations.Configuration
 
                 lock (_configurationSyncLock)
                 {
-                    return ConfigurationHelper.GetXmlConfiguration(configurationType, file, XmlSerializer);
+                    return LoadConfiguration(file, configurationType);
                 }
             });
         }
 
+        private object LoadConfiguration(string path, Type configurationType)
+        {
+            try
+            {
+                return XmlSerializer.DeserializeFromFile(configurationType, path);
+            }
+            catch (FileNotFoundException)
+            {
+                return Activator.CreateInstance(configurationType);
+            }
+            catch (DirectoryNotFoundException)
+            {
+                return Activator.CreateInstance(configurationType);
+            }
+            catch (Exception ex)
+            {
+                Logger.ErrorException("Error loading configuration file: {0}", ex, path);
+
+                return Activator.CreateInstance(configurationType);
+            }
+        }
+
         public void SaveConfiguration(string key, object configuration)
         {
-            var configurationType = GetConfigurationType(key);
+            var configurationStore = GetConfigurationStore(key);
+            var configurationType = configurationStore.ConfigurationType;
 
             if (configuration.GetType() != configurationType)
             {
                 throw new ArgumentException("Expected configuration type is " + configurationType.Name);
             }
 
+            var validatingStore = configurationStore as IValidatingConfiguration;
+            if (validatingStore != null)
+            {
+                var currentConfiguration = GetConfiguration(key);
+
+                validatingStore.Validate(currentConfiguration, configuration);
+            }
+
             EventHelper.FireEventIfNotNull(NamedConfigurationUpdating, this, new ConfigurationUpdateEventArgs
             {
                 Key = key,
@@ -239,6 +270,11 @@ namespace MediaBrowser.Common.Implementations.Configuration
                 XmlSerializer.SerializeToFile(configuration, path);
             }
 
+            OnNamedConfigurationUpdated(key, configuration);
+        }
+
+        protected virtual void OnNamedConfigurationUpdated(string key, object configuration)
+        {
             EventHelper.FireEventIfNotNull(NamedConfigurationUpdated, this, new ConfigurationUpdateEventArgs
             {
                 Key = key,
@@ -249,9 +285,14 @@ namespace MediaBrowser.Common.Implementations.Configuration
 
         public Type GetConfigurationType(string key)
         {
-            return _configurationStores
-                .First(i => string.Equals(i.Key, key, StringComparison.OrdinalIgnoreCase))
+            return GetConfigurationStore(key)
                 .ConfigurationType;
         }
+
+        private ConfigurationStore GetConfigurationStore(string key)
+        {
+            return _configurationStores
+                .First(i => string.Equals(i.Key, key, StringComparison.OrdinalIgnoreCase));
+        }
     }
 }

+ 1 - 16
MediaBrowser.Common/Configuration/ConfigurationHelper.cs → MediaBrowser.Common.Implementations/Configuration/ConfigurationHelper.cs

@@ -3,7 +3,7 @@ using System;
 using System.IO;
 using System.Linq;
 
-namespace MediaBrowser.Common.Configuration
+namespace MediaBrowser.Common.Implementations.Configuration
 {
     /// <summary>
     /// Class ConfigurationHelper
@@ -55,20 +55,5 @@ namespace MediaBrowser.Common.Configuration
                 return configuration;
             }
         }
-
-        /// <summary>
-        /// Reads an xml configuration file from the file system
-        /// It will immediately save the configuration after loading it, just
-        /// in case there are new serializable properties
-        /// </summary>
-        /// <typeparam name="T"></typeparam>
-        /// <param name="path">The path.</param>
-        /// <param name="xmlSerializer">The XML serializer.</param>
-        /// <returns>``0.</returns>
-        public static T GetXmlConfiguration<T>(string path, IXmlSerializer xmlSerializer)
-            where T : class
-        {
-            return GetXmlConfiguration(typeof(T), path, xmlSerializer) as T;
-        }
     }
 }

+ 4 - 1
MediaBrowser.Common.Implementations/Devices/DeviceId.cs

@@ -38,7 +38,10 @@ namespace MediaBrowser.Common.Implementations.Devices
                     _logger.Error("Invalid value found in device id file");
                 }
             }
-            catch (FileNotFoundException ex)
+            catch (DirectoryNotFoundException)
+            {
+            }
+            catch (FileNotFoundException)
             {
             }
             catch (Exception ex)

+ 1 - 3
MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs

@@ -41,7 +41,6 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
         private readonly IApplicationPaths _appPaths;
 
         private readonly IFileSystem _fileSystem;
-        private readonly IConfigurationManager _config;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="HttpClientManager" /> class.
@@ -52,7 +51,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
         /// <exception cref="System.ArgumentNullException">appPaths
         /// or
         /// logger</exception>
-        public HttpClientManager(IApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem, IConfigurationManager config)
+        public HttpClientManager(IApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem)
         {
             if (appPaths == null)
             {
@@ -65,7 +64,6 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
 
             _logger = logger;
             _fileSystem = fileSystem;
-            _config = config;
             _appPaths = appPaths;
 
             // http://stackoverflow.com/questions/566437/http-post-returns-the-error-417-expectation-failed-c

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

@@ -81,6 +81,7 @@
     <Compile Include="BaseApplicationHost.cs" />
     <Compile Include="BaseApplicationPaths.cs" />
     <Compile Include="Configuration\BaseConfigurationManager.cs" />
+    <Compile Include="Configuration\ConfigurationHelper.cs" />
     <Compile Include="Devices\DeviceId.cs" />
     <Compile Include="HttpClientManager\HttpClientInfo.cs" />
     <Compile Include="HttpClientManager\HttpClientManager.cs" />

+ 4 - 0
MediaBrowser.Common.Implementations/Security/MBLicenseFile.cs

@@ -101,6 +101,10 @@ namespace MediaBrowser.Common.Implementations.Security
                 {
                     contents = File.ReadAllLines(licenseFile);
                 }
+                catch (DirectoryNotFoundException)
+                {
+                    (File.Create(licenseFile)).Close();
+                }
                 catch (FileNotFoundException)
                 {
                     (File.Create(licenseFile)).Close();

+ 14 - 3
MediaBrowser.Common.Implementations/Serialization/XmlSerializer.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Model.Serialization;
 using System;
+using System.Collections.Concurrent;
 using System.IO;
 using System.Xml;
 
@@ -10,6 +11,17 @@ namespace MediaBrowser.Common.Implementations.Serialization
     /// </summary>
     public class XmlSerializer : IXmlSerializer
     {
+        // Need to cache these
+        // http://dotnetcodebox.blogspot.com/2013/01/xmlserializer-class-may-result-in.html
+        private readonly ConcurrentDictionary<string, System.Xml.Serialization.XmlSerializer> _serializers =
+            new ConcurrentDictionary<string, System.Xml.Serialization.XmlSerializer>();
+
+        private System.Xml.Serialization.XmlSerializer GetSerializer(Type type)
+        {
+            var key = type.FullName;
+            return _serializers.GetOrAdd(key, k => new System.Xml.Serialization.XmlSerializer(type));
+        }
+
         /// <summary>
         /// Serializes to writer.
         /// </summary>
@@ -18,7 +30,7 @@ namespace MediaBrowser.Common.Implementations.Serialization
         private void SerializeToWriter(object obj, XmlTextWriter writer)
         {
             writer.Formatting = Formatting.Indented;
-            var netSerializer = new System.Xml.Serialization.XmlSerializer(obj.GetType());
+            var netSerializer = GetSerializer(obj.GetType());
             netSerializer.Serialize(writer, obj);
         }
 
@@ -32,8 +44,7 @@ namespace MediaBrowser.Common.Implementations.Serialization
         {
             using (var reader = new XmlTextReader(stream))
             {
-                var netSerializer = new System.Xml.Serialization.XmlSerializer(type);
-
+                var netSerializer = GetSerializer(type);
                 return netSerializer.Deserialize(reader);
             }
         }

+ 0 - 1
MediaBrowser.Common.Implementations/packages.config

@@ -1,6 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
 <packages>
   <package id="NLog" version="3.1.0.0" targetFramework="net45" />
-  <package id="SharpCompress" version="0.10.2" targetFramework="net45" />
   <package id="SimpleInjector" version="2.6.1" targetFramework="net45" />
 </packages>

+ 5 - 0
MediaBrowser.Common/Configuration/IConfigurationFactory.cs

@@ -14,4 +14,9 @@ namespace MediaBrowser.Common.Configuration
 
         public Type ConfigurationType { get; set; }
     }
+
+    public interface IValidatingConfiguration
+    {
+        void Validate(object oldConfig, object newConfig);
+    }
 }

+ 11 - 0
MediaBrowser.Common/Extensions/BaseExtensions.cs

@@ -1,4 +1,6 @@
 using System;
+using System.Globalization;
+using System.Linq;
 using System.Security.Cryptography;
 using System.Text;
 using System.Text.RegularExpressions;
@@ -54,6 +56,15 @@ namespace MediaBrowser.Common.Extensions
             return sb.ToString();
         }
 
+        public static string RemoveDiacritics(this string text)
+        {
+            return String.Concat(
+                text.Normalize(NormalizationForm.FormD)
+                .Where(ch => CharUnicodeInfo.GetUnicodeCategory(ch) !=
+                                              UnicodeCategory.NonSpacingMark)
+              ).Normalize(NormalizationForm.FormC);
+        }
+
         /// <summary>
         /// Gets the M d5.
         /// </summary>

+ 0 - 8
MediaBrowser.Common/MediaBrowser.Common.csproj

@@ -53,7 +53,6 @@
     <Compile Include="..\SharedVersion.cs">
       <Link>Properties\SharedVersion.cs</Link>
     </Compile>
-    <Compile Include="Configuration\ConfigurationHelper.cs" />
     <Compile Include="Configuration\ConfigurationUpdateEventArgs.cs" />
     <Compile Include="Configuration\IConfigurationManager.cs" />
     <Compile Include="Configuration\IConfigurationFactory.cs" />
@@ -64,19 +63,12 @@
     <Compile Include="IO\IFileSystem.cs" />
     <Compile Include="IO\ProgressStream.cs" />
     <Compile Include="IO\StreamDefaults.cs" />
-    <Compile Include="Net\BasePeriodicWebSocketListener.cs" />
     <Compile Include="Configuration\IApplicationPaths.cs" />
     <Compile Include="Net\HttpRequestOptions.cs" />
     <Compile Include="Net\HttpResponseInfo.cs" />
-    <Compile Include="Net\IWebSocketListener.cs" />
     <Compile Include="IApplicationHost.cs" />
     <Compile Include="Net\IHttpClient.cs" />
     <Compile Include="Net\INetworkManager.cs" />
-    <Compile Include="Net\IWebSocket.cs" />
-    <Compile Include="Net\IWebSocketConnection.cs" />
-    <Compile Include="Net\MimeTypes.cs" />
-    <Compile Include="Net\WebSocketConnectEventArgs.cs" />
-    <Compile Include="Net\WebSocketMessageInfo.cs" />
     <Compile Include="Plugins\IDependencyModule.cs" />
     <Compile Include="Plugins\IPlugin.cs" />
     <Compile Include="Progress\ActionableProgress.cs" />

+ 32 - 11
MediaBrowser.Common/Plugins/BasePlugin.cs

@@ -5,7 +5,6 @@ using System;
 using System.IO;
 using System.Reflection;
 using System.Runtime.InteropServices;
-using System.Threading;
 
 namespace MediaBrowser.Common.Plugins
 {
@@ -164,11 +163,7 @@ namespace MediaBrowser.Common.Plugins
         /// <summary>
         /// The _configuration sync lock
         /// </summary>
-        private object _configurationSyncLock = new object();
-        /// <summary>
-        /// The _configuration initialized
-        /// </summary>
-        private bool _configurationInitialized;
+        private readonly object _configurationSyncLock = new object();
         /// <summary>
         /// The _configuration
         /// </summary>
@@ -182,17 +177,43 @@ namespace MediaBrowser.Common.Plugins
             get
             {
                 // Lazy load
-                LazyInitializer.EnsureInitialized(ref _configuration, ref _configurationInitialized, ref _configurationSyncLock, () => ConfigurationHelper.GetXmlConfiguration(ConfigurationType, ConfigurationFilePath, XmlSerializer) as TConfigurationType);
+                if (_configuration == null)
+                {
+                    lock (_configurationSyncLock)
+                    {
+                        if (_configuration == null)
+                        {
+                            _configuration = LoadConfiguration();
+                        }
+                    }
+                } 
                 return _configuration;
             }
             protected set
             {
                 _configuration = value;
+            }
+        }
 
-                if (value == null)
-                {
-                    _configurationInitialized = false;
-                }
+        private TConfigurationType LoadConfiguration()
+        {
+            var path = ConfigurationFilePath;
+
+            try
+            {
+                return (TConfigurationType)XmlSerializer.DeserializeFromFile(typeof(TConfigurationType), path);
+            }
+            catch (DirectoryNotFoundException)
+            {
+                return (TConfigurationType)Activator.CreateInstance(typeof(TConfigurationType));
+            }
+            catch (FileNotFoundException)
+            {
+                return (TConfigurationType)Activator.CreateInstance(typeof(TConfigurationType));
+            }
+            catch (Exception ex)
+            {
+                return (TConfigurationType)Activator.CreateInstance(typeof(TConfigurationType));
             }
         }
 

+ 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);
         }

+ 8 - 0
MediaBrowser.Controller/Devices/IDeviceManager.cs

@@ -85,5 +85,13 @@ namespace MediaBrowser.Controller.Devices
         /// <param name="file">The file.</param>
         /// <returns>Task.</returns>
         Task AcceptCameraUpload(string deviceId, Stream stream, LocalFileInfo file);
+
+        /// <summary>
+        /// Determines whether this instance [can access device] the specified user identifier.
+        /// </summary>
+        /// <param name="userId">The user identifier.</param>
+        /// <param name="deviceId">The device identifier.</param>
+        /// <returns><c>true</c> if this instance [can access device] the specified user identifier; otherwise, <c>false</c>.</returns>
+        bool CanAccessDevice(string userId, string deviceId);
     }
 }

+ 17 - 2
MediaBrowser.Model/Dto/DtoOptions.cs → MediaBrowser.Controller/Dto/DtoOptions.cs

@@ -1,11 +1,18 @@
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Querying;
+using System;
 using System.Collections.Generic;
+using System.Linq;
 
-namespace MediaBrowser.Model.Dto
+namespace MediaBrowser.Controller.Dto
 {
     public class DtoOptions
     {
+        private static readonly List<ItemFields> DefaultExcludedFields = new List<ItemFields>
+        {
+            ItemFields.SeasonUserData
+        };
+
         public List<ItemFields> Fields { get; set; }
         public List<ImageType> ImageTypes { get; set; }
         public int ImageTypeLimit { get; set; }
@@ -14,9 +21,17 @@ namespace MediaBrowser.Model.Dto
         public DtoOptions()
         {
             Fields = new List<ItemFields>();
-            ImageTypes = new List<ImageType>();
             ImageTypeLimit = int.MaxValue;
             EnableImages = true;
+
+            Fields = Enum.GetNames(typeof (ItemFields))
+                    .Select(i => (ItemFields) Enum.Parse(typeof (ItemFields), i, true))
+                    .Except(DefaultExcludedFields)
+                    .ToList();
+
+            ImageTypes = Enum.GetNames(typeof(ImageType))
+                .Select(i => (ImageType)Enum.Parse(typeof(ImageType), i, true))
+                .ToList();
         }
 
         public int GetImageLimit(ImageType type)

+ 17 - 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
 {
@@ -88,6 +89,21 @@ namespace MediaBrowser.Controller.Entities.Audio
             }
         }
 
+        [IgnoreDataMember]
+        public bool IsArchive
+        {
+            get
+            {
+                if (string.IsNullOrWhiteSpace(Path))
+                {
+                    return false;
+                }
+                var ext = System.IO.Path.GetExtension(Path) ?? string.Empty;
+
+                return new[] { ".zip", ".rar", ".7z" }.Contains(ext, StringComparer.OrdinalIgnoreCase);
+            }
+        }
+
         /// <summary>
         /// Gets or sets the artist.
         /// </summary>
@@ -173,7 +189,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);
         }

+ 12 - 25
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);
         }
@@ -135,7 +136,7 @@ namespace MediaBrowser.Controller.Entities.Audio
             // Refresh songs
             foreach (var item in songs)
             {
-                if (tasks.Count >= 3)
+                if (tasks.Count >= 2)
                 {
                     await Task.WhenAll(tasks).ConfigureAwait(false);
                     tasks.Clear();
@@ -172,37 +173,23 @@ namespace MediaBrowser.Controller.Entities.Audio
             // Refresh all non-songs
             foreach (var item in others)
             {
-                if (tasks.Count >= 3)
-                {
-                    await Task.WhenAll(tasks).ConfigureAwait(false);
-                    tasks.Clear();
-                }
-
                 cancellationToken.ThrowIfCancellationRequested();
-                var innerProgress = new ActionableProgress<double>();
 
                 // Avoid implicitly captured closure
                 var currentChild = item;
-                innerProgress.RegisterAction(p =>
-                {
-                    lock (percentages)
-                    {
-                        percentages[currentChild.Id] = p / 100;
 
-                        var percent = percentages.Values.Sum();
-                        percent /= totalItems;
-                        percent *= 100;
-                        progress.Report(percent);
-                    }
-                });
+                await item.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
+                lock (percentages)
+                {
+                    percentages[currentChild.Id] = 1;
 
-                // Avoid implicitly captured closure
-                var taskChild = item;
-                tasks.Add(Task.Run(async () => await RefreshItem(taskChild, refreshOptions, innerProgress, cancellationToken).ConfigureAwait(false), cancellationToken));
+                    var percent = percentages.Values.Sum();
+                    percent /= totalItems;
+                    percent *= 100;
+                    progress.Report(percent);
+                }
             }
 
-            await Task.WhenAll(tasks).ConfigureAwait(false);
-            
             progress.Report(100);
         }
 

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

@@ -14,6 +14,7 @@ using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Library;
 using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Users;
 using System;
 using System.Collections.Generic;
 using System.IO;
@@ -45,6 +46,8 @@ namespace MediaBrowser.Controller.Entities
         /// </summary>
         public static readonly string[] SupportedImageExtensions = { ".png", ".jpg", ".jpeg", ".tbn" };
 
+        public static readonly List<string> SupportedImageExtensionsList = SupportedImageExtensions.ToList();
+
         /// <summary>
         /// The trailer folder name
         /// </summary>
@@ -593,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;
             }
@@ -985,7 +988,7 @@ namespace MediaBrowser.Controller.Entities
                 return false;
             }
 
-            var maxAllowedRating = user.Configuration.MaxParentalRating;
+            var maxAllowedRating = user.Policy.MaxParentalRating;
 
             if (maxAllowedRating == null)
             {
@@ -1001,7 +1004,7 @@ namespace MediaBrowser.Controller.Entities
 
             if (string.IsNullOrWhiteSpace(rating))
             {
-                return !GetBlockUnratedValue(user.Configuration);
+                return !GetBlockUnratedValue(user.Policy);
             }
 
             var value = LocalizationManager.GetRatingLevel(rating);
@@ -1021,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;
                 }
@@ -1035,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);
         }
@@ -1574,6 +1577,11 @@ namespace MediaBrowser.Controller.Entities
 
             foreach (var newImage in images)
             {
+                if (newImage == null)
+                {
+                    throw new ArgumentException("null image found in list");
+                }
+
                 var existing = existingImages
                     .FirstOrDefault(i => string.Equals(i.Path, newImage.FullName, StringComparison.OrdinalIgnoreCase));
 

+ 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);
         }

+ 16 - 0
MediaBrowser.Controller/Entities/CollectionFolder.cs

@@ -66,6 +66,22 @@ namespace MediaBrowser.Controller.Entities
             return CreateResolveArgs(directoryService).FileSystemChildren;
         }
 
+        internal override bool IsValidFromResolver(BaseItem newItem)
+        {
+            var newCollectionFolder = newItem as CollectionFolder;
+
+            if (newCollectionFolder != null)
+            {
+                if (!string.Equals(CollectionType, newCollectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase))
+                {
+                    return false;
+                }
+            }
+
+
+            return base.IsValidFromResolver(newItem);
+        }
+
         private ItemResolveArgs CreateResolveArgs(IDirectoryService directoryService)
         {
             var path = ContainingFolderPath;

+ 4 - 4
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;
                 }
@@ -545,7 +545,7 @@ namespace MediaBrowser.Controller.Entities
 
             foreach (var child in children)
             {
-                if (tasks.Count >= 3)
+                if (tasks.Count >= 2)
                 {
                     await Task.WhenAll(tasks).ConfigureAwait(false);
                     tasks.Clear();
@@ -708,7 +708,7 @@ namespace MediaBrowser.Controller.Entities
         /// <returns>IEnumerable{BaseItem}.</returns>
         protected virtual IEnumerable<BaseItem> GetNonCachedChildren(IDirectoryService directoryService)
         {
-            var collectionType = LibraryManager.FindCollectionType(this);
+            var collectionType = LibraryManager.GetContentType(this);
 
             return LibraryManager.ResolvePaths(GetFileSystemChildren(directoryService), directoryService, this, collectionType);
         }

+ 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 - 2
MediaBrowser.Controller/Entities/IHasMediaSources.cs

@@ -49,8 +49,8 @@ namespace MediaBrowser.Controller.Entities
             : new[] { user.Configuration.AudioLanguagePreference };
 
             var preferredSubs = string.IsNullOrEmpty(user.Configuration.SubtitleLanguagePreference)
-                ? new string[] { }
-                : new[] { user.Configuration.SubtitleLanguagePreference };
+                ? new List<string> { }
+                : new List<string> { user.Configuration.SubtitleLanguagePreference };
 
             foreach (var source in sources)
             {

+ 9 - 5
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
 {
@@ -18,7 +19,7 @@ namespace MediaBrowser.Controller.Entities.Movies
     public class BoxSet : Folder, IHasTrailers, IHasKeywords, IHasPreferredMetadataLanguage, IHasDisplayOrder, IHasLookupInfo<BoxSetInfo>, IMetadataContainer, IHasShares
     {
         public List<Share> Shares { get; set; }
-        
+
         public BoxSet()
         {
             RemoteTrailers = new List<MediaUrl>();
@@ -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);
         }
@@ -170,10 +171,13 @@ namespace MediaBrowser.Controller.Entities.Movies
             {
                 var userId = user.Id.ToString("N");
 
-                return Shares.Any(i => string.Equals(userId, i.UserId, StringComparison.OrdinalIgnoreCase)) ||
+                // Need to check Count > 0 for boxsets created prior to the introduction of Shares
+                if (Shares.Count > 0 && !Shares.Any(i => string.Equals(userId, i.UserId, StringComparison.OrdinalIgnoreCase)))
+                {
+                    //return false;
+                }
 
-                    // Need to support this for boxsets created prior to the creation of Shares
-                    Shares.Count == 0;
+                return true;
             }
 
             return false;

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

@@ -1,6 +1,7 @@
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Users;
 using System;
 using System.Collections.Generic;
 using System.IO;
@@ -146,14 +147,21 @@ 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);
         }
 
         public MovieInfo GetLookupInfo()
         {
-            return GetItemLookupInfo<MovieInfo>();
+            var info = GetItemLookupInfo<MovieInfo>();
+
+            if (!IsInMixedFolder)
+            {
+                info.Name = System.IO.Path.GetFileName(ContainingFolderPath);
+            }
+
+            return info;
         }
 
         public override bool BeforeMetadataRefresh()

+ 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);
         }

+ 14 - 47
MediaBrowser.Controller/Entities/TV/Episode.cs

@@ -1,7 +1,7 @@
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Providers;
+using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Users;
 using System;
 using System.Collections.Generic;
 using System.Linq;
@@ -178,6 +178,15 @@ namespace MediaBrowser.Controller.Entities.TV
             }
         }
 
+        [IgnoreDataMember]
+        public bool IsInSeasonFolder
+        {
+            get
+            {
+                return FindParent<Season>() != null;
+            }
+        }
+
         [IgnoreDataMember]
         public string SeriesName
         {
@@ -275,7 +284,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);
         }
@@ -301,51 +310,9 @@ namespace MediaBrowser.Controller.Entities.TV
         {
             var hasChanges = base.BeforeMetadataRefresh();
 
-            var locationType = LocationType;
-            if (locationType == LocationType.FileSystem || locationType == LocationType.Offline)
-            {
-                if (!IndexNumber.HasValue && !string.IsNullOrEmpty(Path))
-                {
-                    IndexNumber = LibraryManager.GetEpisodeNumberFromFile(Path, true);
-
-                    // If a change was made record it
-                    if (IndexNumber.HasValue)
-                    {
-                        hasChanges = true;
-                    }
-                }
-
-                if (!IndexNumberEnd.HasValue && !string.IsNullOrEmpty(Path))
-                {
-                    IndexNumberEnd = LibraryManager.GetEndingEpisodeNumberFromFile(Path);
-
-                    // If a change was made record it
-                    if (IndexNumberEnd.HasValue)
-                    {
-                        hasChanges = true;
-                    }
-                }
-            }
-
-            if (!ParentIndexNumber.HasValue)
+            if (LibraryManager.FillMissingEpisodeNumbersFromPath(this))
             {
-                var season = Season;
-
-                if (season != null)
-                {
-                    ParentIndexNumber = season.IndexNumber;
-                }
-
-                if (!ParentIndexNumber.HasValue && !string.IsNullOrEmpty(Path))
-                {
-                    ParentIndexNumber = LibraryManager.GetSeasonNumberFromEpisodeFile(Path);
-                }
-
-                // If a change was made record it
-                if (ParentIndexNumber.HasValue)
-                {
-                    hasChanges = true;
-                }
+                hasChanges = true;
             }
 
             return hasChanges;

+ 52 - 27
MediaBrowser.Controller/Entities/TV/Season.cs

@@ -1,9 +1,9 @@
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Localization;
+using MediaBrowser.Controller.Localization;
 using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Querying;
+using MediaBrowser.Model.Users;
+using MoreLinq;
 using System.Collections.Generic;
 using System.Linq;
 using System.Runtime.Serialization;
@@ -155,24 +155,6 @@ namespace MediaBrowser.Controller.Entities.TV
             return IndexNumber != null ? IndexNumber.Value.ToString("0000") : Name;
         }
 
-        private IEnumerable<Episode> GetEpisodes()
-        {
-            var series = Series;
-
-            if (series != null && series.ContainsEpisodesWithoutSeasonFolders)
-            {
-                var seasonNumber = IndexNumber;
-
-                if (seasonNumber.HasValue)
-                {
-                    return series.RecursiveChildren.OfType<Episode>()
-                        .Where(i => i.ParentIndexNumber.HasValue && i.ParentIndexNumber.Value == seasonNumber.Value);
-                }
-            }
-
-            return Children.OfType<Episode>();
-        }
-
         [IgnoreDataMember]
         public bool IsMissingSeason
         {
@@ -220,16 +202,32 @@ namespace MediaBrowser.Controller.Entities.TV
             var episodes = GetRecursiveChildren(user)
                 .OfType<Episode>();
 
-            if (IndexNumber.HasValue)
+            var series = Series;
+
+            if (IndexNumber.HasValue && series != null)
             {
-                var series = Series;
+                return series.GetEpisodes(user, IndexNumber.Value, includeMissingEpisodes, includeVirtualUnairedEpisodes, episodes);
+            }
 
-                if (series != null)
+            if (series != null && series.ContainsEpisodesWithoutSeasonFolders)
+            {
+                var seasonNumber = IndexNumber;
+                var list = episodes.ToList();
+
+                if (seasonNumber.HasValue)
                 {
-                    return series.GetEpisodes(user, IndexNumber.Value, includeMissingEpisodes, includeVirtualUnairedEpisodes, episodes);
+                    list.AddRange(series.GetRecursiveChildren(user).OfType<Episode>()
+                        .Where(i => i.ParentIndexNumber.HasValue && i.ParentIndexNumber.Value == seasonNumber.Value));
+                }
+                else
+                {
+                    list.AddRange(series.GetRecursiveChildren(user).OfType<Episode>()
+                        .Where(i => !i.ParentIndexNumber.HasValue));
                 }
-            }
 
+                episodes = list.DistinctBy(i => i.Id);
+            }
+            
             if (!includeMissingEpisodes)
             {
                 episodes = episodes.Where(i => !i.IsMissingEpisode);
@@ -244,12 +242,39 @@ namespace MediaBrowser.Controller.Entities.TV
                 .Cast<Episode>();
         }
 
+        private IEnumerable<Episode> GetEpisodes()
+        {
+            var episodes = RecursiveChildren.OfType<Episode>();
+            var series = Series;
+
+            if (series != null && series.ContainsEpisodesWithoutSeasonFolders)
+            {
+                var seasonNumber = IndexNumber;
+                var list = episodes.ToList();
+
+                if (seasonNumber.HasValue)
+                {
+                    list.AddRange(series.RecursiveChildren.OfType<Episode>()
+                        .Where(i => i.ParentIndexNumber.HasValue && i.ParentIndexNumber.Value == seasonNumber.Value));
+                }
+                else
+                {
+                    list.AddRange(series.RecursiveChildren.OfType<Episode>()
+                        .Where(i => !i.ParentIndexNumber.HasValue));
+                }
+
+                episodes = list.DistinctBy(i => i.Id);
+            }
+
+            return episodes;
+        }
+
         public override IEnumerable<BaseItem> GetChildren(User user, bool includeLinkedChildren)
         {
             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;

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

@@ -3,6 +3,7 @@ using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Querying;
+using MediaBrowser.Model.Users;
 using System;
 using System.Collections.Generic;
 using System.Linq;
@@ -87,7 +88,17 @@ namespace MediaBrowser.Controller.Entities.TV
         /// Gets or sets the date last episode added.
         /// </summary>
         /// <value>The date last episode added.</value>
-        public DateTime DateLastEpisodeAdded { get; set; }
+        [IgnoreDataMember]
+        public DateTime DateLastEpisodeAdded
+        {
+            get
+            {
+                return RecursiveChildren.OfType<Episode>()
+                        .Select(i => i.DateCreated)
+                        .OrderByDescending(i => i)
+                        .FirstOrDefault();
+            }
+        }
 
         /// <summary>
         /// Series aren't included directly in indices - Their Episodes will roll up to them
@@ -246,7 +257,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;

+ 16 - 1
MediaBrowser.Controller/Entities/Video.cs

@@ -91,6 +91,21 @@ namespace MediaBrowser.Controller.Entities
             get { return LocalAlternateVersions.Count > 0; }
         }
 
+        [IgnoreDataMember]
+        public bool IsArchive
+        {
+            get
+            {
+                if (string.IsNullOrWhiteSpace(Path))
+                {
+                    return false;
+                }
+                var ext = System.IO.Path.GetExtension(Path) ?? string.Empty;
+
+                return new[] { ".zip", ".rar", ".7z" }.Contains(ext, StringComparer.OrdinalIgnoreCase);
+            }
+        }
+
         public IEnumerable<Guid> GetAdditionalPartIds()
         {
             return AdditionalParts.Select(i => LibraryManager.GetNewItemId(i, typeof(Video)));
@@ -246,7 +261,7 @@ namespace MediaBrowser.Controller.Entities
                     {
                         return System.IO.Path.GetFileName(Path);
                     }
-                    
+
                     return System.IO.Path.GetFileNameWithoutExtension(Path);
                 }
 

+ 14 - 23
MediaBrowser.Controller/Library/ILibraryManager.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Resolvers;
 using MediaBrowser.Controller.Sorting;
@@ -22,11 +23,9 @@ namespace MediaBrowser.Controller.Library
         /// </summary>
         /// <param name="fileInfo">The file information.</param>
         /// <param name="parent">The parent.</param>
-        /// <param name="collectionType">Type of the collection.</param>
         /// <returns>BaseItem.</returns>
         BaseItem ResolvePath(FileSystemInfo fileInfo, 
-            Folder parent = null, 
-            string collectionType = null);
+            Folder parent = null);
 
         /// <summary>
         /// Resolves a set of files into a list of BaseItem
@@ -258,8 +257,15 @@ namespace MediaBrowser.Controller.Library
         /// </summary>
         /// <param name="item">The item.</param>
         /// <returns>System.String.</returns>
-        string FindCollectionType(BaseItem item);
+        string GetContentType(BaseItem item);
 
+        /// <summary>
+        /// Gets the type of the inherited content.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <returns>System.String.</returns>
+        string GetInheritedContentType(BaseItem item);
+        
         /// <summary>
         /// Normalizes the root path list.
         /// </summary>
@@ -340,26 +346,11 @@ namespace MediaBrowser.Controller.Library
         int? GetSeasonNumberFromPath(string path);
 
         /// <summary>
-        /// Gets the season number from episode file.
-        /// </summary>
-        /// <param name="path">The path.</param>
-        /// <returns>System.Nullable&lt;System.Int32&gt;.</returns>
-        int? GetSeasonNumberFromEpisodeFile(string path);
-
-        /// <summary>
-        /// Gets the ending episode number from file.
+        /// Fills the missing episode numbers from path.
         /// </summary>
-        /// <param name="path">The path.</param>
-        /// <returns>System.Nullable&lt;System.Int32&gt;.</returns>
-        int? GetEndingEpisodeNumberFromFile(string path);
-
-        /// <summary>
-        /// Gets the episode number from file.
-        /// </summary>
-        /// <param name="path">The path.</param>
-        /// <param name="considerSeasonless">if set to <c>true</c> [consider seasonless].</param>
-        /// <returns>System.Nullable&lt;System.Int32&gt;.</returns>
-        int? GetEpisodeNumberFromFile(string path, bool considerSeasonless);
+        /// <param name="episode">The episode.</param>
+        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
+        bool FillMissingEpisodeNumbersFromPath(Episode episode);
 
         /// <summary>
         /// Parses the name.

+ 4 - 0
MediaBrowser.Controller/Library/IUserDataManager.cs

@@ -61,5 +61,9 @@ namespace MediaBrowser.Controller.Library
         /// <returns></returns>
         Task SaveAllUserData(Guid userId, IEnumerable<UserItemData> userData, CancellationToken cancellationToken);
 
+        /// <summary>
+        /// Updates playstate for an item and returns true or false indicating if it was played to completion
+        /// </summary>
+        bool UpdatePlayState(BaseItem item, UserItemData data, long positionTicks);
     }
 }

+ 22 - 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,11 +165,33 @@ 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>
         /// <param name="userId">The user identifier.</param>
         /// <param name="userPolicy">The user policy.</param>
         Task UpdateUserPolicy(string userId, UserPolicy userPolicy);
+
+        /// <summary>
+        /// Makes the valid username.
+        /// </summary>
+        /// <param name="username">The username.</param>
+        /// <returns>System.String.</returns>
+        string MakeValidUsername(string username);
     }
 }

+ 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;

+ 8 - 3
MediaBrowser.Controller/MediaBrowser.Controller.csproj

@@ -115,6 +115,7 @@
     <Compile Include="Drawing\ImageProcessingOptions.cs" />
     <Compile Include="Drawing\ImageProcessorExtensions.cs" />
     <Compile Include="Drawing\ImageStream.cs" />
+    <Compile Include="Dto\DtoOptions.cs" />
     <Compile Include="Dto\IDtoService.cs" />
     <Compile Include="Entities\AdultVideo.cs" />
     <Compile Include="Entities\Audio\IHasAlbumArtist.cs" />
@@ -198,18 +199,17 @@
     <Compile Include="LiveTv\SeriesTimerInfo.cs" />
     <Compile Include="LiveTv\TimerInfo.cs" />
     <Compile Include="Localization\ILocalizationManager.cs" />
-    <Compile Include="MediaEncoding\EncodingOptions.cs" />
     <Compile Include="MediaEncoding\ChapterImageRefreshOptions.cs" />
-    <Compile Include="MediaEncoding\EncodingResult.cs" />
+    <Compile Include="MediaEncoding\EncodingJobOptions.cs" />
     <Compile Include="MediaEncoding\IEncodingManager.cs" />
     <Compile Include="MediaEncoding\ImageEncodingOptions.cs" />
     <Compile Include="MediaEncoding\IMediaEncoder.cs" />
     <Compile Include="MediaEncoding\InternalMediaInfoResult.cs" />
     <Compile Include="MediaEncoding\ISubtitleEncoder.cs" />
     <Compile Include="MediaEncoding\MediaStreamSelector.cs" />
-    <Compile Include="MediaEncoding\VideoEncodingOptions.cs" />
     <Compile Include="Net\AuthenticatedAttribute.cs" />
     <Compile Include="Net\AuthorizationInfo.cs" />
+    <Compile Include="Net\BasePeriodicWebSocketListener.cs" />
     <Compile Include="Net\IAuthorizationContext.cs" />
     <Compile Include="Net\IAuthService.cs" />
     <Compile Include="Net\IHasAuthorization.cs" />
@@ -221,10 +221,15 @@
     <Compile Include="Net\IServerManager.cs" />
     <Compile Include="Net\IServiceRequest.cs" />
     <Compile Include="Net\ISessionContext.cs" />
+    <Compile Include="Net\IWebSocket.cs" />
+    <Compile Include="Net\IWebSocketConnection.cs" />
+    <Compile Include="Net\IWebSocketListener.cs" />
     <Compile Include="Net\LoggedAttribute.cs" />
     <Compile Include="Net\SecurityException.cs" />
     <Compile Include="Net\ServiceStackServiceRequest.cs" />
     <Compile Include="Net\StaticResultOptions.cs" />
+    <Compile Include="Net\WebSocketConnectEventArgs.cs" />
+    <Compile Include="Net\WebSocketMessageInfo.cs" />
     <Compile Include="News\INewsService.cs" />
     <Compile Include="Notifications\INotificationManager.cs" />
     <Compile Include="Notifications\INotificationService.cs" />

+ 91 - 0
MediaBrowser.Controller/MediaEncoding/EncodingJobOptions.cs

@@ -0,0 +1,91 @@
+using MediaBrowser.Model.Dlna;
+
+namespace MediaBrowser.Controller.MediaEncoding
+{
+    public class EncodingJobOptions
+    {
+        public string OutputContainer { get; set; }
+
+        public long? StartTimeTicks { get; set; }
+        public int? Width { get; set; }
+        public int? Height { get; set; }
+        public int? MaxWidth { get; set; }
+        public int? MaxHeight { get; set; }
+        public bool Static = false;
+        public float? Framerate { get; set; }
+        public float? MaxFramerate { get; set; }
+        public string Profile { get; set; }
+        public int? Level { get; set; }
+
+        public string DeviceId { get; set; }
+        public string ItemId { get; set; }
+        public string MediaSourceId { get; set; }
+        public string AudioCodec { get; set; }
+
+        public bool EnableAutoStreamCopy { get; set; }
+
+        public int? MaxAudioChannels { get; set; }
+        public int? AudioChannels { get; set; }
+        public int? AudioBitRate { get; set; }
+        public int? AudioSampleRate { get; set; }
+   
+        public DeviceProfile DeviceProfile { get; set; }
+        public EncodingContext Context { get; set; }
+
+        public string VideoCodec { get; set; }
+
+        public int? VideoBitRate { get; set; }
+        public int? AudioStreamIndex { get; set; }
+        public int? VideoStreamIndex { get; set; }
+        public int? SubtitleStreamIndex { get; set; }
+        public int? MaxRefFrames { get; set; }
+        public int? MaxVideoBitDepth { get; set; }
+        public SubtitleDeliveryMethod SubtitleMethod { get; set; }
+
+        /// <summary>
+        /// Gets a value indicating whether this instance has fixed resolution.
+        /// </summary>
+        /// <value><c>true</c> if this instance has fixed resolution; otherwise, <c>false</c>.</value>
+        public bool HasFixedResolution
+        {
+            get
+            {
+                return Width.HasValue || Height.HasValue;
+            }
+        }
+
+        public bool? Cabac { get; set; }
+
+        public EncodingJobOptions()
+        {
+            
+        }
+
+        public EncodingJobOptions(StreamInfo info, DeviceProfile deviceProfile)
+        {
+            OutputContainer = info.Container;
+            StartTimeTicks = info.StartPositionTicks;
+            MaxWidth = info.MaxWidth;
+            MaxHeight = info.MaxHeight;
+            MaxFramerate = info.MaxFramerate;
+            Profile = info.VideoProfile;
+            Level = info.VideoLevel;
+            ItemId = info.ItemId;
+            MediaSourceId = info.MediaSourceId;
+            AudioCodec = info.AudioCodec;
+            MaxAudioChannels = info.MaxAudioChannels;
+            AudioBitRate = info.AudioBitrate;
+            AudioSampleRate = info.TargetAudioSampleRate;
+            DeviceProfile = deviceProfile;
+            VideoCodec = info.VideoCodec;
+            VideoBitRate = info.VideoBitrate;
+            AudioStreamIndex = info.AudioStreamIndex;
+            SubtitleStreamIndex = info.SubtitleStreamIndex;
+            MaxRefFrames = info.MaxRefFrames;
+            MaxVideoBitDepth = info.MaxVideoBitDepth;
+            SubtitleMethod = info.SubtitleDeliveryMethod;
+            Cabac = info.Cabac;
+            Context = info.Context;
+        }
+    }
+}

+ 0 - 80
MediaBrowser.Controller/MediaEncoding/EncodingOptions.cs

@@ -1,80 +0,0 @@
-using MediaBrowser.Controller.Dlna;
-using MediaBrowser.Model.Dlna;
-
-namespace MediaBrowser.Controller.MediaEncoding
-{
-    public class EncodingOptions
-    {
-        /// <summary>
-        /// Gets or sets the item identifier.
-        /// </summary>
-        /// <value>The item identifier.</value>
-        public string ItemId { get; set; }
-
-        /// <summary>
-        /// Gets or sets the media source identifier.
-        /// </summary>
-        /// <value>The media source identifier.</value>
-        public string MediaSourceId { get; set; }
-
-        /// <summary>
-        /// Gets or sets the device profile.
-        /// </summary>
-        /// <value>The device profile.</value>
-        public DeviceProfile DeviceProfile { get; set; }
-        
-        /// <summary>
-        /// Gets or sets the output path.
-        /// </summary>
-        /// <value>The output path.</value>
-        public string OutputPath { get; set; }
-
-        /// <summary>
-        /// Gets or sets the container.
-        /// </summary>
-        /// <value>The container.</value>
-        public string Container { get; set; }
-        
-        /// <summary>
-        /// Gets or sets the audio codec.
-        /// </summary>
-        /// <value>The audio codec.</value>
-        public string AudioCodec { get; set; }
-        
-        /// <summary>
-        /// Gets or sets the start time ticks.
-        /// </summary>
-        /// <value>The start time ticks.</value>
-        public long? StartTimeTicks { get; set; }
-
-        /// <summary>
-        /// Gets or sets the maximum channels.
-        /// </summary>
-        /// <value>The maximum channels.</value>
-        public int? MaxAudioChannels { get; set; }
-
-        /// <summary>
-        /// Gets or sets the channels.
-        /// </summary>
-        /// <value>The channels.</value>
-        public int? AudioChannels { get; set; }
-
-        /// <summary>
-        /// Gets or sets the sample rate.
-        /// </summary>
-        /// <value>The sample rate.</value>
-        public int? AudioSampleRate { get; set; }
-
-        /// <summary>
-        /// Gets or sets the bit rate.
-        /// </summary>
-        /// <value>The bit rate.</value>
-        public int? AudioBitRate { get; set; }
-
-        /// <summary>
-        /// Gets or sets the maximum audio bit rate.
-        /// </summary>
-        /// <value>The maximum audio bit rate.</value>
-        public int? MaxAudioBitRate { get; set; }
-    }
-}

+ 0 - 13
MediaBrowser.Controller/MediaEncoding/EncodingResult.cs

@@ -1,13 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Controller.MediaEncoding
-{
-    public class EncodingResult
-    {
-        public string OutputPath { get; set; }
-    }
-}

+ 22 - 0
MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs

@@ -96,5 +96,27 @@ namespace MediaBrowser.Controller.MediaEncoding
         /// <param name="ticks">The ticks.</param>
         /// <returns>System.String.</returns>
         string GetTimeParameter(long ticks);
+
+        /// <summary>
+        /// Encodes the audio.
+        /// </summary>
+        /// <param name="options">The options.</param>
+        /// <param name="progress">The progress.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        Task<string> EncodeAudio(EncodingJobOptions options,
+            IProgress<double> progress,
+            CancellationToken cancellationToken);
+
+        /// <summary>
+        /// Encodes the video.
+        /// </summary>
+        /// <param name="options">The options.</param>
+        /// <param name="progress">The progress.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task&lt;System.String&gt;.</returns>
+        Task<string> EncodeVideo(EncodingJobOptions options,
+            IProgress<double> progress,
+            CancellationToken cancellationToken);
     }
 }

+ 5 - 7
MediaBrowser.Controller/MediaEncoding/MediaStreamSelector.cs

@@ -34,15 +34,13 @@ namespace MediaBrowser.Controller.MediaEncoding
         }
 
         public static int? GetDefaultSubtitleStreamIndex(List<MediaStream> streams,
-            IEnumerable<string> preferredLanguages,
+            List<string> preferredLanguages,
             SubtitlePlaybackMode mode,
             string audioTrackLanguage)
         {
-            var languages = preferredLanguages.ToList();
-            streams = GetSortedStreams(streams, MediaStreamType.Subtitle, languages).ToList();
+            streams = GetSortedStreams(streams, MediaStreamType.Subtitle, preferredLanguages).ToList();
 
             var full = streams.Where(s => !s.IsForced);
-            var forced = streams.Where(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase));
 
             MediaStream stream = null;
 
@@ -54,9 +52,9 @@ namespace MediaBrowser.Controller.MediaEncoding
             if (mode == SubtitlePlaybackMode.Default)
             {
                 // if the audio language is not understood by the user, load their preferred subs, if there are any
-                if (!ContainsOrdinal(languages, audioTrackLanguage))
+                if (!ContainsOrdinal(preferredLanguages, audioTrackLanguage))
                 {
-                    stream = full.FirstOrDefault(s => ContainsOrdinal(languages, s.Language));
+                    stream = full.FirstOrDefault(s => ContainsOrdinal(preferredLanguages, s.Language));
                 }
             }
             else if (mode == SubtitlePlaybackMode.Always)
@@ -66,7 +64,7 @@ namespace MediaBrowser.Controller.MediaEncoding
             }
 
             // load forced subs if we have found no suitable full subtitles
-            stream = stream ?? forced.FirstOrDefault();
+            stream = stream ?? streams.FirstOrDefault(s => s.IsForced && string.Equals(s.Language, audioTrackLanguage, StringComparison.OrdinalIgnoreCase));
 
             if (stream != null)
             {

+ 0 - 26
MediaBrowser.Controller/MediaEncoding/VideoEncodingOptions.cs

@@ -1,26 +0,0 @@
-
-namespace MediaBrowser.Controller.MediaEncoding
-{
-    public class VideoEncodingOptions : EncodingOptions
-    {
-        public string VideoCodec { get; set; }
-
-        public string VideoProfile { get; set; }
-
-        public double? VideoLevel { get; set; }
-        
-        public int? VideoStreamIndex { get; set; }
-
-        public int? AudioStreamIndex { get; set; }
-
-        public int? SubtitleStreamIndex { get; set; }
-
-        public int? MaxWidth { get; set; }
-
-        public int? MaxHeight { get; set; }
-
-        public int? Height { get; set; }
-
-        public int? Width { get; set; }
-    }
-}

+ 3 - 2
MediaBrowser.Common/Net/BasePeriodicWebSocketListener.cs → MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Model.Logging;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Net;
 using System;
 using System.Collections.Generic;
@@ -7,7 +8,7 @@ using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
 
-namespace MediaBrowser.Common.Net
+namespace MediaBrowser.Controller.Net
 {
     /// <summary>
     /// Starts sending data over a web socket periodically when a message is received, and then stops when a corresponding stop message is received

+ 1 - 1
MediaBrowser.Common/Net/IWebSocket.cs → MediaBrowser.Controller/Net/IWebSocket.cs

@@ -3,7 +3,7 @@ using System;
 using System.Threading;
 using System.Threading.Tasks;
 
-namespace MediaBrowser.Common.Net
+namespace MediaBrowser.Controller.Net
 {
     /// <summary>
     /// Interface IWebSocket

+ 1 - 1
MediaBrowser.Common/Net/IWebSocketConnection.cs → MediaBrowser.Controller/Net/IWebSocketConnection.cs

@@ -3,7 +3,7 @@ using System;
 using System.Threading;
 using System.Threading.Tasks;
 
-namespace MediaBrowser.Common.Net
+namespace MediaBrowser.Controller.Net
 {
     public interface IWebSocketConnection : IDisposable
     {

+ 3 - 2
MediaBrowser.Common/Net/IWebSocketListener.cs → MediaBrowser.Controller/Net/IWebSocketListener.cs

@@ -1,6 +1,7 @@
-using System.Threading.Tasks;
+using MediaBrowser.Common.Net;
+using System.Threading.Tasks;
 
-namespace MediaBrowser.Common.Net
+namespace MediaBrowser.Controller.Net
 {
     /// <summary>
     ///This is an interface for listening to messages coming through a web socket connection

+ 1 - 1
MediaBrowser.Common/Net/WebSocketConnectEventArgs.cs → MediaBrowser.Controller/Net/WebSocketConnectEventArgs.cs

@@ -1,6 +1,6 @@
 using System;
 
-namespace MediaBrowser.Common.Net
+namespace MediaBrowser.Controller.Net
 {
     /// <summary>
     /// Class WebSocketConnectEventArgs

+ 1 - 1
MediaBrowser.Common/Net/WebSocketMessageInfo.cs → MediaBrowser.Controller/Net/WebSocketMessageInfo.cs

@@ -1,6 +1,6 @@
 using MediaBrowser.Model.Net;
 
-namespace MediaBrowser.Common.Net
+namespace MediaBrowser.Controller.Net
 {
     /// <summary>
     /// Class WebSocketMessageInfo

+ 5 - 0
MediaBrowser.Controller/Providers/DirectoryService.cs

@@ -46,6 +46,11 @@ namespace MediaBrowser.Controller.Providers
 
         private Dictionary<string, FileSystemInfo> GetFileSystemDictionary(string path, bool clearCache)
         {
+            if (string.IsNullOrWhiteSpace(path))
+            {
+                throw new ArgumentNullException("path");
+            }
+
             Dictionary<string, FileSystemInfo> entries;
 
             if (clearCache)

Неке датотеке нису приказане због велике количине промена