瀏覽代碼

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

Sven Van den brande 11 年之前
父節點
當前提交
e8f8d6651c
共有 100 個文件被更改,包括 1615 次插入722 次删除
  1. 25 13
      MediaBrowser.Api/Library/LibraryHelpers.cs
  2. 12 8
      MediaBrowser.Api/Library/LibraryStructureService.cs
  3. 32 2
      MediaBrowser.Api/LibraryService.cs
  4. 6 3
      MediaBrowser.Api/Playback/BaseStreamingService.cs
  5. 4 3
      MediaBrowser.Api/Playback/Hls/AudioHlsService.cs
  6. 3 3
      MediaBrowser.Api/Playback/Hls/BaseHlsService.cs
  7. 4 3
      MediaBrowser.Api/Playback/Hls/VideoHlsService.cs
  8. 4 3
      MediaBrowser.Api/Playback/Progressive/AudioService.cs
  9. 4 3
      MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
  10. 4 2
      MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs
  11. 4 3
      MediaBrowser.Api/Playback/Progressive/VideoService.cs
  12. 14 1
      MediaBrowser.Api/SessionsService.cs
  13. 8 2
      MediaBrowser.Api/SystemService.cs
  14. 21 42
      MediaBrowser.Api/TvShowsService.cs
  15. 137 41
      MediaBrowser.Api/UserLibrary/ItemsService.cs
  16. 7 4
      MediaBrowser.Api/WebSocket/LogFileWebSocketListener.cs
  17. 17 6
      MediaBrowser.Common.Implementations/BaseApplicationHost.cs
  18. 5 3
      MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
  19. 115 157
      MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs
  20. 5 3
      MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj
  21. 7 3
      MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs
  22. 6 2
      MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs
  23. 1 1
      MediaBrowser.Common.Implementations/packages.config
  24. 78 0
      MediaBrowser.Common/IO/IFileSystem.cs
  25. 1 0
      MediaBrowser.Common/MediaBrowser.Common.csproj
  26. 15 5
      MediaBrowser.Controller/Entities/BaseItem.cs
  27. 1 1
      MediaBrowser.Controller/Entities/Folder.cs
  28. 20 1
      MediaBrowser.Controller/Entities/TV/Episode.cs
  29. 22 1
      MediaBrowser.Controller/Entities/TV/Season.cs
  30. 1 1
      MediaBrowser.Controller/Entities/User.cs
  31. 7 5
      MediaBrowser.Controller/IO/FileData.cs
  32. 1 2
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  33. 6 2
      MediaBrowser.Controller/MediaInfo/FFMpegManager.cs
  34. 0 39
      MediaBrowser.Controller/Providers/BaseItemXmlParser.cs
  35. 38 0
      MediaBrowser.Controller/Providers/IImageProvider.cs
  36. 12 1
      MediaBrowser.Controller/Providers/IProviderManager.cs
  37. 13 10
      MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs
  38. 3 0
      MediaBrowser.Model.Portable/MediaBrowser.Model.Portable.csproj
  39. 3 0
      MediaBrowser.Model.net35/MediaBrowser.Model.net35.csproj
  40. 5 3
      MediaBrowser.Model/Configuration/UserConfiguration.cs
  41. 3 1
      MediaBrowser.Model/Entities/MetadataProviders.cs
  42. 1 0
      MediaBrowser.Model/MediaBrowser.Model.csproj
  43. 59 0
      MediaBrowser.Model/Providers/RemoteImageInfo.cs
  44. 16 5
      MediaBrowser.Model/Querying/ItemQuery.cs
  45. 7 18
      MediaBrowser.Model/Querying/NextUpQuery.cs
  46. 8 9
      MediaBrowser.Mono.userprefs
  47. 6 3
      MediaBrowser.Providers/FolderProviderFromXml.cs
  48. 9 6
      MediaBrowser.Providers/Games/GameProviderFromXml.cs
  49. 6 3
      MediaBrowser.Providers/Games/GameSystemProviderFromXml.cs
  50. 110 0
      MediaBrowser.Providers/Games/GameXmlParser.cs
  51. 8 4
      MediaBrowser.Providers/ImagesByNameProvider.cs
  52. 2 0
      MediaBrowser.Providers/MediaBrowser.Providers.csproj
  53. 6 3
      MediaBrowser.Providers/Movies/BoxSetProviderFromXml.cs
  54. 8 5
      MediaBrowser.Providers/Movies/FanArtMovieProvider.cs
  55. 6 3
      MediaBrowser.Providers/Movies/FanArtMovieUpdatesPrescanTask.cs
  56. 168 0
      MediaBrowser.Providers/Movies/ManualMovieDbImageProvider.cs
  57. 32 92
      MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs
  58. 7 3
      MediaBrowser.Providers/Movies/MovieDbProvider.cs
  59. 6 4
      MediaBrowser.Providers/Movies/MovieProviderFromXml.cs
  60. 6 3
      MediaBrowser.Providers/Movies/MovieUpdatesPrescanTask.cs
  61. 6 3
      MediaBrowser.Providers/Movies/PersonProviderFromXml.cs
  62. 6 3
      MediaBrowser.Providers/Movies/PersonUpdatesPreScanTask.cs
  63. 5 3
      MediaBrowser.Providers/Movies/TmdbPersonProvider.cs
  64. 6 3
      MediaBrowser.Providers/Music/ArtistProviderFromXml.cs
  65. 6 3
      MediaBrowser.Providers/Music/FanArtAlbumProvider.cs
  66. 5 7
      MediaBrowser.Providers/Music/FanArtArtistByNameProvider.cs
  67. 9 8
      MediaBrowser.Providers/Music/FanArtArtistProvider.cs
  68. 6 3
      MediaBrowser.Providers/Music/FanArtUpdatesPrescanTask.cs
  69. 7 3
      MediaBrowser.Providers/RefreshIntrosTask.cs
  70. 18 1
      MediaBrowser.Providers/Savers/GameXmlSaver.cs
  71. 6 3
      MediaBrowser.Providers/TV/EpisodeProviderFromXml.cs
  72. 6 3
      MediaBrowser.Providers/TV/FanArtSeasonProvider.cs
  73. 6 3
      MediaBrowser.Providers/TV/FanArtTVProvider.cs
  74. 6 3
      MediaBrowser.Providers/TV/FanArtTvUpdatesPrescanTask.cs
  75. 11 19
      MediaBrowser.Providers/TV/RemoteEpisodeProvider.cs
  76. 34 5
      MediaBrowser.Providers/TV/RemoteSeasonProvider.cs
  77. 32 12
      MediaBrowser.Providers/TV/RemoteSeriesProvider.cs
  78. 6 3
      MediaBrowser.Providers/TV/SeasonProviderFromXml.cs
  79. 19 6
      MediaBrowser.Providers/TV/SeriesPostScanTask.cs
  80. 7 4
      MediaBrowser.Providers/TV/SeriesProviderFromXml.cs
  81. 9 4
      MediaBrowser.Providers/TV/TvdbPrescanTask.cs
  82. 17 5
      MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs
  83. 6 3
      MediaBrowser.Server.Implementations/Drawing/ImageHeader.cs
  84. 13 10
      MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs
  85. 12 0
      MediaBrowser.Server.Implementations/Dto/DtoService.cs
  86. 6 2
      MediaBrowser.Server.Implementations/EntryPoints/Notifications/RemoteNotifications.cs
  87. 6 3
      MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs
  88. 20 4
      MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs
  89. 12 8
      MediaBrowser.Server.Implementations/Library/LibraryManager.cs
  90. 5 2
      MediaBrowser.Server.Implementations/Library/ResolverHelper.cs
  91. 2 1
      MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
  92. 8 3
      MediaBrowser.Server.Implementations/Localization/LocalizationManager.cs
  93. 8 6
      MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
  94. 6 4
      MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs
  95. 11 19
      MediaBrowser.Server.Implementations/Providers/ImageSaver.cs
  96. 65 5
      MediaBrowser.Server.Implementations/Providers/ProviderManager.cs
  97. 1 1
      MediaBrowser.Server.Implementations/packages.config
  98. 21 0
      MediaBrowser.Server.Mono/IO/FileSystemFactory.cs
  99. 2 0
      MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj
  100. 33 0
      MediaBrowser.Server.Mono/Program.cs

+ 25 - 13
MediaBrowser.Api/Library/LibraryHelpers.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.IO;
 using System;
 using System;
@@ -12,20 +13,27 @@ namespace MediaBrowser.Api.Library
     /// </summary>
     /// </summary>
     public static class LibraryHelpers
     public static class LibraryHelpers
     {
     {
+        /// <summary>
+        /// The shortcut file extension
+        /// </summary>
         private const string ShortcutFileExtension = ".mblink";
         private const string ShortcutFileExtension = ".mblink";
+        /// <summary>
+        /// The shortcut file search
+        /// </summary>
         private const string ShortcutFileSearch = "*" + ShortcutFileExtension;
         private const string ShortcutFileSearch = "*" + ShortcutFileExtension;
 
 
         /// <summary>
         /// <summary>
         /// Adds the virtual folder.
         /// Adds the virtual folder.
         /// </summary>
         /// </summary>
+        /// <param name="fileSystem">The file system.</param>
         /// <param name="name">The name.</param>
         /// <param name="name">The name.</param>
         /// <param name="collectionType">Type of the collection.</param>
         /// <param name="collectionType">Type of the collection.</param>
         /// <param name="user">The user.</param>
         /// <param name="user">The user.</param>
         /// <param name="appPaths">The app paths.</param>
         /// <param name="appPaths">The app paths.</param>
         /// <exception cref="System.ArgumentException">There is already a media collection with the name  + name + .</exception>
         /// <exception cref="System.ArgumentException">There is already a media collection with the name  + name + .</exception>
-        public static void AddVirtualFolder(string name, string collectionType, User user, IServerApplicationPaths appPaths)
+        public static void AddVirtualFolder(IFileSystem fileSystem, string name, string collectionType, User user, IServerApplicationPaths appPaths)
         {
         {
-            name = FileSystem.GetValidFilename(name);
+            name = fileSystem.GetValidFilename(name);
 
 
             var rootFolderPath = user != null ? user.RootFolderPath : appPaths.DefaultUserViewsPath;
             var rootFolderPath = user != null ? user.RootFolderPath : appPaths.DefaultUserViewsPath;
             var virtualFolderPath = Path.Combine(rootFolderPath, name);
             var virtualFolderPath = Path.Combine(rootFolderPath, name);
@@ -106,12 +114,13 @@ namespace MediaBrowser.Api.Library
         /// <summary>
         /// <summary>
         /// Deletes a shortcut from within a virtual folder, within either the default view or a user view
         /// Deletes a shortcut from within a virtual folder, within either the default view or a user view
         /// </summary>
         /// </summary>
+        /// <param name="fileSystem">The file system.</param>
         /// <param name="virtualFolderName">Name of the virtual folder.</param>
         /// <param name="virtualFolderName">Name of the virtual folder.</param>
         /// <param name="mediaPath">The media path.</param>
         /// <param name="mediaPath">The media path.</param>
         /// <param name="user">The user.</param>
         /// <param name="user">The user.</param>
         /// <param name="appPaths">The app paths.</param>
         /// <param name="appPaths">The app paths.</param>
         /// <exception cref="System.IO.DirectoryNotFoundException">The media folder does not exist</exception>
         /// <exception cref="System.IO.DirectoryNotFoundException">The media folder does not exist</exception>
-        public static void RemoveMediaPath(string virtualFolderName, string mediaPath, User user, IServerApplicationPaths appPaths)
+        public static void RemoveMediaPath(IFileSystem fileSystem, string virtualFolderName, string mediaPath, User user, IServerApplicationPaths appPaths)
         {
         {
             var rootFolderPath = user != null ? user.RootFolderPath : appPaths.DefaultUserViewsPath;
             var rootFolderPath = user != null ? user.RootFolderPath : appPaths.DefaultUserViewsPath;
             var path = Path.Combine(rootFolderPath, virtualFolderName);
             var path = Path.Combine(rootFolderPath, virtualFolderName);
@@ -121,7 +130,7 @@ namespace MediaBrowser.Api.Library
                 throw new DirectoryNotFoundException(string.Format("The media collection {0} does not exist", virtualFolderName));
                 throw new DirectoryNotFoundException(string.Format("The media collection {0} does not exist", virtualFolderName));
             }
             }
 
 
-            var shortcut = Directory.EnumerateFiles(path, ShortcutFileSearch, SearchOption.AllDirectories).FirstOrDefault(f => FileSystem.ResolveShortcut(f).Equals(mediaPath, StringComparison.OrdinalIgnoreCase));
+            var shortcut = Directory.EnumerateFiles(path, ShortcutFileSearch, SearchOption.AllDirectories).FirstOrDefault(f => fileSystem.ResolveShortcut(f).Equals(mediaPath, StringComparison.OrdinalIgnoreCase));
 
 
             if (!string.IsNullOrEmpty(shortcut))
             if (!string.IsNullOrEmpty(shortcut))
             {
             {
@@ -132,13 +141,14 @@ namespace MediaBrowser.Api.Library
         /// <summary>
         /// <summary>
         /// Adds an additional mediaPath to an existing virtual folder, within either the default view or a user view
         /// Adds an additional mediaPath to an existing virtual folder, within either the default view or a user view
         /// </summary>
         /// </summary>
+        /// <param name="fileSystem">The file system.</param>
         /// <param name="virtualFolderName">Name of the virtual folder.</param>
         /// <param name="virtualFolderName">Name of the virtual folder.</param>
         /// <param name="path">The path.</param>
         /// <param name="path">The path.</param>
         /// <param name="user">The user.</param>
         /// <param name="user">The user.</param>
         /// <param name="appPaths">The app paths.</param>
         /// <param name="appPaths">The app paths.</param>
         /// <exception cref="System.ArgumentException">The path is not valid.</exception>
         /// <exception cref="System.ArgumentException">The path is not valid.</exception>
         /// <exception cref="System.IO.DirectoryNotFoundException">The path does not exist.</exception>
         /// <exception cref="System.IO.DirectoryNotFoundException">The path does not exist.</exception>
-        public static void AddMediaPath(string virtualFolderName, string path, User user, IServerApplicationPaths appPaths)
+        public static void AddMediaPath(IFileSystem fileSystem, string virtualFolderName, string path, User user, IServerApplicationPaths appPaths)
         {
         {
             if (!Path.IsPathRooted(path))
             if (!Path.IsPathRooted(path))
             {
             {
@@ -160,7 +170,7 @@ namespace MediaBrowser.Api.Library
             var rootFolderPath = user != null ? user.RootFolderPath : appPaths.DefaultUserViewsPath;
             var rootFolderPath = user != null ? user.RootFolderPath : appPaths.DefaultUserViewsPath;
             var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
             var virtualFolderPath = Path.Combine(rootFolderPath, virtualFolderName);
 
 
-            ValidateNewMediaPath(rootFolderPath, path, appPaths);
+            ValidateNewMediaPath(fileSystem, rootFolderPath, path, appPaths);
 
 
             var shortcutFilename = Path.GetFileNameWithoutExtension(path);
             var shortcutFilename = Path.GetFileNameWithoutExtension(path);
 
 
@@ -172,20 +182,22 @@ namespace MediaBrowser.Api.Library
                 lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension);
                 lnk = Path.Combine(virtualFolderPath, shortcutFilename + ShortcutFileExtension);
             }
             }
 
 
-            FileSystem.CreateShortcut(lnk, path);
+            fileSystem.CreateShortcut(lnk, path);
         }
         }
 
 
         /// <summary>
         /// <summary>
         /// Validates that a new media path can be added
         /// Validates that a new media path can be added
         /// </summary>
         /// </summary>
+        /// <param name="fileSystem">The file system.</param>
         /// <param name="currentViewRootFolderPath">The current view root folder path.</param>
         /// <param name="currentViewRootFolderPath">The current view root folder path.</param>
         /// <param name="mediaPath">The media path.</param>
         /// <param name="mediaPath">The media path.</param>
         /// <param name="appPaths">The app paths.</param>
         /// <param name="appPaths">The app paths.</param>
-        /// <exception cref="System.ArgumentException"></exception>
-        private static void ValidateNewMediaPath(string currentViewRootFolderPath, string mediaPath, IServerApplicationPaths appPaths)
+        /// <exception cref="System.ArgumentException">
+        /// </exception>
+        private static void ValidateNewMediaPath(IFileSystem fileSystem, string currentViewRootFolderPath, string mediaPath, IServerApplicationPaths appPaths)
         {
         {
             var duplicate = Directory.EnumerateFiles(appPaths.RootFolderPath, ShortcutFileSearch, SearchOption.AllDirectories)
             var duplicate = Directory.EnumerateFiles(appPaths.RootFolderPath, ShortcutFileSearch, SearchOption.AllDirectories)
-                .Select(FileSystem.ResolveShortcut)
+                .Select(fileSystem.ResolveShortcut)
                 .FirstOrDefault(p => !IsNewPathValid(mediaPath, p, false));
                 .FirstOrDefault(p => !IsNewPathValid(mediaPath, p, false));
 
 
             if (!string.IsNullOrEmpty(duplicate))
             if (!string.IsNullOrEmpty(duplicate))
@@ -196,7 +208,7 @@ namespace MediaBrowser.Api.Library
             // Don't allow duplicate sub-paths within the same user library, or it will result in duplicate items
             // Don't allow duplicate sub-paths within the same user library, or it will result in duplicate items
             // See comments in IsNewPathValid
             // See comments in IsNewPathValid
             duplicate = Directory.EnumerateFiles(currentViewRootFolderPath, ShortcutFileSearch, SearchOption.AllDirectories)
             duplicate = Directory.EnumerateFiles(currentViewRootFolderPath, ShortcutFileSearch, SearchOption.AllDirectories)
-              .Select(FileSystem.ResolveShortcut)
+              .Select(fileSystem.ResolveShortcut)
               .FirstOrDefault(p => !IsNewPathValid(mediaPath, p, true));
               .FirstOrDefault(p => !IsNewPathValid(mediaPath, p, true));
 
 
             if (!string.IsNullOrEmpty(duplicate))
             if (!string.IsNullOrEmpty(duplicate))
@@ -206,7 +218,7 @@ namespace MediaBrowser.Api.Library
             
             
             // Make sure the current root folder doesn't already have a shortcut to the same path
             // Make sure the current root folder doesn't already have a shortcut to the same path
             duplicate = Directory.EnumerateFiles(currentViewRootFolderPath, ShortcutFileSearch, SearchOption.AllDirectories)
             duplicate = Directory.EnumerateFiles(currentViewRootFolderPath, ShortcutFileSearch, SearchOption.AllDirectories)
-                .Select(FileSystem.ResolveShortcut)
+                .Select(fileSystem.ResolveShortcut)
                 .FirstOrDefault(p => mediaPath.Equals(p, StringComparison.OrdinalIgnoreCase));
                 .FirstOrDefault(p => mediaPath.Equals(p, StringComparison.OrdinalIgnoreCase));
 
 
             if (!string.IsNullOrEmpty(duplicate))
             if (!string.IsNullOrEmpty(duplicate))

+ 12 - 8
MediaBrowser.Api/Library/LibraryStructureService.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller;
 using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
@@ -186,6 +187,8 @@ namespace MediaBrowser.Api.Library
 
 
         private readonly IDirectoryWatchers _directoryWatchers;
         private readonly IDirectoryWatchers _directoryWatchers;
 
 
+        private readonly IFileSystem _fileSystem;
+
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="LibraryStructureService"/> class.
         /// Initializes a new instance of the <see cref="LibraryStructureService"/> class.
         /// </summary>
         /// </summary>
@@ -193,7 +196,7 @@ namespace MediaBrowser.Api.Library
         /// <param name="userManager">The user manager.</param>
         /// <param name="userManager">The user manager.</param>
         /// <param name="libraryManager">The library manager.</param>
         /// <param name="libraryManager">The library manager.</param>
         /// <exception cref="System.ArgumentNullException">appPaths</exception>
         /// <exception cref="System.ArgumentNullException">appPaths</exception>
-        public LibraryStructureService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IDirectoryWatchers directoryWatchers)
+        public LibraryStructureService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IDirectoryWatchers directoryWatchers, IFileSystem fileSystem)
         {
         {
             if (appPaths == null)
             if (appPaths == null)
             {
             {
@@ -204,6 +207,7 @@ namespace MediaBrowser.Api.Library
             _appPaths = appPaths;
             _appPaths = appPaths;
             _libraryManager = libraryManager;
             _libraryManager = libraryManager;
             _directoryWatchers = directoryWatchers;
             _directoryWatchers = directoryWatchers;
+            _fileSystem = fileSystem;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -241,13 +245,13 @@ namespace MediaBrowser.Api.Library
             {
             {
                 if (string.IsNullOrEmpty(request.UserId))
                 if (string.IsNullOrEmpty(request.UserId))
                 {
                 {
-                    LibraryHelpers.AddVirtualFolder(request.Name, request.CollectionType, null, _appPaths);
+                    LibraryHelpers.AddVirtualFolder(_fileSystem, request.Name, request.CollectionType, null, _appPaths);
                 }
                 }
                 else
                 else
                 {
                 {
                     var user = _userManager.GetUserById(new Guid(request.UserId));
                     var user = _userManager.GetUserById(new Guid(request.UserId));
 
 
-                    LibraryHelpers.AddVirtualFolder(request.Name, request.CollectionType, user, _appPaths);
+                    LibraryHelpers.AddVirtualFolder(_fileSystem, request.Name, request.CollectionType, user, _appPaths);
                 }
                 }
 
 
                 // Need to add a delay here or directory watchers may still pick up the changes
                 // Need to add a delay here or directory watchers may still pick up the changes
@@ -352,13 +356,13 @@ namespace MediaBrowser.Api.Library
             {
             {
                 if (string.IsNullOrEmpty(request.UserId))
                 if (string.IsNullOrEmpty(request.UserId))
                 {
                 {
-                    LibraryHelpers.AddMediaPath(request.Name, request.Path, null, _appPaths);
+                    LibraryHelpers.AddMediaPath(_fileSystem, request.Name, request.Path, null, _appPaths);
                 }
                 }
                 else
                 else
                 {
                 {
                     var user = _userManager.GetUserById(new Guid(request.UserId));
                     var user = _userManager.GetUserById(new Guid(request.UserId));
 
 
-                    LibraryHelpers.AddMediaPath(request.Name, request.Path, user, _appPaths);
+                    LibraryHelpers.AddMediaPath(_fileSystem, request.Name, request.Path, user, _appPaths);
                 }
                 }
 
 
                 // Need to add a delay here or directory watchers may still pick up the changes
                 // Need to add a delay here or directory watchers may still pick up the changes
@@ -389,13 +393,13 @@ namespace MediaBrowser.Api.Library
             {
             {
                 if (string.IsNullOrEmpty(request.UserId))
                 if (string.IsNullOrEmpty(request.UserId))
                 {
                 {
-                    LibraryHelpers.RemoveMediaPath(request.Name, request.Path, null, _appPaths);
+                    LibraryHelpers.RemoveMediaPath(_fileSystem, request.Name, request.Path, null, _appPaths);
                 }
                 }
                 else
                 else
                 {
                 {
                     var user = _userManager.GetUserById(new Guid(request.UserId));
                     var user = _userManager.GetUserById(new Guid(request.UserId));
 
 
-                    LibraryHelpers.RemoveMediaPath(request.Name, request.Path, user, _appPaths);
+                    LibraryHelpers.RemoveMediaPath(_fileSystem, request.Name, request.Path, user, _appPaths);
                 }
                 }
 
 
                 // Need to add a delay here or directory watchers may still pick up the changes
                 // Need to add a delay here or directory watchers may still pick up the changes

+ 32 - 2
MediaBrowser.Api/LibraryService.cs

@@ -5,8 +5,10 @@ using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
 using MediaBrowser.Model.Querying;
 using MediaBrowser.Model.Querying;
 using ServiceStack.ServiceHost;
 using ServiceStack.ServiceHost;
 using System;
 using System;
@@ -32,6 +34,21 @@ namespace MediaBrowser.Api
         public string Id { get; set; }
         public string Id { get; set; }
     }
     }
 
 
+    [Route("/Items/{Id}/RemoteImages/{Type}", "GET")]
+    [Api(Description = "Gets available remote images for an item")]
+    public class GetRemoteImages : IReturn<List<RemoteImageInfo>>
+    {
+        /// <summary>
+        /// Gets or sets the id.
+        /// </summary>
+        /// <value>The id.</value>
+        [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+        public string Id { get; set; }
+
+        [ApiMember(Name = "Type", Description = "The image type", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+        public ImageType Type { get; set; }
+    }
+    
     /// <summary>
     /// <summary>
     /// Class GetCriticReviews
     /// Class GetCriticReviews
     /// </summary>
     /// </summary>
@@ -208,6 +225,7 @@ namespace MediaBrowser.Api
         private readonly ILibraryManager _libraryManager;
         private readonly ILibraryManager _libraryManager;
         private readonly IUserManager _userManager;
         private readonly IUserManager _userManager;
         private readonly IUserDataManager _userDataManager;
         private readonly IUserDataManager _userDataManager;
+        private readonly IProviderManager _providerManager;
 
 
         private readonly IDtoService _dtoService;
         private readonly IDtoService _dtoService;
 
 
@@ -215,13 +233,14 @@ namespace MediaBrowser.Api
         /// Initializes a new instance of the <see cref="LibraryService" /> class.
         /// Initializes a new instance of the <see cref="LibraryService" /> class.
         /// </summary>
         /// </summary>
         public LibraryService(IItemRepository itemRepo, ILibraryManager libraryManager, IUserManager userManager,
         public LibraryService(IItemRepository itemRepo, ILibraryManager libraryManager, IUserManager userManager,
-                              IDtoService dtoService, IUserDataManager userDataManager)
+                              IDtoService dtoService, IUserDataManager userDataManager, IProviderManager providerManager)
         {
         {
             _itemRepo = itemRepo;
             _itemRepo = itemRepo;
             _libraryManager = libraryManager;
             _libraryManager = libraryManager;
             _userManager = userManager;
             _userManager = userManager;
             _dtoService = dtoService;
             _dtoService = dtoService;
             _userDataManager = userDataManager;
             _userDataManager = userDataManager;
+            _providerManager = providerManager;
         }
         }
 
 
         public object Get(GetFile request)
         public object Get(GetFile request)
@@ -240,6 +259,15 @@ namespace MediaBrowser.Api
             return ToStaticFileResult(item.Path);
             return ToStaticFileResult(item.Path);
         }
         }
 
 
+        public object Get(GetRemoteImages request)
+        {
+            var item = _dtoService.GetItemByDtoId(request.Id);
+
+            var result = _providerManager.GetAvailableRemoteImages(item, request.Type, CancellationToken.None).Result;
+
+            return ToOptimizedResult(result);
+        }
+
         /// <summary>
         /// <summary>
         /// Gets the specified request.
         /// Gets the specified request.
         /// </summary>
         /// </summary>
@@ -335,7 +363,9 @@ namespace MediaBrowser.Api
         /// <returns>System.Object.</returns>
         /// <returns>System.Object.</returns>
         public object Get(GetItemCounts request)
         public object Get(GetItemCounts request)
         {
         {
-            var items = GetAllLibraryItems(request.UserId, _userManager, _libraryManager).ToList();
+            var items = GetAllLibraryItems(request.UserId, _userManager, _libraryManager)
+                .Where(i => i.LocationType != LocationType.Virtual)
+                .ToList();
 
 
             var filteredItems = request.UserId.HasValue ? FilterItems(items, request, request.UserId.Value).ToList() : items;
             var filteredItems = request.UserId.HasValue ? FilterItems(items, request, request.UserId.Value).ToList() : items;
 
 

+ 6 - 3
MediaBrowser.Api/Playback/BaseStreamingService.cs

@@ -59,6 +59,8 @@ namespace MediaBrowser.Api.Playback
         protected IMediaEncoder MediaEncoder { get; private set; }
         protected IMediaEncoder MediaEncoder { get; private set; }
         protected IDtoService DtoService { get; private set; }
         protected IDtoService DtoService { get; private set; }
 
 
+        protected IFileSystem FileSystem { get; private set; }
+        
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
         /// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
         /// </summary>
         /// </summary>
@@ -67,8 +69,9 @@ namespace MediaBrowser.Api.Playback
         /// <param name="libraryManager">The library manager.</param>
         /// <param name="libraryManager">The library manager.</param>
         /// <param name="isoManager">The iso manager.</param>
         /// <param name="isoManager">The iso manager.</param>
         /// <param name="mediaEncoder">The media encoder.</param>
         /// <param name="mediaEncoder">The media encoder.</param>
-        protected BaseStreamingService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService)
+        protected BaseStreamingService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem)
         {
         {
+            FileSystem = fileSystem;
             DtoService = dtoService;
             DtoService = dtoService;
             ApplicationPaths = appPaths;
             ApplicationPaths = appPaths;
             UserManager = userManager;
             UserManager = userManager;
@@ -269,7 +272,7 @@ namespace MediaBrowser.Api.Playback
             // If fixed dimensions were supplied
             // If fixed dimensions were supplied
             if (request.Width.HasValue && request.Height.HasValue)
             if (request.Width.HasValue && request.Height.HasValue)
             {
             {
-                return string.Format(" -vf \"scale={0}:{1}{2}\"", request.Width.Value, request.Height.Value, assSubtitleParam);
+                return string.Format(" -vf \"scale=trunc({0}/2)*2:trunc({1}/2)*2{2}\"", request.Width.Value, request.Height.Value, assSubtitleParam);
             }
             }
 
 
             var isH264Output = outputVideoCodec.Equals("libx264", StringComparison.OrdinalIgnoreCase);
             var isH264Output = outputVideoCodec.Equals("libx264", StringComparison.OrdinalIgnoreCase);
@@ -653,7 +656,7 @@ namespace MediaBrowser.Api.Playback
             var logFilePath = Path.Combine(ApplicationPaths.LogDirectoryPath, "ffmpeg-" + Guid.NewGuid() + ".txt");
             var logFilePath = Path.Combine(ApplicationPaths.LogDirectoryPath, "ffmpeg-" + Guid.NewGuid() + ".txt");
 
 
             // FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
             // FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
-            state.LogFileStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous);
+            state.LogFileStream = FileSystem.GetFileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, true);
 
 
             process.Exited += (sender, args) => OnFfMpegProcessExited(process, state);
             process.Exited += (sender, args) => OnFfMpegProcessExited(process, state);
 
 

+ 4 - 3
MediaBrowser.Api/Playback/Hls/AudioHlsService.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Common.MediaInfo;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Common.MediaInfo;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
@@ -32,8 +33,8 @@ namespace MediaBrowser.Api.Playback.Hls
         /// <param name="libraryManager">The library manager.</param>
         /// <param name="libraryManager">The library manager.</param>
         /// <param name="isoManager">The iso manager.</param>
         /// <param name="isoManager">The iso manager.</param>
         /// <param name="mediaEncoder">The media encoder.</param>
         /// <param name="mediaEncoder">The media encoder.</param>
-        public AudioHlsService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService)
-            : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, dtoService)
+        public AudioHlsService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem)
+            : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem)
         {
         {
         }
         }
 
 

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

@@ -38,8 +38,8 @@ namespace MediaBrowser.Api.Playback.Hls
         /// <param name="libraryManager">The library manager.</param>
         /// <param name="libraryManager">The library manager.</param>
         /// <param name="isoManager">The iso manager.</param>
         /// <param name="isoManager">The iso manager.</param>
         /// <param name="mediaEncoder">The media encoder.</param>
         /// <param name="mediaEncoder">The media encoder.</param>
-        protected BaseHlsService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService)
-            : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, dtoService)
+        protected BaseHlsService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem)
+            : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem)
         {
         {
         }
         }
 
 
@@ -209,7 +209,7 @@ namespace MediaBrowser.Api.Playback.Hls
                 string fileText;
                 string fileText;
 
 
                 // Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written
                 // Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written
-                using (var fileStream = new FileStream(playlist, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
+                using (var fileStream = FileSystem.GetFileStream(playlist, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
                 {
                 {
                     using (var reader = new StreamReader(fileStream))
                     using (var reader = new StreamReader(fileStream))
                     {
                     {

+ 4 - 3
MediaBrowser.Api/Playback/Hls/VideoHlsService.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Common.MediaInfo;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Common.MediaInfo;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
@@ -39,8 +40,8 @@ namespace MediaBrowser.Api.Playback.Hls
         /// <param name="isoManager">The iso manager.</param>
         /// <param name="isoManager">The iso manager.</param>
         /// <param name="mediaEncoder">The media encoder.</param>
         /// <param name="mediaEncoder">The media encoder.</param>
         /// <param name="dtoService">The dto service.</param>
         /// <param name="dtoService">The dto service.</param>
-        public VideoHlsService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService)
-            : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, dtoService)
+        public VideoHlsService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IDtoService dtoService, IFileSystem fileSystem)
+            : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem)
         {
         {
         }
         }
 
 

+ 4 - 3
MediaBrowser.Api/Playback/Progressive/AudioService.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Common.MediaInfo;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Common.MediaInfo;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Dto;
@@ -40,8 +41,8 @@ namespace MediaBrowser.Api.Playback.Progressive
     /// </summary>
     /// </summary>
     public class AudioService : BaseProgressiveStreamingService
     public class AudioService : BaseProgressiveStreamingService
     {
     {
-        public AudioService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IDtoService dtoService, IImageProcessor imageProcessor)
-            : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, itemRepo, dtoService, imageProcessor)
+        public AudioService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IDtoService dtoService, IImageProcessor imageProcessor, IFileSystem fileSystem)
+            : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, itemRepo, dtoService, imageProcessor, fileSystem)
         {
         {
         }
         }
 
 

+ 4 - 3
MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs

@@ -1,4 +1,5 @@
 using MediaBrowser.Api.Images;
 using MediaBrowser.Api.Images;
+using MediaBrowser.Common.IO;
 using MediaBrowser.Common.MediaInfo;
 using MediaBrowser.Common.MediaInfo;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller;
@@ -27,8 +28,8 @@ namespace MediaBrowser.Api.Playback.Progressive
         protected readonly IItemRepository ItemRepository;
         protected readonly IItemRepository ItemRepository;
         protected readonly IImageProcessor ImageProcessor;
         protected readonly IImageProcessor ImageProcessor;
 
 
-        protected BaseProgressiveStreamingService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepository, IDtoService dtoService, IImageProcessor imageProcessor) :
-            base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, dtoService)
+        protected BaseProgressiveStreamingService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepository, IDtoService dtoService, IImageProcessor imageProcessor, IFileSystem fileSystem) :
+            base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, dtoService, fileSystem)
         {
         {
             ItemRepository = itemRepository;
             ItemRepository = itemRepository;
             ImageProcessor = imageProcessor;
             ImageProcessor = imageProcessor;
@@ -346,7 +347,7 @@ namespace MediaBrowser.Api.Playback.Progressive
                 ApiEntryPoint.Instance.OnTranscodeBeginRequest(outputPath, TranscodingJobType.Progressive);
                 ApiEntryPoint.Instance.OnTranscodeBeginRequest(outputPath, TranscodingJobType.Progressive);
             }
             }
 
 
-            var result = new ProgressiveStreamWriter(outputPath, Logger);
+            var result = new ProgressiveStreamWriter(outputPath, Logger, FileSystem);
 
 
             result.Options["Accept-Ranges"] = "none";
             result.Options["Accept-Ranges"] = "none";
             result.Options["Content-Type"] = contentType;
             result.Options["Content-Type"] = contentType;

+ 4 - 2
MediaBrowser.Api/Playback/Progressive/ProgressiveStreamWriter.cs

@@ -13,6 +13,7 @@ namespace MediaBrowser.Api.Playback.Progressive
     {
     {
         private string Path { get; set; }
         private string Path { get; set; }
         private ILogger Logger { get; set; }
         private ILogger Logger { get; set; }
+        private readonly IFileSystem _fileSystem;
 
 
         /// <summary>
         /// <summary>
         /// The _options
         /// The _options
@@ -32,10 +33,11 @@ namespace MediaBrowser.Api.Playback.Progressive
         /// </summary>
         /// </summary>
         /// <param name="path">The path.</param>
         /// <param name="path">The path.</param>
         /// <param name="logger">The logger.</param>
         /// <param name="logger">The logger.</param>
-        public ProgressiveStreamWriter(string path, ILogger logger)
+        public ProgressiveStreamWriter(string path, ILogger logger, IFileSystem fileSystem)
         {
         {
             Path = path;
             Path = path;
             Logger = logger;
             Logger = logger;
+            _fileSystem = fileSystem;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -83,7 +85,7 @@ namespace MediaBrowser.Api.Playback.Progressive
             var eofCount = 0;
             var eofCount = 0;
             long position = 0;
             long position = 0;
 
 
-            using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
+            using (var fs = _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
             {
             {
                 while (eofCount < 15)
                 while (eofCount < 15)
                 {
                 {

+ 4 - 3
MediaBrowser.Api/Playback/Progressive/VideoService.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Common.MediaInfo;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Common.MediaInfo;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Dto;
@@ -54,8 +55,8 @@ namespace MediaBrowser.Api.Playback.Progressive
     /// </summary>
     /// </summary>
     public class VideoService : BaseProgressiveStreamingService
     public class VideoService : BaseProgressiveStreamingService
     {
     {
-        public VideoService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IDtoService dtoService, IImageProcessor imageProcessor)
-            : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, itemRepo, dtoService, imageProcessor)
+        public VideoService(IServerApplicationPaths appPaths, IUserManager userManager, ILibraryManager libraryManager, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IDtoService dtoService, IImageProcessor imageProcessor, IFileSystem fileSystem)
+            : base(appPaths, userManager, libraryManager, isoManager, mediaEncoder, itemRepo, dtoService, imageProcessor, fileSystem)
         {
         {
         }
         }
 
 

+ 14 - 1
MediaBrowser.Api/SessionsService.cs

@@ -1,4 +1,5 @@
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Model.Session;
 using MediaBrowser.Model.Session;
 using ServiceStack.ServiceHost;
 using ServiceStack.ServiceHost;
@@ -182,16 +183,18 @@ namespace MediaBrowser.Api
         private readonly ISessionManager _sessionManager;
         private readonly ISessionManager _sessionManager;
 
 
         private readonly IDtoService _dtoService;
         private readonly IDtoService _dtoService;
+        private readonly IUserManager _userManager;
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="SessionsService" /> class.
         /// Initializes a new instance of the <see cref="SessionsService" /> class.
         /// </summary>
         /// </summary>
         /// <param name="sessionManager">The session manager.</param>
         /// <param name="sessionManager">The session manager.</param>
         /// <param name="dtoService">The dto service.</param>
         /// <param name="dtoService">The dto service.</param>
-        public SessionsService(ISessionManager sessionManager, IDtoService dtoService)
+        public SessionsService(ISessionManager sessionManager, IDtoService dtoService, IUserManager userManager)
         {
         {
             _sessionManager = sessionManager;
             _sessionManager = sessionManager;
             _dtoService = dtoService;
             _dtoService = dtoService;
+            _userManager = userManager;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -208,6 +211,16 @@ namespace MediaBrowser.Api
                 result = result.Where(i => i.SupportsRemoteControl == request.SupportsRemoteControl.Value);
                 result = result.Where(i => i.SupportsRemoteControl == request.SupportsRemoteControl.Value);
             }
             }
 
 
+            if (request.ControllableByUserId.HasValue)
+            {
+                var user = _userManager.GetUserById(request.ControllableByUserId.Value);
+
+                if (!user.Configuration.EnableRemoteControlOfOtherUsers)
+                {
+                    result = result.Where(i => i.User == null || i.User.Id == request.ControllableByUserId.Value);
+                }
+            }
+
             return ToOptimizedResult(result.Select(_dtoService.GetSessionInfoDto).ToList());
             return ToOptimizedResult(result.Select(_dtoService.GetSessionInfoDto).ToList());
         }
         }
 
 

+ 8 - 2
MediaBrowser.Api/SystemService.cs

@@ -1,6 +1,8 @@
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.IO;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.IO;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.System;
 using MediaBrowser.Model.System;
@@ -75,6 +77,9 @@ namespace MediaBrowser.Api
         /// </summary>
         /// </summary>
         private readonly IServerConfigurationManager _configurationManager;
         private readonly IServerConfigurationManager _configurationManager;
 
 
+        private readonly IFileSystem _fileSystem;
+
+
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="SystemService" /> class.
         /// Initializes a new instance of the <see cref="SystemService" /> class.
         /// </summary>
         /// </summary>
@@ -82,7 +87,7 @@ namespace MediaBrowser.Api
         /// <param name="appHost">The app host.</param>
         /// <param name="appHost">The app host.</param>
         /// <param name="configurationManager">The configuration manager.</param>
         /// <param name="configurationManager">The configuration manager.</param>
         /// <exception cref="System.ArgumentNullException">jsonSerializer</exception>
         /// <exception cref="System.ArgumentNullException">jsonSerializer</exception>
-        public SystemService(IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IServerConfigurationManager configurationManager)
+        public SystemService(IJsonSerializer jsonSerializer, IServerApplicationHost appHost, IServerConfigurationManager configurationManager, IFileSystem fileSystem)
             : base()
             : base()
         {
         {
             if (jsonSerializer == null)
             if (jsonSerializer == null)
@@ -96,6 +101,7 @@ namespace MediaBrowser.Api
 
 
             _appHost = appHost;
             _appHost = appHost;
             _configurationManager = configurationManager;
             _configurationManager = configurationManager;
+            _fileSystem = fileSystem;
             _jsonSerializer = jsonSerializer;
             _jsonSerializer = jsonSerializer;
         }
         }
 
 
@@ -118,7 +124,7 @@ namespace MediaBrowser.Api
         /// <returns>System.Object.</returns>
         /// <returns>System.Object.</returns>
         public object Get(GetConfiguration request)
         public object Get(GetConfiguration request)
         {
         {
-            var dateModified = File.GetLastWriteTimeUtc(_configurationManager.ApplicationPaths.SystemConfigurationFilePath);
+            var dateModified = _fileSystem.GetLastWriteTimeUtc(_configurationManager.ApplicationPaths.SystemConfigurationFilePath);
 
 
             var cacheKey = (_configurationManager.ApplicationPaths.SystemConfigurationFilePath + dateModified.Ticks).GetMD5();
             var cacheKey = (_configurationManager.ApplicationPaths.SystemConfigurationFilePath + dateModified.Ticks).GetMD5();
 
 

+ 21 - 42
MediaBrowser.Api/TvShowsService.cs

@@ -1,10 +1,9 @@
-using System.Collections;
-using System.Globalization;
-using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Persistence;
+using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Querying;
 using MediaBrowser.Model.Querying;
 using ServiceStack.ServiceHost;
 using ServiceStack.ServiceHost;
 using System;
 using System;
@@ -48,18 +47,9 @@ namespace MediaBrowser.Api
         [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, OverviewHtml, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         [ApiMember(Name = "Fields", Description = "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimeted. Options: Budget, Chapters, CriticRatingSummary, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, OverviewHtml, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         public string Fields { get; set; }
         public string Fields { get; set; }
 
 
-        [ApiMember(Name = "ExcludeLocationTypes", Description = "Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
-        public string ExcludeLocationTypes { get; set; }
+        [ApiMember(Name = "SeriesId", Description = "Optional. Filter by series id", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string SeriesId { get; set; }
 
 
-        [ApiMember(Name = "MinPremiereDate", Description = "Optional. The minimum premiere date. Format = yyyyMMddHHmmss", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
-        public string MinPremiereDate { get; set; }
-
-        [ApiMember(Name = "MaxPremiereDate", Description = "Optional. The maximum premiere date. Format = yyyyMMddHHmmss", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
-        public string MaxPremiereDate { get; set; }
-
-        [ApiMember(Name = "HasPremiereDate", Description = "Optional filter by items with premiere dates.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public bool? HasPremiereDate { get; set; }
-        
         /// <summary>
         /// <summary>
         /// Gets the item fields.
         /// Gets the item fields.
         /// </summary>
         /// </summary>
@@ -170,10 +160,15 @@ namespace MediaBrowser.Api
         {
         {
             var user = _userManager.GetUserById(request.UserId);
             var user = _userManager.GetUserById(request.UserId);
 
 
-            var itemsList = user.RootFolder
-                .GetRecursiveChildren(user, i => i is Series)
+            var items = user.RootFolder
+                .GetRecursiveChildren(user)
+                .OfType<Series>();
+
+            items = FilterSeries(request, items);
+
+            var itemsList = items
                 .AsParallel()
                 .AsParallel()
-                .Select(i => GetNextUp((Series)i, user, request))
+                .Select(i => GetNextUp(i, user, request))
                 .ToList();
                 .ToList();
 
 
             itemsList = itemsList
             itemsList = itemsList
@@ -264,35 +259,19 @@ namespace MediaBrowser.Api
 
 
         private IEnumerable<Episode> FilterItems(GetNextUpEpisodes request, IEnumerable<Episode> items)
         private IEnumerable<Episode> FilterItems(GetNextUpEpisodes request, IEnumerable<Episode> items)
         {
         {
-            // ExcludeLocationTypes
-            if (!string.IsNullOrEmpty(request.ExcludeLocationTypes))
-            {
-                var vals = request.ExcludeLocationTypes.Split(',');
-
-                items = items
-                    .Where(f => !vals.Contains(f.LocationType.ToString(), StringComparer.OrdinalIgnoreCase))
-                    .ToList();
-            }
-            
-            if (!string.IsNullOrEmpty(request.MinPremiereDate))
-            {
-                var date = DateTime.ParseExact(request.MinPremiereDate, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal);
+            // Make this configurable when needed
+            items = items.Where(i => i.LocationType != LocationType.Virtual);
 
 
-                items = items.Where(i => !i.PremiereDate.HasValue || i.PremiereDate.Value >= date);
-            }
-
-            if (!string.IsNullOrEmpty(request.MaxPremiereDate))
-            {
-                var date = DateTime.ParseExact(request.MaxPremiereDate, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal);
-
-                items = items.Where(i => !i.PremiereDate.HasValue || i.PremiereDate.Value <= date);
-            }
+            return items;
+        }
 
 
-            if (request.HasPremiereDate.HasValue)
+        private IEnumerable<Series> FilterSeries(GetNextUpEpisodes request, IEnumerable<Series> items)
+        {
+            if (!string.IsNullOrWhiteSpace(request.SeriesId))
             {
             {
-                var val = request.HasPremiereDate.Value;
+                var id = new Guid(request.SeriesId);
 
 
-                items = items.Where(i => i.PremiereDate.HasValue == val);
+                items = items.Where(i => i.Id == id);
             }
             }
 
 
             return items;
             return items;

+ 137 - 41
MediaBrowser.Api/UserLibrary/ItemsService.cs

@@ -1,5 +1,4 @@
-using System.Globalization;
-using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.Movies;
@@ -181,22 +180,22 @@ namespace MediaBrowser.Api.UserLibrary
         [ApiMember(Name = "IsHD", Description = "Optional filter by items that are HD or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
         [ApiMember(Name = "IsHD", Description = "Optional filter by items that are HD or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
         public bool? IsHD { get; set; }
         public bool? IsHD { get; set; }
 
 
-        [ApiMember(Name = "ExcludeLocationTypes", Description = "Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
-        public string ExcludeLocationTypes { get; set; }
-
         [ApiMember(Name = "LocationTypes", Description = "Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         [ApiMember(Name = "LocationTypes", Description = "Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
         public string LocationTypes { get; set; }
         public string LocationTypes { get; set; }
 
 
-        [ApiMember(Name = "MinPremiereDate", Description = "Optional. The minimum premiere date. Format = yyyyMMddHHmmss", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
-        public string MinPremiereDate { get; set; }
+        [ApiMember(Name = "ExcludeLocationTypes", Description = "Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimeted.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET", AllowMultiple = true)]
+        public string ExcludeLocationTypes { get; set; }
 
 
-        [ApiMember(Name = "MaxPremiereDate", Description = "Optional. The maximum premiere date. Format = yyyyMMddHHmmss", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "POST")]
-        public string MaxPremiereDate { get; set; }
+        public bool IncludeIndexContainers { get; set; }
 
 
-        [ApiMember(Name = "HasPremiereDate", Description = "Optional filter by items with premiere dates.", IsRequired = false, DataType = "string", ParameterType = "query", Verb = "GET")]
-        public bool? HasPremiereDate { get; set; }
+        [ApiMember(Name = "IsMissing", Description = "Optional filter by items that are missing episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
+        public bool? IsMissing { get; set; }
 
 
-        public bool IncludeIndexContainers { get; set; }
+        [ApiMember(Name = "IsUnaired", Description = "Optional filter by items that are unaired episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
+        public bool? IsUnaired { get; set; }
+
+        [ApiMember(Name = "IsVirtualUnaired", Description = "Optional filter by items that are virtual unaired episodes or not.", IsRequired = false, DataType = "bool", ParameterType = "query", Verb = "GET")]
+        public bool? IsVirtualUnaired { get; set; }
     }
     }
 
 
     /// <summary>
     /// <summary>
@@ -270,6 +269,8 @@ namespace MediaBrowser.Api.UserLibrary
                 items = ApplyFilter(items, filter, user, _userDataRepository);
                 items = ApplyFilter(items, filter, user, _userDataRepository);
             }
             }
 
 
+            items = FilterVirtualEpisodes(request, items, user);
+
             items = items.AsEnumerable();
             items = items.AsEnumerable();
 
 
             items = ApplySearchTerm(request, items);
             items = ApplySearchTerm(request, items);
@@ -440,6 +441,121 @@ namespace MediaBrowser.Api.UserLibrary
             return items;
             return items;
         }
         }
 
 
+        private IEnumerable<BaseItem> FilterVirtualEpisodes(GetItems request, IEnumerable<BaseItem> items, User user)
+        {
+            items = FilterVirtualSeasons(request, items, user);
+
+            if (request.IsMissing.HasValue)
+            {
+                var val = request.IsMissing.Value;
+                items = items.Where(i =>
+                {
+                    var e = i as Episode;
+                    if (e != null)
+                    {
+                        return e.IsMissingEpisode == val;
+                    }
+                    return true;
+                });
+            }
+
+            if (request.IsUnaired.HasValue)
+            {
+                var val = request.IsUnaired.Value;
+                items = items.Where(i =>
+                {
+                    var e = i as Episode;
+                    if (e != null)
+                    {
+                        return e.IsUnaired == val;
+                    }
+                    return true;
+                });
+            }
+
+            if (request.IsVirtualUnaired.HasValue)
+            {
+                var val = request.IsVirtualUnaired.Value;
+                items = items.Where(i =>
+                {
+                    var e = i as Episode;
+                    if (e != null)
+                    {
+                        return e.IsVirtualUnaired == val;
+                    }
+                    return true;
+                });
+            }
+
+            return items;
+        }
+
+        private IEnumerable<BaseItem> FilterVirtualSeasons(GetItems request, IEnumerable<BaseItem> items, User user)
+        {
+            if (request.IsMissing.HasValue && request.IsUnaired.HasValue)
+            {
+                var isMissing = request.IsMissing.Value;
+                var isUnaired = request.IsUnaired.Value;
+
+                if (!isMissing && !isUnaired)
+                {
+                    return items.Where(i =>
+                    {
+                        var e = i as Season;
+                        if (e != null)
+                        {
+                            return !e.IsMissingOrVirtualUnaired;
+                        }
+                        return true;
+                    });
+                }
+            }
+
+            if (request.IsMissing.HasValue)
+            {
+                var val = request.IsMissing.Value;
+                items = items.Where(i =>
+                {
+                    var e = i as Season;
+                    if (e != null)
+                    {
+                        return e.IsMissingSeason == val;
+                    }
+                    return true;
+                });
+            }
+
+            if (request.IsUnaired.HasValue)
+            {
+                var val = request.IsUnaired.Value;
+                items = items.Where(i =>
+                {
+                    var e = i as Season;
+                    if (e != null)
+                    {
+                        return e.IsUnaired == val;
+                    }
+                    return true;
+                });
+            }
+
+            if (request.IsVirtualUnaired.HasValue)
+            {
+                var val = request.IsVirtualUnaired.Value;
+                items = items.Where(i =>
+                {
+                    var e = i as Season;
+                    if (e != null)
+                    {
+                        return e.IsVirtualUnaired == val;
+                    }
+                    return true;
+                });
+            }
+
+            return items;
+        }
+
         /// <summary>
         /// <summary>
         /// Applies the additional filters.
         /// Applies the additional filters.
         /// </summary>
         /// </summary>
@@ -593,13 +709,6 @@ namespace MediaBrowser.Api.UserLibrary
                 items = items.Where(f => vals.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase));
                 items = items.Where(f => vals.Contains(f.GetType().Name, StringComparer.OrdinalIgnoreCase));
             }
             }
 
 
-            // ExcludeLocationTypes
-            if (!string.IsNullOrEmpty(request.ExcludeLocationTypes))
-            {
-                var vals = request.ExcludeLocationTypes.Split(',');
-                items = items.Where(f => !vals.Contains(f.LocationType.ToString(), StringComparer.OrdinalIgnoreCase));
-            }
-
             // LocationTypes
             // LocationTypes
             if (!string.IsNullOrEmpty(request.LocationTypes))
             if (!string.IsNullOrEmpty(request.LocationTypes))
             {
             {
@@ -607,6 +716,13 @@ namespace MediaBrowser.Api.UserLibrary
                 items = items.Where(f => vals.Contains(f.LocationType.ToString(), StringComparer.OrdinalIgnoreCase));
                 items = items.Where(f => vals.Contains(f.LocationType.ToString(), StringComparer.OrdinalIgnoreCase));
             }
             }
 
 
+            // ExcludeLocationTypes
+            if (!string.IsNullOrEmpty(request.ExcludeLocationTypes))
+            {
+                var vals = request.ExcludeLocationTypes.Split(',');
+                items = items.Where(f => !vals.Contains(f.LocationType.ToString(), StringComparer.OrdinalIgnoreCase));
+            }
+
             if (!string.IsNullOrEmpty(request.NameStartsWithOrGreater))
             if (!string.IsNullOrEmpty(request.NameStartsWithOrGreater))
             {
             {
                 items = items.Where(i => string.Compare(request.NameStartsWithOrGreater, i.SortName, StringComparison.CurrentCultureIgnoreCase) < 1);
                 items = items.Where(i => string.Compare(request.NameStartsWithOrGreater, i.SortName, StringComparison.CurrentCultureIgnoreCase) < 1);
@@ -826,7 +942,8 @@ namespace MediaBrowser.Api.UserLibrary
 
 
             if (request.IsHD.HasValue)
             if (request.IsHD.HasValue)
             {
             {
-                items = items.OfType<Video>().Where(i => i.IsHD == request.IsHD.Value);
+                var val = request.IsHD.Value;
+                items = items.OfType<Video>().Where(i => i.IsHD == val);
             }
             }
 
 
             if (request.ParentIndexNumber.HasValue)
             if (request.ParentIndexNumber.HasValue)
@@ -853,27 +970,6 @@ namespace MediaBrowser.Api.UserLibrary
                 });
                 });
             }
             }
 
 
-            if (!string.IsNullOrEmpty(request.MinPremiereDate))
-            {
-                var date = DateTime.ParseExact(request.MinPremiereDate, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal);
-
-                items = items.Where(i => !i.PremiereDate.HasValue || i.PremiereDate.Value >= date);
-            }
-
-            if (!string.IsNullOrEmpty(request.MaxPremiereDate))
-            {
-                var date = DateTime.ParseExact(request.MaxPremiereDate, "yyyyMMddHHmmss", CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal);
-
-                items = items.Where(i => !i.PremiereDate.HasValue || i.PremiereDate.Value <= date);
-            }
-
-            if (request.HasPremiereDate.HasValue)
-            {
-                var val = request.HasPremiereDate.Value;
-
-                items = items.Where(i => i.PremiereDate.HasValue == val);
-            }
-
             return items;
             return items;
         }
         }
 
 

+ 7 - 4
MediaBrowser.Api/WebSocket/LogFileWebSocketListener.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Common.IO;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.IO;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
@@ -27,16 +28,18 @@ namespace MediaBrowser.Api.WebSocket
         /// The _kernel
         /// The _kernel
         /// </summary>
         /// </summary>
         private readonly ILogManager _logManager;
         private readonly ILogManager _logManager;
+        private readonly IFileSystem _fileSystem;
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="LogFileWebSocketListener" /> class.
         /// Initializes a new instance of the <see cref="LogFileWebSocketListener" /> class.
         /// </summary>
         /// </summary>
         /// <param name="logger">The logger.</param>
         /// <param name="logger">The logger.</param>
         /// <param name="logManager">The log manager.</param>
         /// <param name="logManager">The log manager.</param>
-        public LogFileWebSocketListener(ILogger logger, ILogManager logManager)
+        public LogFileWebSocketListener(ILogger logger, ILogManager logManager, IFileSystem fileSystem)
             : base(logger)
             : base(logger)
         {
         {
             _logManager = logManager;
             _logManager = logManager;
+            _fileSystem = fileSystem;
             _logManager.LoggerLoaded += kernel_LoggerLoaded;
             _logManager.LoggerLoaded += kernel_LoggerLoaded;
         }
         }
 
 
@@ -53,7 +56,7 @@ namespace MediaBrowser.Api.WebSocket
                 state.StartLine = 0;
                 state.StartLine = 0;
             }
             }
 
 
-            var lines = await GetLogLines(state.LastLogFilePath, state.StartLine).ConfigureAwait(false);
+            var lines = await GetLogLines(state.LastLogFilePath, state.StartLine, _fileSystem).ConfigureAwait(false);
 
 
             state.StartLine += lines.Count;
             state.StartLine += lines.Count;
 
 
@@ -96,11 +99,11 @@ namespace MediaBrowser.Api.WebSocket
         /// <param name="logFilePath">The log file path.</param>
         /// <param name="logFilePath">The log file path.</param>
         /// <param name="startLine">The start line.</param>
         /// <param name="startLine">The start line.</param>
         /// <returns>Task{IEnumerable{System.String}}.</returns>
         /// <returns>Task{IEnumerable{System.String}}.</returns>
-        internal static async Task<List<string>> GetLogLines(string logFilePath, int startLine)
+        internal static async Task<List<string>> GetLogLines(string logFilePath, int startLine, IFileSystem fileSystem)
         {
         {
             var lines = new List<string>();
             var lines = new List<string>();
 
 
-            using (var fs = new FileStream(logFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, StreamDefaults.DefaultFileStreamBufferSize, true))
+            using (var fs = fileSystem.GetFileStream(logFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, true))
             {
             {
                 using (var reader = new StreamReader(fs))
                 using (var reader = new StreamReader(fs))
                 {
                 {

+ 17 - 6
MediaBrowser.Common.Implementations/BaseApplicationHost.cs

@@ -1,5 +1,4 @@
-using System.Net;
-using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Events;
 using MediaBrowser.Common.Events;
 using MediaBrowser.Common.Implementations.Archiving;
 using MediaBrowser.Common.Implementations.Archiving;
 using MediaBrowser.Common.Implementations.IO;
 using MediaBrowser.Common.Implementations.IO;
@@ -7,6 +6,7 @@ using MediaBrowser.Common.Implementations.ScheduledTasks;
 using MediaBrowser.Common.Implementations.Security;
 using MediaBrowser.Common.Implementations.Security;
 using MediaBrowser.Common.Implementations.Serialization;
 using MediaBrowser.Common.Implementations.Serialization;
 using MediaBrowser.Common.Implementations.Updates;
 using MediaBrowser.Common.Implementations.Updates;
+using MediaBrowser.Common.IO;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Plugins;
 using MediaBrowser.Common.Plugins;
 using MediaBrowser.Common.ScheduledTasks;
 using MediaBrowser.Common.ScheduledTasks;
@@ -21,6 +21,7 @@ using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
+using System.Net;
 using System.Net.Http;
 using System.Net.Http;
 using System.Reflection;
 using System.Reflection;
 using System.Threading;
 using System.Threading;
@@ -150,15 +151,17 @@ namespace MediaBrowser.Common.Implementations
         /// Gets or sets the installation manager.
         /// Gets or sets the installation manager.
         /// </summary>
         /// </summary>
         /// <value>The installation manager.</value>
         /// <value>The installation manager.</value>
-        protected IInstallationManager InstallationManager { get; set; }
+        protected IInstallationManager InstallationManager { get; private set; }
 
 
+        protected IFileSystem FileSystemManager { get; private set; }
+        
         /// <summary>
         /// <summary>
         /// Gets or sets the zip client.
         /// Gets or sets the zip client.
         /// </summary>
         /// </summary>
         /// <value>The zip client.</value>
         /// <value>The zip client.</value>
-        protected IZipClient ZipClient { get; set; }
+        protected IZipClient ZipClient { get; private set; }
 
 
-        protected IIsoManager IsoManager { get; set; }
+        protected IIsoManager IsoManager { get; private set; }
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="BaseApplicationHost{TApplicationPathsType}"/> class.
         /// Initializes a new instance of the <see cref="BaseApplicationHost{TApplicationPathsType}"/> class.
@@ -347,7 +350,10 @@ namespace MediaBrowser.Common.Implementations
 
 
                 RegisterSingleInstance(TaskManager);
                 RegisterSingleInstance(TaskManager);
 
 
-                HttpClient = new HttpClientManager.HttpClientManager(ApplicationPaths, Logger, CreateHttpClient);
+                FileSystemManager = CreateFileSystemManager();
+                RegisterSingleInstance(FileSystemManager);
+
+                HttpClient = new HttpClientManager.HttpClientManager(ApplicationPaths, Logger, CreateHttpClient, FileSystemManager);
                 RegisterSingleInstance(HttpClient);
                 RegisterSingleInstance(HttpClient);
 
 
                 NetworkManager = CreateNetworkManager();
                 NetworkManager = CreateNetworkManager();
@@ -367,6 +373,11 @@ namespace MediaBrowser.Common.Implementations
             });
             });
         }
         }
 
 
+        protected virtual IFileSystem CreateFileSystemManager()
+        {
+            return new CommonFileSystem(Logger, true);
+        }
+
         protected abstract HttpClient CreateHttpClient(bool enableHttpCompression);
         protected abstract HttpClient CreateHttpClient(bool enableHttpCompression);
 
 
         /// <summary>
         /// <summary>

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

@@ -34,6 +34,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
         public delegate HttpClient GetHttpClientHandler(bool enableHttpCompression);
         public delegate HttpClient GetHttpClientHandler(bool enableHttpCompression);
 
 
         private readonly GetHttpClientHandler _getHttpClientHandler;
         private readonly GetHttpClientHandler _getHttpClientHandler;
+        private readonly IFileSystem _fileSystem;
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="HttpClientManager"/> class.
         /// Initializes a new instance of the <see cref="HttpClientManager"/> class.
@@ -46,7 +47,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
         /// or
         /// or
         /// logger
         /// logger
         /// </exception>
         /// </exception>
-        public HttpClientManager(IApplicationPaths appPaths, ILogger logger, GetHttpClientHandler getHttpClientHandler)
+        public HttpClientManager(IApplicationPaths appPaths, ILogger logger, GetHttpClientHandler getHttpClientHandler, IFileSystem fileSystem)
         {
         {
             if (appPaths == null)
             if (appPaths == null)
             {
             {
@@ -59,6 +60,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
 
 
             _logger = logger;
             _logger = logger;
             _getHttpClientHandler = getHttpClientHandler;
             _getHttpClientHandler = getHttpClientHandler;
+            _fileSystem = fileSystem;
             _appPaths = appPaths;
             _appPaths = appPaths;
         }
         }
 
 
@@ -417,7 +419,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
                             // We're not able to track progress
                             // We're not able to track progress
                             using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
                             using (var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
                             {
                             {
-                                using (var fs = new FileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
+                                using (var fs = _fileSystem.GetFileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read, true))
                                 {
                                 {
                                     await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false);
                                     await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false);
                                 }
                                 }
@@ -427,7 +429,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
                         {
                         {
                             using (var stream = ProgressStream.CreateReadProgressStream(await response.Content.ReadAsStreamAsync().ConfigureAwait(false), options.Progress.Report, contentLength.Value))
                             using (var stream = ProgressStream.CreateReadProgressStream(await response.Content.ReadAsStreamAsync().ConfigureAwait(false), options.Progress.Report, contentLength.Value))
                             {
                             {
-                                using (var fs = new FileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
+                                using (var fs = _fileSystem.GetFileStream(tempFile, FileMode.Create, FileAccess.Write, FileShare.Read, true))
                                 {
                                 {
                                     await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false);
                                     await stream.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, options.CancellationToken).ConfigureAwait(false);
                                 }
                                 }

+ 115 - 157
MediaBrowser.Controller/IO/FileSystem.cs → MediaBrowser.Common.Implementations/IO/CommonFileSystem.cs

@@ -1,122 +1,42 @@
-using System.Collections.Generic;
-using System.Linq;
+using MediaBrowser.Common.IO;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using System;
 using System;
-using System.Collections.Specialized;
 using System.IO;
 using System.IO;
 using System.Text;
 using System.Text;
 
 
-namespace MediaBrowser.Controller.IO
+namespace MediaBrowser.Common.Implementations.IO
 {
 {
     /// <summary>
     /// <summary>
-    /// Class FileSystem
+    /// Class CommonFileSystem
     /// </summary>
     /// </summary>
-    public static class FileSystem
+    public class CommonFileSystem : IFileSystem
     {
     {
-        /// <summary>
-        /// Gets the file system info.
-        /// </summary>
-        /// <param name="path">The path.</param>
-        /// <returns>FileSystemInfo.</returns>
-        public static FileSystemInfo GetFileSystemInfo(string path)
-        {
-            // Take a guess to try and avoid two file system hits, but we'll double-check by calling Exists
-            if (Path.HasExtension(path))
-            {
-                var fileInfo = new FileInfo(path);
-
-                if (fileInfo.Exists)
-                {
-                    return fileInfo;
-                }
-
-                return new DirectoryInfo(path);
-            }
-            else
-            {
-                var fileInfo = new DirectoryInfo(path);
-
-                if (fileInfo.Exists)
-                {
-                    return fileInfo;
-                }
+        protected ILogger Logger;
 
 
-                return new FileInfo(path);
-            }
-        }
+        private readonly bool _supportsAsyncFileStreams;
 
 
-        /// <summary>
-        /// Gets the creation time UTC.
-        /// </summary>
-        /// <param name="info">The info.</param>
-        /// <param name="logger">The logger.</param>
-        /// <returns>DateTime.</returns>
-        public static DateTime GetLastWriteTimeUtc(FileSystemInfo info, ILogger logger)
+        public CommonFileSystem(ILogger logger, bool supportsAsyncFileStreams)
         {
         {
-            // This could throw an error on some file systems that have dates out of range
-
-            try
-            {
-                return info.LastWriteTimeUtc;
-            }
-            catch (Exception ex)
-            {
-                logger.ErrorException("Error determining LastAccessTimeUtc for {0}", ex, info.FullName);
-                return DateTime.MinValue;
-            }
-        }
-
-        /// <summary>
-        /// Gets the creation time UTC.
-        /// </summary>
-        /// <param name="info">The info.</param>
-        /// <param name="logger">The logger.</param>
-        /// <returns>DateTime.</returns>
-        public static DateTime GetCreationTimeUtc(FileSystemInfo info, ILogger logger)
-        {
-            // This could throw an error on some file systems that have dates out of range
-
-            try
-            {
-                return info.CreationTimeUtc;
-            }
-            catch (Exception ex)
-            {
-                logger.ErrorException("Error determining CreationTimeUtc for {0}", ex, info.FullName);
-                return DateTime.MinValue;
-            }
+            Logger = logger;
+            _supportsAsyncFileStreams = supportsAsyncFileStreams;
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// The space char
-        /// </summary>
-        private const char SpaceChar = ' ';
-        /// <summary>
-        /// The invalid file name chars
-        /// </summary>
-        private static readonly char[] InvalidFileNameChars = Path.GetInvalidFileNameChars();
-
-        /// <summary>
-        /// Takes a filename and removes invalid characters
+        /// Determines whether the specified filename is shortcut.
         /// </summary>
         /// </summary>
         /// <param name="filename">The filename.</param>
         /// <param name="filename">The filename.</param>
-        /// <returns>System.String.</returns>
+        /// <returns><c>true</c> if the specified filename is shortcut; otherwise, <c>false</c>.</returns>
         /// <exception cref="System.ArgumentNullException">filename</exception>
         /// <exception cref="System.ArgumentNullException">filename</exception>
-        public static string GetValidFilename(string filename)
+        public virtual bool IsShortcut(string filename)
         {
         {
             if (string.IsNullOrEmpty(filename))
             if (string.IsNullOrEmpty(filename))
             {
             {
                 throw new ArgumentNullException("filename");
                 throw new ArgumentNullException("filename");
             }
             }
 
 
-            var builder = new StringBuilder(filename);
-
-            foreach (var c in InvalidFileNameChars)
-            {
-                builder = builder.Replace(c, SpaceChar);
-            }
+            var extension = Path.GetExtension(filename);
 
 
-            return builder.ToString();
+            return string.Equals(extension, ".mblink", StringComparison.OrdinalIgnoreCase);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -125,7 +45,7 @@ namespace MediaBrowser.Controller.IO
         /// <param name="filename">The filename.</param>
         /// <param name="filename">The filename.</param>
         /// <returns>System.String.</returns>
         /// <returns>System.String.</returns>
         /// <exception cref="System.ArgumentNullException">filename</exception>
         /// <exception cref="System.ArgumentNullException">filename</exception>
-        public static string ResolveShortcut(string filename)
+        public virtual string ResolveShortcut(string filename)
         {
         {
             if (string.IsNullOrEmpty(filename))
             if (string.IsNullOrEmpty(filename))
             {
             {
@@ -137,25 +57,20 @@ namespace MediaBrowser.Controller.IO
                 return File.ReadAllText(filename);
                 return File.ReadAllText(filename);
             }
             }
 
 
-            //return new WindowsShortcut(filename).ResolvedPath;
-
-            var link = new ShellLink();
-            ((IPersistFile)link).Load(filename, NativeMethods.STGM_READ);
-            // TODO: if I can get hold of the hwnd call resolve first. This handles moved and renamed files.  
-            // ((IShellLinkW)link).Resolve(hwnd, 0) 
-            var sb = new StringBuilder(NativeMethods.MAX_PATH);
-            WIN32_FIND_DATA data;
-            ((IShellLinkW)link).GetPath(sb, sb.Capacity, out data, 0);
-            return sb.ToString();
+            return null;
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Creates a shortcut file pointing to a specified path
+        /// Creates the shortcut.
         /// </summary>
         /// </summary>
         /// <param name="shortcutPath">The shortcut path.</param>
         /// <param name="shortcutPath">The shortcut path.</param>
         /// <param name="target">The target.</param>
         /// <param name="target">The target.</param>
-        /// <exception cref="System.ArgumentNullException">shortcutPath</exception>
-        public static void CreateShortcut(string shortcutPath, string target)
+        /// <exception cref="System.ArgumentNullException">
+        /// shortcutPath
+        /// or
+        /// target
+        /// </exception>
+        public void CreateShortcut(string shortcutPath, string target)
         {
         {
             if (string.IsNullOrEmpty(shortcutPath))
             if (string.IsNullOrEmpty(shortcutPath))
             {
             {
@@ -168,96 +83,138 @@ namespace MediaBrowser.Controller.IO
             }
             }
 
 
             File.WriteAllText(shortcutPath, target);
             File.WriteAllText(shortcutPath, target);
+        }
 
 
-            //var link = new ShellLink();
+        /// <summary>
+        /// Gets the file system info.
+        /// </summary>
+        /// <param name="path">The path.</param>
+        /// <returns>FileSystemInfo.</returns>
+        public FileSystemInfo GetFileSystemInfo(string path)
+        {
+            // Take a guess to try and avoid two file system hits, but we'll double-check by calling Exists
+            if (Path.HasExtension(path))
+            {
+                var fileInfo = new FileInfo(path);
+
+                if (fileInfo.Exists)
+                {
+                    return fileInfo;
+                }
 
 
-            //((IShellLinkW)link).SetPath(target);
+                return new DirectoryInfo(path);
+            }
+            else
+            {
+                var fileInfo = new DirectoryInfo(path);
+
+                if (fileInfo.Exists)
+                {
+                    return fileInfo;
+                }
 
 
-            //((IPersistFile)link).Save(shortcutPath, true);
+                return new FileInfo(path);
+            }
         }
         }
 
 
-        private static readonly Dictionary<string, string> ShortcutExtensionsDictionary = new[] { ".mblink", ".lnk" }
-            .ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
+        /// <summary>
+        /// The space char
+        /// </summary>
+        private const char SpaceChar = ' ';
+        /// <summary>
+        /// The invalid file name chars
+        /// </summary>
+        private static readonly char[] InvalidFileNameChars = Path.GetInvalidFileNameChars();
 
 
         /// <summary>
         /// <summary>
-        /// Determines whether the specified filename is shortcut.
+        /// Takes a filename and removes invalid characters
         /// </summary>
         /// </summary>
         /// <param name="filename">The filename.</param>
         /// <param name="filename">The filename.</param>
-        /// <returns><c>true</c> if the specified filename is shortcut; otherwise, <c>false</c>.</returns>
+        /// <returns>System.String.</returns>
         /// <exception cref="System.ArgumentNullException">filename</exception>
         /// <exception cref="System.ArgumentNullException">filename</exception>
-        public static bool IsShortcut(string filename)
+        public string GetValidFilename(string filename)
         {
         {
             if (string.IsNullOrEmpty(filename))
             if (string.IsNullOrEmpty(filename))
             {
             {
                 throw new ArgumentNullException("filename");
                 throw new ArgumentNullException("filename");
             }
             }
 
 
-            var extension = Path.GetExtension(filename);
+            var builder = new StringBuilder(filename);
 
 
-            return !string.IsNullOrEmpty(extension) && ShortcutExtensionsDictionary.ContainsKey(extension);
+            foreach (var c in InvalidFileNameChars)
+            {
+                builder = builder.Replace(c, SpaceChar);
+            }
+
+            return builder.ToString();
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Copies all.
+        /// Gets the creation time UTC.
         /// </summary>
         /// </summary>
-        /// <param name="source">The source.</param>
-        /// <param name="target">The target.</param>
-        /// <exception cref="System.ArgumentNullException">source</exception>
-        /// <exception cref="System.ArgumentException">The source and target directories are the same</exception>
-        public static void CopyAll(string source, string target)
+        /// <param name="info">The info.</param>
+        /// <returns>DateTime.</returns>
+        public DateTime GetCreationTimeUtc(FileSystemInfo info)
         {
         {
-            if (string.IsNullOrEmpty(source))
-            {
-                throw new ArgumentNullException("source");
-            }
-            if (string.IsNullOrEmpty(target))
+            // This could throw an error on some file systems that have dates out of range
+            try
             {
             {
-                throw new ArgumentNullException("target");
+                return info.CreationTimeUtc;
             }
             }
-
-            if (source.Equals(target, StringComparison.OrdinalIgnoreCase))
+            catch (Exception ex)
             {
             {
-                throw new ArgumentException("The source and target directories are the same");
+                Logger.ErrorException("Error determining CreationTimeUtc for {0}", ex, info.FullName);
+                return DateTime.MinValue;
             }
             }
+        }
 
 
-            // Check if the target directory exists, if not, create it. 
-            Directory.CreateDirectory(target);
-
-            foreach (var file in Directory.EnumerateFiles(source))
+        /// <summary>
+        /// Gets the creation time UTC.
+        /// </summary>
+        /// <param name="info">The info.</param>
+        /// <param name="logger">The logger.</param>
+        /// <returns>DateTime.</returns>
+        public DateTime GetLastWriteTimeUtc(FileSystemInfo info)
+        {
+            // This could throw an error on some file systems that have dates out of range
+            try
             {
             {
-                File.Copy(file, Path.Combine(target, Path.GetFileName(file)), true);
+                return info.LastWriteTimeUtc;
             }
             }
-
-            // Copy each subdirectory using recursion. 
-            foreach (var dir in Directory.EnumerateDirectories(source))
+            catch (Exception ex)
             {
             {
-                CopyAll(dir, Path.Combine(target, Path.GetFileName(dir)));
+                Logger.ErrorException("Error determining LastAccessTimeUtc for {0}", ex, info.FullName);
+                return DateTime.MinValue;
             }
             }
         }
         }
 
 
         /// <summary>
         /// <summary>
-        /// Parses the ini file.
+        /// Gets the last write time UTC.
         /// </summary>
         /// </summary>
         /// <param name="path">The path.</param>
         /// <param name="path">The path.</param>
-        /// <returns>NameValueCollection.</returns>
-        public static NameValueCollection ParseIniFile(string path)
+        /// <returns>DateTime.</returns>
+        public DateTime GetLastWriteTimeUtc(string path)
         {
         {
-            var values = new NameValueCollection();
+            return GetLastWriteTimeUtc(GetFileSystemInfo(path));
+        }
 
 
-            foreach (var line in File.ReadAllLines(path))
+        /// <summary>
+        /// Gets the file stream.
+        /// </summary>
+        /// <param name="path">The path.</param>
+        /// <param name="mode">The mode.</param>
+        /// <param name="access">The access.</param>
+        /// <param name="share">The share.</param>
+        /// <param name="isAsync">if set to <c>true</c> [is asynchronous].</param>
+        /// <returns>FileStream.</returns>
+        public FileStream GetFileStream(string path, FileMode mode, FileAccess access, FileShare share, bool isAsync = false)
+        {
+            if (_supportsAsyncFileStreams && isAsync)
             {
             {
-                var data = line.Split('=');
-
-                if (data.Length < 2) continue;
-
-                var key = data[0];
-
-                var value = data.Length == 2 ? data[1] : string.Join(string.Empty, data, 1, data.Length - 1);
-
-                values[key] = value;
+                return new FileStream(path, mode, access, share, 4096, true);
             }
             }
 
 
-            return values;
+            return new FileStream(path, mode, access, share);
         }
         }
     }
     }
 
 
@@ -381,4 +338,5 @@ namespace MediaBrowser.Controller.IO
         }
         }
 
 
     }
     }
+
 }
 }

+ 5 - 3
MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj

@@ -44,6 +44,10 @@
     <Reference Include="SharpCompress">
     <Reference Include="SharpCompress">
       <HintPath>..\packages\sharpcompress.0.10.1.3\lib\net40\SharpCompress.dll</HintPath>
       <HintPath>..\packages\sharpcompress.0.10.1.3\lib\net40\SharpCompress.dll</HintPath>
     </Reference>
     </Reference>
+    <Reference Include="SimpleInjector, Version=2.3.6.0, Culture=neutral, PublicKeyToken=984cb50dea722e99, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\packages\SimpleInjector.2.3.6\lib\net40-client\SimpleInjector.dll</HintPath>
+    </Reference>
     <Reference Include="System" />
     <Reference Include="System" />
     <Reference Include="System.Configuration" />
     <Reference Include="System.Configuration" />
     <Reference Include="System.Core" />
     <Reference Include="System.Core" />
@@ -54,9 +58,6 @@
     <Reference Include="ServiceStack.Text">
     <Reference Include="ServiceStack.Text">
       <HintPath>..\packages\ServiceStack.Text.3.9.62\lib\net35\ServiceStack.Text.dll</HintPath>
       <HintPath>..\packages\ServiceStack.Text.3.9.62\lib\net35\ServiceStack.Text.dll</HintPath>
     </Reference>
     </Reference>
-    <Reference Include="SimpleInjector">
-      <HintPath>..\packages\SimpleInjector.2.3.5\lib\net40-client\SimpleInjector.dll</HintPath>
-    </Reference>
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <Compile Include="..\SharedVersion.cs">
     <Compile Include="..\SharedVersion.cs">
@@ -68,6 +69,7 @@
     <Compile Include="Configuration\BaseConfigurationManager.cs" />
     <Compile Include="Configuration\BaseConfigurationManager.cs" />
     <Compile Include="HttpClientManager\HttpClientInfo.cs" />
     <Compile Include="HttpClientManager\HttpClientInfo.cs" />
     <Compile Include="HttpClientManager\HttpClientManager.cs" />
     <Compile Include="HttpClientManager\HttpClientManager.cs" />
+    <Compile Include="IO\CommonFileSystem.cs" />
     <Compile Include="IO\IsoManager.cs" />
     <Compile Include="IO\IsoManager.cs" />
     <Compile Include="Logging\LogHelper.cs" />
     <Compile Include="Logging\LogHelper.cs" />
     <Compile Include="Logging\NLogger.cs" />
     <Compile Include="Logging\NLogger.cs" />

+ 7 - 3
MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteCacheFileTask.cs

@@ -1,12 +1,13 @@
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.IO;
 using MediaBrowser.Common.ScheduledTasks;
 using MediaBrowser.Common.ScheduledTasks;
+using MediaBrowser.Model.Logging;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
-using MediaBrowser.Model.Logging;
 
 
 namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
 namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
 {
 {
@@ -23,14 +24,17 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
 
 
         private readonly ILogger _logger;
         private readonly ILogger _logger;
 
 
+        private readonly IFileSystem _fileSystem;
+        
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="DeleteCacheFileTask" /> class.
         /// Initializes a new instance of the <see cref="DeleteCacheFileTask" /> class.
         /// </summary>
         /// </summary>
         /// <param name="appPaths">The app paths.</param>
         /// <param name="appPaths">The app paths.</param>
-        public DeleteCacheFileTask(IApplicationPaths appPaths, ILogger logger)
+        public DeleteCacheFileTask(IApplicationPaths appPaths, ILogger logger, IFileSystem fileSystem)
         {
         {
             ApplicationPaths = appPaths;
             ApplicationPaths = appPaths;
             _logger = logger;
             _logger = logger;
+            _fileSystem = fileSystem;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -94,7 +98,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
         private void DeleteCacheFilesFromDirectory(CancellationToken cancellationToken, string directory, DateTime minDateModified, IProgress<double> progress)
         private void DeleteCacheFilesFromDirectory(CancellationToken cancellationToken, string directory, DateTime minDateModified, IProgress<double> progress)
         {
         {
             var filesToDelete = new DirectoryInfo(directory).EnumerateFiles("*", SearchOption.AllDirectories)
             var filesToDelete = new DirectoryInfo(directory).EnumerateFiles("*", SearchOption.AllDirectories)
-                .Where(f => f.LastWriteTimeUtc < minDateModified)
+                .Where(f => _fileSystem.GetLastWriteTimeUtc(f) < minDateModified)
                 .ToList();
                 .ToList();
 
 
             var index = 0;
             var index = 0;

+ 6 - 2
MediaBrowser.Common.Implementations/ScheduledTasks/Tasks/DeleteLogFileTask.cs

@@ -1,4 +1,5 @@
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.IO;
 using MediaBrowser.Common.ScheduledTasks;
 using MediaBrowser.Common.ScheduledTasks;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
@@ -20,13 +21,16 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
         /// <value>The configuration manager.</value>
         /// <value>The configuration manager.</value>
         private IConfigurationManager ConfigurationManager { get; set; }
         private IConfigurationManager ConfigurationManager { get; set; }
 
 
+        private readonly IFileSystem _fileSystem;
+
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="DeleteLogFileTask" /> class.
         /// Initializes a new instance of the <see cref="DeleteLogFileTask" /> class.
         /// </summary>
         /// </summary>
         /// <param name="configurationManager">The configuration manager.</param>
         /// <param name="configurationManager">The configuration manager.</param>
-        public DeleteLogFileTask(IConfigurationManager configurationManager)
+        public DeleteLogFileTask(IConfigurationManager configurationManager, IFileSystem fileSystem)
         {
         {
             ConfigurationManager = configurationManager;
             ConfigurationManager = configurationManager;
+            _fileSystem = fileSystem;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -58,7 +62,7 @@ namespace MediaBrowser.Common.Implementations.ScheduledTasks.Tasks
             var minDateModified = DateTime.UtcNow.AddDays(-(ConfigurationManager.CommonConfiguration.LogFileRetentionDays));
             var minDateModified = DateTime.UtcNow.AddDays(-(ConfigurationManager.CommonConfiguration.LogFileRetentionDays));
 
 
             var filesToDelete = new DirectoryInfo(ConfigurationManager.CommonApplicationPaths.LogDirectoryPath).EnumerateFileSystemInfos("*", SearchOption.AllDirectories)
             var filesToDelete = new DirectoryInfo(ConfigurationManager.CommonApplicationPaths.LogDirectoryPath).EnumerateFileSystemInfos("*", SearchOption.AllDirectories)
-                          .Where(f => f.LastWriteTimeUtc < minDateModified)
+                          .Where(f => _fileSystem.GetLastWriteTimeUtc(f) < minDateModified)
                           .ToList();
                           .ToList();
 
 
             var index = 0;
             var index = 0;

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

@@ -3,5 +3,5 @@
   <package id="NLog" version="2.1.0" targetFramework="net45" />
   <package id="NLog" version="2.1.0" targetFramework="net45" />
   <package id="ServiceStack.Text" version="3.9.62" targetFramework="net45" />
   <package id="ServiceStack.Text" version="3.9.62" targetFramework="net45" />
   <package id="sharpcompress" version="0.10.1.3" targetFramework="net45" />
   <package id="sharpcompress" version="0.10.1.3" targetFramework="net45" />
-  <package id="SimpleInjector" version="2.3.5" targetFramework="net45" />
+  <package id="SimpleInjector" version="2.3.6" targetFramework="net45" />
 </packages>
 </packages>

+ 78 - 0
MediaBrowser.Common/IO/IFileSystem.cs

@@ -0,0 +1,78 @@
+using System;
+using System.IO;
+
+namespace MediaBrowser.Common.IO
+{
+    /// <summary>
+    /// Interface IFileSystem
+    /// </summary>
+    public interface IFileSystem
+    {
+        /// <summary>
+        /// Determines whether the specified filename is shortcut.
+        /// </summary>
+        /// <param name="filename">The filename.</param>
+        /// <returns><c>true</c> if the specified filename is shortcut; otherwise, <c>false</c>.</returns>
+        bool IsShortcut(string filename);
+
+        /// <summary>
+        /// Resolves the shortcut.
+        /// </summary>
+        /// <param name="filename">The filename.</param>
+        /// <returns>System.String.</returns>
+        string ResolveShortcut(string filename);
+
+        /// <summary>
+        /// Creates the shortcut.
+        /// </summary>
+        /// <param name="shortcutPath">The shortcut path.</param>
+        /// <param name="target">The target.</param>
+        void CreateShortcut(string shortcutPath, string target);
+
+        /// <summary>
+        /// Gets the file system info.
+        /// </summary>
+        /// <param name="path">The path.</param>
+        /// <returns>FileSystemInfo.</returns>
+        FileSystemInfo GetFileSystemInfo(string path);
+
+        /// <summary>
+        /// Gets the valid filename.
+        /// </summary>
+        /// <param name="filename">The filename.</param>
+        /// <returns>System.String.</returns>
+        string GetValidFilename(string filename);
+
+        /// <summary>
+        /// Gets the creation time UTC.
+        /// </summary>
+        /// <param name="info">The info.</param>
+        /// <returns>DateTime.</returns>
+        DateTime GetCreationTimeUtc(FileSystemInfo info);
+
+        /// <summary>
+        /// Gets the last write time UTC.
+        /// </summary>
+        /// <param name="info">The information.</param>
+        /// <returns>DateTime.</returns>
+        DateTime GetLastWriteTimeUtc(FileSystemInfo info);
+
+        /// <summary>
+        /// Gets the last write time UTC.
+        /// </summary>
+        /// <param name="path">The path.</param>
+        /// <returns>DateTime.</returns>
+        DateTime GetLastWriteTimeUtc(string path);
+
+        /// <summary>
+        /// Gets the file stream.
+        /// </summary>
+        /// <param name="path">The path.</param>
+        /// <param name="mode">The mode.</param>
+        /// <param name="access">The access.</param>
+        /// <param name="share">The share.</param>
+        /// <param name="isAsync">if set to <c>true</c> [is asynchronous].</param>
+        /// <returns>FileStream.</returns>
+        FileStream GetFileStream(string path, FileMode mode, FileAccess access, FileShare share, bool isAsync = false);
+    }
+}

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

@@ -60,6 +60,7 @@
     <Compile Include="Events\GenericEventArgs.cs" />
     <Compile Include="Events\GenericEventArgs.cs" />
     <Compile Include="Extensions\ResourceNotFoundException.cs" />
     <Compile Include="Extensions\ResourceNotFoundException.cs" />
     <Compile Include="IO\FileSystemRepository.cs" />
     <Compile Include="IO\FileSystemRepository.cs" />
+    <Compile Include="IO\IFileSystem.cs" />
     <Compile Include="IO\ProgressStream.cs" />
     <Compile Include="IO\ProgressStream.cs" />
     <Compile Include="IO\StreamDefaults.cs" />
     <Compile Include="IO\StreamDefaults.cs" />
     <Compile Include="MediaInfo\MediaInfoResult.cs" />
     <Compile Include="MediaInfo\MediaInfoResult.cs" />

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

@@ -1,4 +1,5 @@
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.IO;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.IO;
@@ -212,6 +213,7 @@ namespace MediaBrowser.Controller.Entities
         public static IProviderManager ProviderManager { get; set; }
         public static IProviderManager ProviderManager { get; set; }
         public static ILocalizationManager LocalizationManager { get; set; }
         public static ILocalizationManager LocalizationManager { get; set; }
         public static IItemRepository ItemRepository { get; set; }
         public static IItemRepository ItemRepository { get; set; }
+        public static IFileSystem FileSystem { get; set; }
 
 
         /// <summary>
         /// <summary>
         /// Returns a <see cref="System.String" /> that represents this instance.
         /// Returns a <see cref="System.String" /> that represents this instance.
@@ -395,7 +397,7 @@ namespace MediaBrowser.Controller.Entities
                 // When resolving the root, we need it's grandchildren (children of user views)
                 // When resolving the root, we need it's grandchildren (children of user views)
                 var flattenFolderDepth = isPhysicalRoot ? 2 : 0;
                 var flattenFolderDepth = isPhysicalRoot ? 2 : 0;
 
 
-                args.FileSystemDictionary = FileData.GetFilteredFileSystemEntries(args.Path, Logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf);
+                args.FileSystemDictionary = FileData.GetFilteredFileSystemEntries(args.Path, FileSystem, Logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf);
 
 
                 // Need to remove subpaths that may have been resolved from shortcuts
                 // Need to remove subpaths that may have been resolved from shortcuts
                 // Example: if \\server\movies exists, then strip out \\server\movies\action
                 // Example: if \\server\movies exists, then strip out \\server\movies\action
@@ -413,7 +415,7 @@ namespace MediaBrowser.Controller.Entities
             }
             }
 
 
             //update our dates
             //update our dates
-            EntityResolutionHelper.EnsureDates(this, args, false);
+            EntityResolutionHelper.EnsureDates(FileSystem, this, args, false);
 
 
             IsOffline = false;
             IsOffline = false;
 
 
@@ -1337,6 +1339,13 @@ namespace MediaBrowser.Controller.Entities
 
 
             var data = userManager.GetUserData(user.Id, key);
             var data = userManager.GetUserData(user.Id, key);
 
 
+            if (datePlayed.HasValue)
+            {
+                // Incremenet
+                data.PlayCount++;
+            }
+
+            // Ensure it's at least one
             data.PlayCount = Math.Max(data.PlayCount, 1);
             data.PlayCount = Math.Max(data.PlayCount, 1);
 
 
             data.LastPlayedDate = datePlayed ?? data.LastPlayedDate;
             data.LastPlayedDate = datePlayed ?? data.LastPlayedDate;
@@ -1530,7 +1539,8 @@ namespace MediaBrowser.Controller.Entities
             }
             }
 
 
             // Refresh metadata
             // Refresh metadata
-            return RefreshMetadata(CancellationToken.None, forceSave: true);
+            // Need to disable slow providers or the image might get re-downloaded
+            return RefreshMetadata(CancellationToken.None, forceSave: true, allowSlowProviders: false);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -1728,7 +1738,7 @@ namespace MediaBrowser.Controller.Entities
             if (locationType == LocationType.Remote ||
             if (locationType == LocationType.Remote ||
                 locationType == LocationType.Virtual)
                 locationType == LocationType.Virtual)
             {
             {
-                return File.GetLastWriteTimeUtc(imagePath);
+                return FileSystem.GetLastWriteTimeUtc(imagePath);
             }
             }
 
 
             var metaFileEntry = ResolveArgs.GetMetaFileByPath(imagePath);
             var metaFileEntry = ResolveArgs.GetMetaFileByPath(imagePath);
@@ -1745,7 +1755,7 @@ namespace MediaBrowser.Controller.Entities
             }
             }
 
 
             // See if we can avoid a file system lookup by looking for the file in ResolveArgs
             // See if we can avoid a file system lookup by looking for the file in ResolveArgs
-            return metaFileEntry == null ? File.GetLastWriteTimeUtc(imagePath) : metaFileEntry.LastWriteTimeUtc;
+            return metaFileEntry == null ? FileSystem.GetLastWriteTimeUtc(imagePath) : FileSystem.GetLastWriteTimeUtc(metaFileEntry);
         }
         }
     }
     }
 }
 }

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

@@ -693,7 +693,7 @@ namespace MediaBrowser.Controller.Entities
                     //existing item - check if it has changed
                     //existing item - check if it has changed
                     if (currentChild.HasChanged(child))
                     if (currentChild.HasChanged(child))
                     {
                     {
-                        EntityResolutionHelper.EnsureDates(currentChild, child.ResolveArgs, false);
+                        EntityResolutionHelper.EnsureDates(FileSystem, currentChild, child.ResolveArgs, false);
 
 
                         validChildren.Add(new Tuple<BaseItem, bool>(currentChild, true));
                         validChildren.Add(new Tuple<BaseItem, bool>(currentChild, true));
                     }
                     }

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

@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using System.Runtime.Serialization;
 using System.Runtime.Serialization;
 
 
@@ -191,5 +192,23 @@ namespace MediaBrowser.Controller.Entities.TV
 
 
             return false;
             return false;
         }
         }
+
+        public bool IsMissingEpisode
+        {
+            get
+            {
+                return LocationType == Model.Entities.LocationType.Virtual && PremiereDate.HasValue && PremiereDate.Value < DateTime.UtcNow;
+            }
+        }
+
+        public bool IsUnaired
+        {
+            get { return PremiereDate.HasValue && PremiereDate.Value.ToLocalTime().Date >= DateTime.Now.Date; }
+        }
+
+        public bool IsVirtualUnaired
+        {
+            get { return LocationType == Model.Entities.LocationType.Virtual && IsUnaired; }
+        }
     }
     }
 }
 }

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

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Library;
+using System.Linq;
+using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Localization;
 using MediaBrowser.Controller.Localization;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
@@ -147,5 +148,25 @@ namespace MediaBrowser.Controller.Entities.TV
         {
         {
             return IndexNumber != null ? IndexNumber.Value.ToString("0000") : Name;
             return IndexNumber != null ? IndexNumber.Value.ToString("0000") : Name;
         }
         }
+
+        public bool IsMissingSeason
+        {
+            get { return LocationType == Model.Entities.LocationType.Virtual && Children.OfType<Episode>().All(i => i.IsMissingEpisode); }
+        }
+
+        public bool IsUnaired
+        {
+            get { return Children.OfType<Episode>().All(i => i.IsUnaired); }
+        }
+
+        public bool IsVirtualUnaired
+        {
+            get { return LocationType == Model.Entities.LocationType.Virtual && IsUnaired; }
+        }
+
+        public bool IsMissingOrVirtualUnaired
+        {
+            get { return LocationType == Model.Entities.LocationType.Virtual && Children.OfType<Episode>().All(i => i.IsVirtualUnaired || i.IsMissingEpisode); }
+        }
     }
     }
 }
 }

+ 1 - 1
MediaBrowser.Controller/Entities/User.cs

@@ -165,7 +165,7 @@ namespace MediaBrowser.Controller.Entities
                 // Ensure it's been lazy loaded
                 // Ensure it's been lazy loaded
                 var config = Configuration;
                 var config = Configuration;
 
 
-                return File.GetLastWriteTimeUtc(ConfigurationFilePath);
+                return FileSystem.GetLastWriteTimeUtc(ConfigurationFilePath);
             }
             }
         }
         }
 
 

+ 7 - 5
MediaBrowser.Controller/IO/FileData.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Library;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
@@ -15,6 +16,7 @@ namespace MediaBrowser.Controller.IO
         /// Gets the filtered file system entries.
         /// Gets the filtered file system entries.
         /// </summary>
         /// </summary>
         /// <param name="path">The path.</param>
         /// <param name="path">The path.</param>
+        /// <param name="fileSystem">The file system.</param>
         /// <param name="logger">The logger.</param>
         /// <param name="logger">The logger.</param>
         /// <param name="args">The args.</param>
         /// <param name="args">The args.</param>
         /// <param name="searchPattern">The search pattern.</param>
         /// <param name="searchPattern">The search pattern.</param>
@@ -22,7 +24,7 @@ namespace MediaBrowser.Controller.IO
         /// <param name="resolveShortcuts">if set to <c>true</c> [resolve shortcuts].</param>
         /// <param name="resolveShortcuts">if set to <c>true</c> [resolve shortcuts].</param>
         /// <returns>Dictionary{System.StringFileSystemInfo}.</returns>
         /// <returns>Dictionary{System.StringFileSystemInfo}.</returns>
         /// <exception cref="System.ArgumentNullException">path</exception>
         /// <exception cref="System.ArgumentNullException">path</exception>
-        public static Dictionary<string, FileSystemInfo> GetFilteredFileSystemEntries(string path, ILogger logger, ItemResolveArgs args, string searchPattern = "*", int flattenFolderDepth = 0, bool resolveShortcuts = true)
+        public static Dictionary<string, FileSystemInfo> GetFilteredFileSystemEntries(string path, IFileSystem fileSystem, ILogger logger, ItemResolveArgs args, string searchPattern = "*", int flattenFolderDepth = 0, bool resolveShortcuts = true)
         {
         {
             if (string.IsNullOrEmpty(path))
             if (string.IsNullOrEmpty(path))
             {
             {
@@ -56,9 +58,9 @@ namespace MediaBrowser.Controller.IO
 
 
                 var fullName = entry.FullName;
                 var fullName = entry.FullName;
 
 
-                if (resolveShortcuts && FileSystem.IsShortcut(fullName))
+                if (resolveShortcuts && fileSystem.IsShortcut(fullName))
                 {
                 {
-                    var newPath = FileSystem.ResolveShortcut(fullName);
+                    var newPath = fileSystem.ResolveShortcut(fullName);
 
 
                     if (string.IsNullOrWhiteSpace(newPath))
                     if (string.IsNullOrWhiteSpace(newPath))
                     {
                     {
@@ -77,7 +79,7 @@ namespace MediaBrowser.Controller.IO
                 }
                 }
                 else if (flattenFolderDepth > 0 && isDirectory)
                 else if (flattenFolderDepth > 0 && isDirectory)
                 {
                 {
-                    foreach (var child in GetFilteredFileSystemEntries(fullName, logger, args, flattenFolderDepth: flattenFolderDepth - 1, resolveShortcuts: resolveShortcuts))
+                    foreach (var child in GetFilteredFileSystemEntries(fullName, fileSystem, logger, args, flattenFolderDepth: flattenFolderDepth - 1, resolveShortcuts: resolveShortcuts))
                     {
                     {
                         dict[child.Key] = child.Value;
                         dict[child.Key] = child.Value;
                     }
                     }

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

@@ -108,6 +108,7 @@
     <Compile Include="Notifications\INotificationsRepository.cs" />
     <Compile Include="Notifications\INotificationsRepository.cs" />
     <Compile Include="Notifications\NotificationUpdateEventArgs.cs" />
     <Compile Include="Notifications\NotificationUpdateEventArgs.cs" />
     <Compile Include="Providers\IDynamicInfoProvider.cs" />
     <Compile Include="Providers\IDynamicInfoProvider.cs" />
+    <Compile Include="Providers\IImageProvider.cs" />
     <Compile Include="Session\ISessionManager.cs" />
     <Compile Include="Session\ISessionManager.cs" />
     <Compile Include="Drawing\ImageExtensions.cs" />
     <Compile Include="Drawing\ImageExtensions.cs" />
     <Compile Include="Entities\AggregateFolder.cs" />
     <Compile Include="Entities\AggregateFolder.cs" />
@@ -139,9 +140,7 @@
     <Compile Include="Entities\Video.cs" />
     <Compile Include="Entities\Video.cs" />
     <Compile Include="Entities\CollectionFolder.cs" />
     <Compile Include="Entities\CollectionFolder.cs" />
     <Compile Include="Entities\Year.cs" />
     <Compile Include="Entities\Year.cs" />
-    <Compile Include="IO\FileSystem.cs" />
     <Compile Include="IO\IDirectoryWatchers.cs" />
     <Compile Include="IO\IDirectoryWatchers.cs" />
-    <Compile Include="IO\NativeMethods.cs" />
     <Compile Include="IServerApplicationHost.cs" />
     <Compile Include="IServerApplicationHost.cs" />
     <Compile Include="IServerApplicationPaths.cs" />
     <Compile Include="IServerApplicationPaths.cs" />
     <Compile Include="Library\SearchHintInfo.cs" />
     <Compile Include="Library\SearchHintInfo.cs" />

+ 6 - 2
MediaBrowser.Controller/MediaInfo/FFMpegManager.cs

@@ -1,6 +1,7 @@
 using MediaBrowser.Common.IO;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Common.MediaInfo;
 using MediaBrowser.Common.MediaInfo;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
@@ -35,6 +36,8 @@ namespace MediaBrowser.Controller.MediaInfo
         private readonly ILogger _logger;
         private readonly ILogger _logger;
         private readonly IItemRepository _itemRepo;
         private readonly IItemRepository _itemRepo;
 
 
+        private readonly IFileSystem _fileSystem;
+
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="FFMpegManager" /> class.
         /// Initializes a new instance of the <see cref="FFMpegManager" /> class.
         /// </summary>
         /// </summary>
@@ -43,12 +46,13 @@ namespace MediaBrowser.Controller.MediaInfo
         /// <param name="logger">The logger.</param>
         /// <param name="logger">The logger.</param>
         /// <param name="itemRepo">The item repo.</param>
         /// <param name="itemRepo">The item repo.</param>
         /// <exception cref="System.ArgumentNullException">zipClient</exception>
         /// <exception cref="System.ArgumentNullException">zipClient</exception>
-        public FFMpegManager(IServerApplicationPaths appPaths, IMediaEncoder encoder, ILogger logger, IItemRepository itemRepo)
+        public FFMpegManager(IServerApplicationPaths appPaths, IMediaEncoder encoder, ILogger logger, IItemRepository itemRepo, IFileSystem fileSystem)
         {
         {
             _appPaths = appPaths;
             _appPaths = appPaths;
             _encoder = encoder;
             _encoder = encoder;
             _logger = logger;
             _logger = logger;
             _itemRepo = itemRepo;
             _itemRepo = itemRepo;
+            _fileSystem = fileSystem;
 
 
             VideoImageCache = new FileSystemRepository(VideoImagesDataPath);
             VideoImageCache = new FileSystemRepository(VideoImagesDataPath);
             SubtitleCache = new FileSystemRepository(SubtitleCachePath);
             SubtitleCache = new FileSystemRepository(SubtitleCachePath);
@@ -203,7 +207,7 @@ namespace MediaBrowser.Controller.MediaInfo
 
 
             if (stream.IsExternal)
             if (stream.IsExternal)
             {
             {
-                ticksParam += File.GetLastWriteTimeUtc(stream.Path).Ticks;
+                ticksParam += _fileSystem.GetLastWriteTimeUtc(stream.Path).Ticks;
             }
             }
 
 
             return SubtitleCache.GetResourcePath(input.Id + "_" + subtitleStreamIndex + "_" + input.DateModified.Ticks + ticksParam, outputExtension);
             return SubtitleCache.GetResourcePath(input.Id + "_" + subtitleStreamIndex + "_" + input.DateModified.Ticks + ticksParam, outputExtension);

+ 0 - 39
MediaBrowser.Controller/Providers/BaseItemXmlParser.cs

@@ -552,32 +552,6 @@ namespace MediaBrowser.Controller.Providers
                     }
                     }
                     break;
                     break;
 
 
-                case "GamesDbId":
-                    var gamesdbId = reader.ReadElementContentAsString();
-                    if (!string.IsNullOrWhiteSpace(gamesdbId))
-                    {
-                        item.SetProviderId(MetadataProviders.Gamesdb, gamesdbId);
-                    }
-                    break;
-
-                case "Players":
-                    {
-                        var val = reader.ReadElementContentAsString();
-                        if (!string.IsNullOrWhiteSpace(val))
-                        {
-                            int num;
-
-                            if (int.TryParse(val, NumberStyles.Integer, _usCulture, out num))
-                            {
-                                var game = item as Game;
-                                if (game != null)
-                                {
-                                    game.PlayersSupported = num;
-                                }
-                            }
-                        }
-                        break;
-                    }
                 case "VoteCount":
                 case "VoteCount":
                     {
                     {
                         var val = reader.ReadElementContentAsString();
                         var val = reader.ReadElementContentAsString();
@@ -592,19 +566,6 @@ namespace MediaBrowser.Controller.Providers
                         }
                         }
                         break;
                         break;
                     }
                     }
-                case "GameSystem":
-                    {
-                        var val = reader.ReadElementContentAsString();
-                        if (!string.IsNullOrWhiteSpace(val))
-                        {
-                            var game = item as Game;
-                            if (game != null)
-                            {
-                                game.GameSystem = val;
-                            }
-                        }
-                        break;
-                    }
                 case "MusicbrainzId":
                 case "MusicbrainzId":
                     {
                     {
                         var mbz = reader.ReadElementContentAsString();
                         var mbz = reader.ReadElementContentAsString();

+ 38 - 0
MediaBrowser.Controller/Providers/IImageProvider.cs

@@ -0,0 +1,38 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Controller.Providers
+{
+    /// <summary>
+    /// Interface IImageProvider
+    /// </summary>
+    public interface IImageProvider
+    {
+        /// <summary>
+        /// Gets the name.
+        /// </summary>
+        /// <value>The name.</value>
+        string Name { get; }
+
+        /// <summary>
+        /// Supportses the specified item.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <param name="imageType">Type of the image.</param>
+        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
+        bool Supports(BaseItem item, ImageType imageType);
+
+        /// <summary>
+        /// Gets the available images.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <param name="imageType">Type of the image.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task{IEnumerable{RemoteImageInfo}}.</returns>
+        Task<IEnumerable<RemoteImageInfo>> GetAvailableImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken);
+    }
+}

+ 12 - 1
MediaBrowser.Controller/Providers/IProviderManager.cs

@@ -1,6 +1,7 @@
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.IO;
 using System.IO;
 using System.Threading;
 using System.Threading;
@@ -52,6 +53,16 @@ namespace MediaBrowser.Controller.Providers
         /// Adds the metadata providers.
         /// Adds the metadata providers.
         /// </summary>
         /// </summary>
         /// <param name="providers">The providers.</param>
         /// <param name="providers">The providers.</param>
-        void AddParts(IEnumerable<BaseMetadataProvider> providers);
+        /// <param name="imageProviders">The image providers.</param>
+        void AddParts(IEnumerable<BaseMetadataProvider> providers, IEnumerable<IImageProvider> imageProviders);
+
+        /// <summary>
+        /// Gets the available remote images.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <param name="type">The type.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task{IEnumerable{RemoteImageInfo}}.</returns>
+        Task<IEnumerable<RemoteImageInfo>> GetAvailableRemoteImages(BaseItem item, ImageType type, CancellationToken cancellationToken);
     }
     }
 }
 }

+ 13 - 10
MediaBrowser.Controller/Resolvers/EntityResolutionHelper.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Entities;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using System;
 using System;
@@ -44,7 +45,8 @@ namespace MediaBrowser.Controller.Resolvers
                 ".f4v",
                 ".f4v",
                 ".3gp",
                 ".3gp",
                 ".webm",
                 ".webm",
-                ".mts"
+                ".mts",
+                ".rec"
         };
         };
 
 
         private static readonly Dictionary<string, string> VideoFileExtensionsDictionary = VideoFileExtensions.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
         private static readonly Dictionary<string, string> VideoFileExtensionsDictionary = VideoFileExtensions.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
@@ -125,10 +127,11 @@ namespace MediaBrowser.Controller.Resolvers
         /// <summary>
         /// <summary>
         /// Ensures DateCreated and DateModified have values
         /// Ensures DateCreated and DateModified have values
         /// </summary>
         /// </summary>
+        /// <param name="fileSystem">The file system.</param>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <param name="args">The args.</param>
         /// <param name="args">The args.</param>
         /// <param name="includeCreationTime">if set to <c>true</c> [include creation time].</param>
         /// <param name="includeCreationTime">if set to <c>true</c> [include creation time].</param>
-        public static void EnsureDates(BaseItem item, ItemResolveArgs args, bool includeCreationTime)
+        public static void EnsureDates(IFileSystem fileSystem, BaseItem item, ItemResolveArgs args, bool includeCreationTime)
         {
         {
             if (!Path.IsPathRooted(item.Path))
             if (!Path.IsPathRooted(item.Path))
             {
             {
@@ -144,22 +147,22 @@ namespace MediaBrowser.Controller.Resolvers
                 {
                 {
                     if (includeCreationTime)
                     if (includeCreationTime)
                     {
                     {
-                        item.DateCreated = childData.CreationTimeUtc;
+                        item.DateCreated = fileSystem.GetCreationTimeUtc(childData);
                     }
                     }
 
 
-                    item.DateModified = childData.LastWriteTimeUtc;
+                    item.DateModified = fileSystem.GetLastWriteTimeUtc(childData);
                 }
                 }
                 else
                 else
                 {
                 {
-                    var fileData = FileSystem.GetFileSystemInfo(item.Path);
+                    var fileData = fileSystem.GetFileSystemInfo(item.Path);
 
 
                     if (fileData.Exists)
                     if (fileData.Exists)
                     {
                     {
                         if (includeCreationTime)
                         if (includeCreationTime)
                         {
                         {
-                            item.DateCreated = fileData.CreationTimeUtc;
+                            item.DateCreated = fileSystem.GetCreationTimeUtc(fileData);
                         }
                         }
-                        item.DateModified = fileData.LastWriteTimeUtc;
+                        item.DateModified = fileSystem.GetLastWriteTimeUtc(fileData);
                     }
                     }
                 }
                 }
             }
             }
@@ -167,9 +170,9 @@ namespace MediaBrowser.Controller.Resolvers
             {
             {
                 if (includeCreationTime)
                 if (includeCreationTime)
                 {
                 {
-                    item.DateCreated = args.FileInfo.CreationTimeUtc;
+                    item.DateCreated = fileSystem.GetCreationTimeUtc(args.FileInfo);
                 }
                 }
-                item.DateModified = args.FileInfo.LastWriteTimeUtc;
+                item.DateModified = fileSystem.GetLastWriteTimeUtc(args.FileInfo);
             }
             }
         }
         }
     }
     }

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

@@ -290,6 +290,9 @@
     <Compile Include="..\MediaBrowser.Model\Plugins\PluginInfo.cs">
     <Compile Include="..\MediaBrowser.Model\Plugins\PluginInfo.cs">
       <Link>Plugins\PluginInfo.cs</Link>
       <Link>Plugins\PluginInfo.cs</Link>
     </Compile>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\Providers\RemoteImageInfo.cs">
+      <Link>Providers\RemoteImageInfo.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\Querying\ArtistsQuery.cs">
     <Compile Include="..\MediaBrowser.Model\Querying\ArtistsQuery.cs">
       <Link>Querying\ArtistsQuery.cs</Link>
       <Link>Querying\ArtistsQuery.cs</Link>
     </Compile>
     </Compile>

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

@@ -277,6 +277,9 @@
     <Compile Include="..\MediaBrowser.Model\Plugins\PluginInfo.cs">
     <Compile Include="..\MediaBrowser.Model\Plugins\PluginInfo.cs">
       <Link>Plugins\PluginInfo.cs</Link>
       <Link>Plugins\PluginInfo.cs</Link>
     </Compile>
     </Compile>
+    <Compile Include="..\MediaBrowser.Model\Providers\RemoteImageInfo.cs">
+      <Link>Providers\RemoteImageInfo.cs</Link>
+    </Compile>
     <Compile Include="..\MediaBrowser.Model\Querying\ArtistsQuery.cs">
     <Compile Include="..\MediaBrowser.Model\Querying\ArtistsQuery.cs">
       <Link>Querying\ArtistsQuery.cs</Link>
       <Link>Querying\ArtistsQuery.cs</Link>
     </Compile>
     </Compile>

+ 5 - 3
MediaBrowser.Model/Configuration/UserConfiguration.cs

@@ -56,16 +56,18 @@ namespace MediaBrowser.Model.Configuration
 
 
         public bool IsDisabled { get; set; }
         public bool IsDisabled { get; set; }
 
 
-        public bool DisplayVirtualEpisodes { get; set; }
-        
+        public bool DisplayMissingEpisodes { get; set; }
+        public bool DisplayUnairedEpisodes { get; set; }
+        public bool EnableRemoteControlOfOtherUsers { get; set; }
+
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="UserConfiguration" /> class.
         /// Initializes a new instance of the <see cref="UserConfiguration" /> class.
         /// </summary>
         /// </summary>
         public UserConfiguration()
         public UserConfiguration()
         {
         {
             IsAdministrator = true;
             IsAdministrator = true;
+            EnableRemoteControlOfOtherUsers = true;
             BlockNotRated = false;
             BlockNotRated = false;
-            DisplayVirtualEpisodes = true;
         }
         }
     }
     }
 }
 }

+ 3 - 1
MediaBrowser.Model/Entities/MetadataProviders.cs

@@ -36,6 +36,8 @@ namespace MediaBrowser.Model.Entities
         /// </summary>
         /// </summary>
         TmdbCollection,
         TmdbCollection,
         MusicBrainzReleaseGroup,
         MusicBrainzReleaseGroup,
-        Zap2It
+        Zap2It,
+        NesBox,
+        NesBoxRom
     }
     }
 }
 }

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

@@ -59,6 +59,7 @@
     <Compile Include="Dto\ItemByNameCounts.cs" />
     <Compile Include="Dto\ItemByNameCounts.cs" />
     <Compile Include="Dto\ItemCounts.cs" />
     <Compile Include="Dto\ItemCounts.cs" />
     <Compile Include="Dto\ItemIndex.cs" />
     <Compile Include="Dto\ItemIndex.cs" />
+    <Compile Include="Providers\RemoteImageInfo.cs" />
     <Compile Include="Dto\StudioDto.cs" />
     <Compile Include="Dto\StudioDto.cs" />
     <Compile Include="Entities\CollectionType.cs" />
     <Compile Include="Entities\CollectionType.cs" />
     <Compile Include="Entities\ItemReview.cs" />
     <Compile Include="Entities\ItemReview.cs" />

+ 59 - 0
MediaBrowser.Model/Providers/RemoteImageInfo.cs

@@ -0,0 +1,59 @@
+
+using MediaBrowser.Model.Entities;
+
+namespace MediaBrowser.Model.Providers
+{
+    /// <summary>
+    /// Class RemoteImageInfo
+    /// </summary>
+    public class RemoteImageInfo
+    {
+        /// <summary>
+        /// Gets or sets the name of the provider.
+        /// </summary>
+        /// <value>The name of the provider.</value>
+        public string ProviderName { get; set; }
+
+        /// <summary>
+        /// Gets or sets the URL.
+        /// </summary>
+        /// <value>The URL.</value>
+        public string Url { get; set; }
+
+        /// <summary>
+        /// Gets or sets the height.
+        /// </summary>
+        /// <value>The height.</value>
+        public int? Height { get; set; }
+
+        /// <summary>
+        /// Gets or sets the width.
+        /// </summary>
+        /// <value>The width.</value>
+        public int? Width { get; set; }
+
+        /// <summary>
+        /// Gets or sets the community rating.
+        /// </summary>
+        /// <value>The community rating.</value>
+        public double? CommunityRating { get; set; }
+
+        /// <summary>
+        /// Gets or sets the vote count.
+        /// </summary>
+        /// <value>The vote count.</value>
+        public int? VoteCount { get; set; }
+
+        /// <summary>
+        /// Gets or sets the language.
+        /// </summary>
+        /// <value>The language.</value>
+        public string Language { get; set; }
+
+        /// <summary>
+        /// Gets or sets the type.
+        /// </summary>
+        /// <value>The type.</value>
+        public ImageType Type { get; set; }
+    }
+}

+ 16 - 5
MediaBrowser.Model/Querying/ItemQuery.cs

@@ -241,16 +241,27 @@ namespace MediaBrowser.Model.Querying
         /// </summary>
         /// </summary>
         /// <value>The location types.</value>
         /// <value>The location types.</value>
         public LocationType[] LocationTypes { get; set; }
         public LocationType[] LocationTypes { get; set; }
+
+        /// <summary>
+        /// Gets or sets a value indicating whether this instance is missing episode.
+        /// </summary>
+        /// <value><c>null</c> if [is missing episode] contains no value, <c>true</c> if [is missing episode]; otherwise, <c>false</c>.</value>
+        public bool? IsMissing { get; set; }
+
+        /// <summary>
+        /// Gets or sets a value indicating whether this instance is unaired episode.
+        /// </summary>
+        /// <value><c>null</c> if [is unaired episode] contains no value, <c>true</c> if [is unaired episode]; otherwise, <c>false</c>.</value>
+        public bool? IsUnaired { get; set; }
+
+        public bool? IsVirtualUnaired { get; set; }
+        
         /// <summary>
         /// <summary>
         /// Gets or sets the exclude location types.
         /// Gets or sets the exclude location types.
         /// </summary>
         /// </summary>
         /// <value>The exclude location types.</value>
         /// <value>The exclude location types.</value>
         public LocationType[] ExcludeLocationTypes { get; set; }
         public LocationType[] ExcludeLocationTypes { get; set; }
-
-        public bool? HasPremiereDate { get; set; }
-        public DateTime? MinPremiereDate { get; set; }
-        public DateTime? MaxPremiereDate { get; set; }
-
+        
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="ItemQuery" /> class.
         /// Initializes a new instance of the <see cref="ItemQuery" /> class.
         /// </summary>
         /// </summary>

+ 7 - 18
MediaBrowser.Model/Querying/NextUpQuery.cs

@@ -1,6 +1,4 @@
-using MediaBrowser.Model.Entities;
-using System;
-
+
 namespace MediaBrowser.Model.Querying
 namespace MediaBrowser.Model.Querying
 {
 {
     public class NextUpQuery
     public class NextUpQuery
@@ -11,6 +9,12 @@ namespace MediaBrowser.Model.Querying
         /// <value>The user id.</value>
         /// <value>The user id.</value>
         public string UserId { get; set; }
         public string UserId { get; set; }
 
 
+        /// <summary>
+        /// Gets or sets the series id.
+        /// </summary>
+        /// <value>The series id.</value>
+        public string SeriesId { get; set; }
+        
         /// <summary>
         /// <summary>
         /// Skips over a given number of items within the results. Use for paging.
         /// Skips over a given number of items within the results. Use for paging.
         /// </summary>
         /// </summary>
@@ -28,20 +32,5 @@ namespace MediaBrowser.Model.Querying
         /// </summary>
         /// </summary>
         /// <value>The fields.</value>
         /// <value>The fields.</value>
         public ItemFields[] Fields { get; set; }
         public ItemFields[] Fields { get; set; }
-
-        /// <summary>
-        /// Gets or sets the exclude location types.
-        /// </summary>
-        /// <value>The exclude location types.</value>
-        public LocationType[] ExcludeLocationTypes { get; set; }
-
-        public bool? HasPremiereDate { get; set; }
-        public DateTime? MinPremiereDate { get; set; }
-        public DateTime? MaxPremiereDate { get; set; }
-
-        public NextUpQuery()
-        {
-            ExcludeLocationTypes = new LocationType[] { };
-        }
     }
     }
 }
 }

+ 8 - 9
MediaBrowser.Mono.userprefs

@@ -1,25 +1,24 @@
 <Properties>
 <Properties>
-  <MonoDevelop.Ide.Workspace ActiveConfiguration="Release|x86" />
+  <MonoDevelop.Ide.Workspace ActiveConfiguration="Debug|x86" />
   <MonoDevelop.Ide.Workbench ActiveDocument="MediaBrowser.Server.Mono\Program.cs">
   <MonoDevelop.Ide.Workbench ActiveDocument="MediaBrowser.Server.Mono\Program.cs">
     <Files>
     <Files>
-      <File FileName="MediaBrowser.Server.Mono\Program.cs" Line="60" Column="5" />
+      <File FileName="MediaBrowser.Server.Mono\Program.cs" Line="259" Column="4" />
+      <File FileName="MediaBrowser.ServerApplication\ApplicationHost.cs" Line="58" Column="12" />
+      <File FileName="MediaBrowser.Server.Mono\IO\FileSystemFactory.cs" Line="22" Column="1" />
     </Files>
     </Files>
     <Pads>
     <Pads>
       <Pad Id="ProjectPad">
       <Pad Id="ProjectPad">
         <State expanded="True">
         <State expanded="True">
-          <Node name="MediaBrowser.Common.Implementations" expanded="True">
-            <Node name="HttpClientManager" expanded="True" />
-          </Node>
-          <Node name="MediaBrowser.Controller" expanded="True">
-            <Node name="Drawing" expanded="True" />
-          </Node>
+          <Node name="MediaBrowser.Common" expanded="True" />
+          <Node name="MediaBrowser.Common.Implementations" expanded="True" />
+          <Node name="MediaBrowser.Controller" expanded="True" />
           <Node name="MediaBrowser.Model" expanded="True">
           <Node name="MediaBrowser.Model" expanded="True">
             <Node name="References" expanded="True" />
             <Node name="References" expanded="True" />
             <Node name="Web" expanded="True" />
             <Node name="Web" expanded="True" />
           </Node>
           </Node>
           <Node name="MediaBrowser.Server.Implementations" expanded="True" />
           <Node name="MediaBrowser.Server.Implementations" expanded="True" />
           <Node name="MediaBrowser.Server.Mono" expanded="True">
           <Node name="MediaBrowser.Server.Mono" expanded="True">
-            <Node name="FFMpeg" expanded="True" />
+            <Node name="IO" expanded="True" />
             <Node name="Program.cs" selected="True" />
             <Node name="Program.cs" selected="True" />
           </Node>
           </Node>
         </State>
         </State>

+ 6 - 3
MediaBrowser.Providers/FolderProviderFromXml.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
@@ -17,10 +18,12 @@ namespace MediaBrowser.Providers
     public class FolderProviderFromXml : BaseMetadataProvider
     public class FolderProviderFromXml : BaseMetadataProvider
     {
     {
         public static FolderProviderFromXml Current;
         public static FolderProviderFromXml Current;
+        private readonly IFileSystem _fileSystem;
 
 
-        public FolderProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager)
+        public FolderProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem)
             : base(logManager, configurationManager)
             : base(logManager, configurationManager)
         {
         {
+            _fileSystem = fileSystem;
             Current = this;
             Current = this;
         }
         }
 
 
@@ -53,7 +56,7 @@ namespace MediaBrowser.Providers
                 return false;
                 return false;
             }
             }
 
 
-            return FileSystem.GetLastWriteTimeUtc(xml, Logger) > providerInfo.LastRefreshed;
+            return _fileSystem.GetLastWriteTimeUtc(xml) > providerInfo.LastRefreshed;
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 9 - 6
MediaBrowser.Providers/Games/GameProviderFromXml.cs

@@ -1,27 +1,30 @@
-using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
+using MediaBrowser.Providers.Savers;
 using System;
 using System;
 using System.IO;
 using System.IO;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
-using MediaBrowser.Providers.Savers;
 
 
 namespace MediaBrowser.Providers.Games
 namespace MediaBrowser.Providers.Games
 {
 {
     public class GameProviderFromXml : BaseMetadataProvider
     public class GameProviderFromXml : BaseMetadataProvider
     {
     {
+        private readonly IFileSystem _fileSystem;
+
         /// <summary>
         /// <summary>
         /// 
         /// 
         /// </summary>
         /// </summary>
         /// <param name="logManager"></param>
         /// <param name="logManager"></param>
         /// <param name="configurationManager"></param>
         /// <param name="configurationManager"></param>
-        public GameProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager)
+        public GameProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem)
             : base(logManager, configurationManager)
             : base(logManager, configurationManager)
         {
         {
-
+            _fileSystem = fileSystem;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -45,7 +48,7 @@ namespace MediaBrowser.Providers.Games
                 return false;
                 return false;
             }
             }
 
 
-            return FileSystem.GetLastWriteTimeUtc(xml, Logger) > providerInfo.LastRefreshed;
+            return _fileSystem.GetLastWriteTimeUtc(xml) > providerInfo.LastRefreshed;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -78,7 +81,7 @@ namespace MediaBrowser.Providers.Games
 
 
                 try
                 try
                 {
                 {
-                    new BaseItemXmlParser<Game>(Logger).Fetch(game, metaFile, cancellationToken);
+                    new GameXmlParser(Logger).Fetch(game, metaFile, cancellationToken);
                 }
                 }
                 finally
                 finally
                 {
                 {

+ 6 - 3
MediaBrowser.Providers/Games/GameSystemProviderFromXml.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
@@ -14,10 +15,12 @@ namespace MediaBrowser.Providers.Games
     public class GameSystemProviderFromXml : BaseMetadataProvider
     public class GameSystemProviderFromXml : BaseMetadataProvider
     {
     {
         internal static GameSystemProviderFromXml Current { get; private set; }
         internal static GameSystemProviderFromXml Current { get; private set; }
+        private readonly IFileSystem _fileSystem;
 
 
-        public GameSystemProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager)
+        public GameSystemProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem)
             : base(logManager, configurationManager)
             : base(logManager, configurationManager)
         {
         {
+            _fileSystem = fileSystem;
             Current = this;
             Current = this;
         }
         }
 
 
@@ -50,7 +53,7 @@ namespace MediaBrowser.Providers.Games
                 return false;
                 return false;
             }
             }
 
 
-            return FileSystem.GetLastWriteTimeUtc(xml, Logger) > providerInfo.LastRefreshed;
+            return _fileSystem.GetLastWriteTimeUtc(xml) > providerInfo.LastRefreshed;
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 110 - 0
MediaBrowser.Providers/Games/GameXmlParser.cs

@@ -0,0 +1,110 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+using System.Globalization;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Xml;
+
+namespace MediaBrowser.Providers.Games
+{
+    /// <summary>
+    /// Class EpisodeXmlParser
+    /// </summary>
+    public class GameXmlParser : BaseItemXmlParser<Game>
+    {
+        private Task _chaptersTask = null;
+        private readonly CultureInfo _usCulture = new CultureInfo("en-US");
+
+        public GameXmlParser(ILogger logger)
+            : base(logger)
+        {
+        }
+
+        public async Task FetchAsync(Game item, string metadataFile, CancellationToken cancellationToken)
+        {
+            _chaptersTask = null;
+
+            Fetch(item, metadataFile, cancellationToken);
+
+            cancellationToken.ThrowIfCancellationRequested();
+
+            if (_chaptersTask != null)
+            {
+                await _chaptersTask.ConfigureAwait(false);
+            }
+        }
+
+        /// <summary>
+        /// Fetches the data from XML node.
+        /// </summary>
+        /// <param name="reader">The reader.</param>
+        /// <param name="item">The item.</param>
+        protected override void FetchDataFromXmlNode(XmlReader reader, Game item)
+        {
+            switch (reader.Name)
+            {
+                case "GameSystem":
+                    {
+                        var val = reader.ReadElementContentAsString();
+                        if (!string.IsNullOrWhiteSpace(val))
+                        {
+                            item.GameSystem = val;
+                        }
+                        break;
+                    }
+
+                case "GamesDbId":
+                    {
+                        var val = reader.ReadElementContentAsString();
+                        if (!string.IsNullOrWhiteSpace(val))
+                        {
+                            item.SetProviderId(MetadataProviders.Gamesdb, val);
+                        }
+                        break;
+                    }
+
+                case "NesBox":
+                    {
+                        var val = reader.ReadElementContentAsString();
+                        if (!string.IsNullOrWhiteSpace(val))
+                        {
+                            item.SetProviderId(MetadataProviders.NesBox, val);
+                        }
+                        break;
+                    }
+
+                case "NesBoxRom":
+                    {
+                        var val = reader.ReadElementContentAsString();
+                        if (!string.IsNullOrWhiteSpace(val))
+                        {
+                            item.SetProviderId(MetadataProviders.NesBoxRom, val);
+                        }
+                        break;
+                    }
+
+                case "Players":
+                    {
+                        var val = reader.ReadElementContentAsString();
+                        if (!string.IsNullOrWhiteSpace(val))
+                        {
+                            int num;
+
+                            if (int.TryParse(val, NumberStyles.Integer, _usCulture, out num))
+                            {
+                                item.PlayersSupported = num;
+                            }
+                        }
+                        break;
+                    }
+
+
+                default:
+                    base.FetchDataFromXmlNode(reader, item);
+                    break;
+            }
+        }
+    }
+}

+ 8 - 4
MediaBrowser.Providers/ImagesByNameProvider.cs

@@ -1,4 +1,5 @@
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.IO;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.IO;
@@ -18,9 +19,12 @@ namespace MediaBrowser.Providers
     /// </summary>
     /// </summary>
     public class ImagesByNameProvider : ImageFromMediaLocationProvider
     public class ImagesByNameProvider : ImageFromMediaLocationProvider
     {
     {
-        public ImagesByNameProvider(ILogManager logManager, IServerConfigurationManager configurationManager)
+        private readonly IFileSystem _fileSystem;
+        
+        public ImagesByNameProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem)
             : base(logManager, configurationManager)
             : base(logManager, configurationManager)
         {
         {
+            _fileSystem = fileSystem;
         }
         }
 
 
         public override ItemUpdateType ItemUpdateType
         public override ItemUpdateType ItemUpdateType
@@ -110,8 +114,8 @@ namespace MediaBrowser.Providers
 
 
             return files.Select(f =>
             return files.Select(f =>
             {
             {
-                var lastWriteTime = FileSystem.GetLastWriteTimeUtc(f, Logger);
-                var creationTime = FileSystem.GetCreationTimeUtc(f, Logger);
+                var lastWriteTime = _fileSystem.GetLastWriteTimeUtc(f);
+                var creationTime = _fileSystem.GetCreationTimeUtc(f);
 
 
                 return creationTime > lastWriteTime ? creationTime : lastWriteTime;
                 return creationTime > lastWriteTime ? creationTime : lastWriteTime;
 
 
@@ -150,7 +154,7 @@ namespace MediaBrowser.Providers
         /// <returns>System.String.</returns>
         /// <returns>System.String.</returns>
         protected string GetLocation(BaseItem item)
         protected string GetLocation(BaseItem item)
         {
         {
-            var name = FileSystem.GetValidFilename(item.Name);
+            var name = _fileSystem.GetValidFilename(item.Name);
 
 
             return Path.Combine(ConfigurationManager.ApplicationPaths.GeneralPath, name);
             return Path.Combine(ConfigurationManager.ApplicationPaths.GeneralPath, name);
         }
         }

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

@@ -49,6 +49,7 @@
   <ItemGroup>
   <ItemGroup>
     <Compile Include="FanartBaseProvider.cs" />
     <Compile Include="FanartBaseProvider.cs" />
     <Compile Include="FolderProviderFromXml.cs" />
     <Compile Include="FolderProviderFromXml.cs" />
+    <Compile Include="Games\GameXmlParser.cs" />
     <Compile Include="Games\GameProviderFromXml.cs" />
     <Compile Include="Games\GameProviderFromXml.cs" />
     <Compile Include="Games\GameSystemProviderFromXml.cs" />
     <Compile Include="Games\GameSystemProviderFromXml.cs" />
     <Compile Include="ImageFromMediaLocationProvider.cs" />
     <Compile Include="ImageFromMediaLocationProvider.cs" />
@@ -59,6 +60,7 @@
     <Compile Include="MediaInfo\FFProbeVideoInfoProvider.cs" />
     <Compile Include="MediaInfo\FFProbeVideoInfoProvider.cs" />
     <Compile Include="MediaInfo\VideoImageProvider.cs" />
     <Compile Include="MediaInfo\VideoImageProvider.cs" />
     <Compile Include="Movies\BoxSetProviderFromXml.cs" />
     <Compile Include="Movies\BoxSetProviderFromXml.cs" />
+    <Compile Include="Movies\ManualMovieDbImageProvider.cs" />
     <Compile Include="Movies\MovieUpdatesPrescanTask.cs" />
     <Compile Include="Movies\MovieUpdatesPrescanTask.cs" />
     <Compile Include="Movies\MovieXmlParser.cs" />
     <Compile Include="Movies\MovieXmlParser.cs" />
     <Compile Include="Movies\FanArtMovieProvider.cs" />
     <Compile Include="Movies\FanArtMovieProvider.cs" />

+ 6 - 3
MediaBrowser.Providers/Movies/BoxSetProviderFromXml.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.IO;
@@ -18,10 +19,12 @@ namespace MediaBrowser.Providers.Movies
     public class BoxSetProviderFromXml : BaseMetadataProvider
     public class BoxSetProviderFromXml : BaseMetadataProvider
     {
     {
         public static BoxSetProviderFromXml Current;
         public static BoxSetProviderFromXml Current;
+        private readonly IFileSystem _fileSystem;
 
 
-        public BoxSetProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager)
+        public BoxSetProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem)
             : base(logManager, configurationManager)
             : base(logManager, configurationManager)
         {
         {
+            _fileSystem = fileSystem;
             Current = this;
             Current = this;
         }
         }
 
 
@@ -54,7 +57,7 @@ namespace MediaBrowser.Providers.Movies
                 return false;
                 return false;
             }
             }
 
 
-            return FileSystem.GetLastWriteTimeUtc(xml, Logger) > providerInfo.LastRefreshed;
+            return _fileSystem.GetLastWriteTimeUtc(xml) > providerInfo.LastRefreshed;
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 8 - 5
MediaBrowser.Providers/Movies/FanArtMovieProvider.cs

@@ -4,6 +4,7 @@ using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
@@ -40,6 +41,7 @@ namespace MediaBrowser.Providers.Movies
         private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
         private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
 
 
         internal static FanArtMovieProvider Current { get; private set; }
         internal static FanArtMovieProvider Current { get; private set; }
+        private readonly IFileSystem _fileSystem;
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="FanArtMovieProvider" /> class.
         /// Initializes a new instance of the <see cref="FanArtMovieProvider" /> class.
@@ -49,7 +51,7 @@ namespace MediaBrowser.Providers.Movies
         /// <param name="configurationManager">The configuration manager.</param>
         /// <param name="configurationManager">The configuration manager.</param>
         /// <param name="providerManager">The provider manager.</param>
         /// <param name="providerManager">The provider manager.</param>
         /// <exception cref="System.ArgumentNullException">httpClient</exception>
         /// <exception cref="System.ArgumentNullException">httpClient</exception>
-        public FanArtMovieProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager)
+        public FanArtMovieProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager, IFileSystem fileSystem)
             : base(logManager, configurationManager)
             : base(logManager, configurationManager)
         {
         {
             if (httpClient == null)
             if (httpClient == null)
@@ -58,6 +60,7 @@ namespace MediaBrowser.Providers.Movies
             }
             }
             HttpClient = httpClient;
             HttpClient = httpClient;
             _providerManager = providerManager;
             _providerManager = providerManager;
+            _fileSystem = fileSystem;
             Current = this;
             Current = this;
         }
         }
 
 
@@ -174,7 +177,7 @@ namespace MediaBrowser.Providers.Movies
                 {
                 {
                     var files = new DirectoryInfo(path)
                     var files = new DirectoryInfo(path)
                         .EnumerateFiles("*.xml", SearchOption.TopDirectoryOnly)
                         .EnumerateFiles("*.xml", SearchOption.TopDirectoryOnly)
-                        .Select(i => i.LastWriteTimeUtc)
+                        .Select(i => _fileSystem.GetLastWriteTimeUtc(i))
                         .ToList();
                         .ToList();
 
 
                     if (files.Count > 0)
                     if (files.Count > 0)
@@ -275,7 +278,7 @@ namespace MediaBrowser.Providers.Movies
 
 
             }).ConfigureAwait(false))
             }).ConfigureAwait(false))
             {
             {
-                using (var xmlFileStream = new FileStream(xmlPath, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
+                using (var xmlFileStream = _fileSystem.GetFileStream(xmlPath, FileMode.Create, FileAccess.Write, FileShare.Read, true))
                 {
                 {
                     await response.CopyToAsync(xmlFileStream).ConfigureAwait(false);
                     await response.CopyToAsync(xmlFileStream).ConfigureAwait(false);
                 }
                 }
@@ -300,14 +303,14 @@ namespace MediaBrowser.Providers.Movies
 
 
             string path;
             string path;
 
 
-            if (ConfigurationManager.Configuration.DownloadMovieImages.Disc && !item.HasImage(ImageType.Disc))
+            if (ConfigurationManager.Configuration.DownloadMovieImages.Primary && !item.HasImage(ImageType.Primary))
             {
             {
                 var node = doc.SelectSingleNode("//fanart/movie/movieposters/movieposter[@lang = \"" + language + "\"]/@url") ??
                 var node = doc.SelectSingleNode("//fanart/movie/movieposters/movieposter[@lang = \"" + language + "\"]/@url") ??
                            doc.SelectSingleNode("//fanart/movie/movieposters/movieposter/@url");
                            doc.SelectSingleNode("//fanart/movie/movieposters/movieposter/@url");
                 path = node != null ? node.Value : null;
                 path = node != null ? node.Value : null;
                 if (!string.IsNullOrEmpty(path))
                 if (!string.IsNullOrEmpty(path))
                 {
                 {
-                    await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Disc, null, cancellationToken)
+                    await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Primary, null, cancellationToken)
                                         .ConfigureAwait(false);
                                         .ConfigureAwait(false);
                 }
                 }
             }
             }

+ 6 - 3
MediaBrowser.Providers/Movies/FanArtMovieUpdatesPrescanTask.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Common.Net;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
@@ -32,15 +33,17 @@ namespace MediaBrowser.Providers.Movies
         /// </summary>
         /// </summary>
         private readonly IServerConfigurationManager _config;
         private readonly IServerConfigurationManager _config;
         private readonly IJsonSerializer _jsonSerializer;
         private readonly IJsonSerializer _jsonSerializer;
+        private readonly IFileSystem _fileSystem;
 
 
         private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
         private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
 
 
-        public FanArtMovieUpdatesPrescanTask(IJsonSerializer jsonSerializer, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient)
+        public FanArtMovieUpdatesPrescanTask(IJsonSerializer jsonSerializer, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient, IFileSystem fileSystem)
         {
         {
             _jsonSerializer = jsonSerializer;
             _jsonSerializer = jsonSerializer;
             _config = config;
             _config = config;
             _logger = logger;
             _logger = logger;
             _httpClient = httpClient;
             _httpClient = httpClient;
+            _fileSystem = fileSystem;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -66,7 +69,7 @@ namespace MediaBrowser.Providers.Movies
             var timestampFileInfo = new FileInfo(timestampFile);
             var timestampFileInfo = new FileInfo(timestampFile);
 
 
             // Don't check for tvdb updates anymore frequently than 24 hours
             // Don't check for tvdb updates anymore frequently than 24 hours
-            if (timestampFileInfo.Exists && (DateTime.UtcNow - timestampFileInfo.LastWriteTimeUtc).TotalDays < 1)
+            if (timestampFileInfo.Exists && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(timestampFileInfo)).TotalDays < 1)
             {
             {
                 return;
                 return;
             }
             }

+ 168 - 0
MediaBrowser.Providers/Movies/ManualMovieDbImageProvider.cs

@@ -0,0 +1,168 @@
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
+using MediaBrowser.Model.Serialization;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Providers.Movies
+{
+    class ManualMovieDbImageProvider : IImageProvider
+    {
+        private readonly IJsonSerializer _jsonSerializer;
+        private readonly IServerConfigurationManager _config;
+
+        public ManualMovieDbImageProvider(IJsonSerializer jsonSerializer, IServerConfigurationManager config)
+        {
+            _jsonSerializer = jsonSerializer;
+            _config = config;
+        }
+
+        public string Name
+        {
+            get { return "TheMovieDB"; }
+        }
+
+        public bool Supports(BaseItem item, ImageType imageType)
+        {
+            if (MovieDbImagesProvider.SupportsItem(item))
+            {
+                return imageType == ImageType.Primary || imageType == ImageType.Backdrop;
+            }
+
+            return false;
+        }
+
+        public async Task<IEnumerable<RemoteImageInfo>> GetAvailableImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken)
+        {
+            var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false);
+
+            return images.Where(i => i.Type == imageType);
+        }
+
+        public async Task<IEnumerable<RemoteImageInfo>> GetAllImages(BaseItem item, CancellationToken cancellationToken)
+        {
+            var list = new List<RemoteImageInfo>();
+
+            var results = FetchImages(item, _jsonSerializer);
+
+            if (results == null)
+            {
+                return list;
+            }
+
+            var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
+
+            var tmdbImageUrl = tmdbSettings.images.base_url + "original";
+
+            list.AddRange(GetPosters(results, item).Select(i => new RemoteImageInfo
+            {
+                Url = tmdbImageUrl + i.file_path,
+                CommunityRating = i.vote_average,
+                VoteCount = i.vote_count,
+                Width = i.width,
+                Height = i.height,
+                Language = i.iso_639_1,
+                ProviderName = Name,
+                Type = ImageType.Primary
+            }));
+
+            list.AddRange(GetBackdrops(results, item).Select(i => new RemoteImageInfo
+            {
+                Url = tmdbImageUrl + i.file_path,
+                CommunityRating = i.vote_average,
+                VoteCount = i.vote_count,
+                Width = i.width,
+                Height = i.height,
+                ProviderName = Name,
+                Type = ImageType.Backdrop
+            }));
+            
+            return list;
+        }
+        
+        /// <summary>
+        /// Gets the posters.
+        /// </summary>
+        /// <param name="images">The images.</param>
+        /// <param name="item">The item.</param>
+        /// <returns>IEnumerable{MovieDbProvider.Poster}.</returns>
+        private IEnumerable<MovieDbProvider.Poster> GetPosters(MovieDbProvider.Images images, BaseItem item)
+        {
+            var language = _config.Configuration.PreferredMetadataLanguage;
+
+            var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
+
+            var eligiblePosters = images.posters == null ?
+                new List<MovieDbProvider.Poster>() :
+                images.posters.Where(i => i.width >= _config.Configuration.MinMoviePosterWidth)
+                .ToList();
+
+            return eligiblePosters.OrderByDescending(i =>
+                {
+                    if (string.Equals(language, i.iso_639_1, StringComparison.OrdinalIgnoreCase))
+                    {
+                        return 3;
+                    }
+                    if (!isLanguageEn)
+                    {
+                        if (string.Equals("en", i.iso_639_1, StringComparison.OrdinalIgnoreCase))
+                        {
+                            return 2;
+                        }
+                    }
+                    if (string.IsNullOrEmpty(i.iso_639_1))
+                    {
+                        return isLanguageEn ? 3 : 2;
+                    }
+                    return 0;
+                })
+                .ThenByDescending(i => i.vote_average)
+                .ToList();
+        }
+
+        /// <summary>
+        /// Gets the backdrops.
+        /// </summary>
+        /// <param name="images">The images.</param>
+        /// <param name="item">The item.</param>
+        /// <returns>IEnumerable{MovieDbProvider.Backdrop}.</returns>
+        private IEnumerable<MovieDbProvider.Backdrop> GetBackdrops(MovieDbProvider.Images images, BaseItem item)
+        {
+            var eligibleBackdrops = images.backdrops == null ? new List<MovieDbProvider.Backdrop>() :
+                images.backdrops.Where(i => i.width >= _config.Configuration.MinMovieBackdropWidth)
+                .ToList();
+
+            return eligibleBackdrops.OrderByDescending(i => i.vote_average);
+        }
+
+        /// <summary>
+        /// Fetches the images.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <param name="jsonSerializer">The json serializer.</param>
+        /// <returns>Task{MovieImages}.</returns>
+        private MovieDbProvider.Images FetchImages(BaseItem item, IJsonSerializer jsonSerializer)
+        {
+            var path = MovieDbProvider.Current.GetDataFilePath(item, "default");
+
+            if (!string.IsNullOrEmpty(path))
+            {
+                var fileInfo = new FileInfo(path);
+
+                if (fileInfo.Exists)
+                {
+                    return jsonSerializer.DeserializeFromFile<MovieDbProvider.CompleteMovieData>(path).images;
+                }
+            }
+
+            return null;
+        }
+    }
+}

+ 32 - 92
MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Common.Net;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.Movies;
@@ -6,6 +7,7 @@ using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Providers;
 using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.Serialization;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
@@ -21,11 +23,6 @@ namespace MediaBrowser.Providers.Movies
     /// </summary>
     /// </summary>
     public class MovieDbImagesProvider : BaseMetadataProvider
     public class MovieDbImagesProvider : BaseMetadataProvider
     {
     {
-        /// <summary>
-        /// The get images
-        /// </summary>
-        private const string GetImages = @"http://api.themoviedb.org/3/{2}/{0}/images?api_key={1}";
-
         /// <summary>
         /// <summary>
         /// The _provider manager
         /// The _provider manager
         /// </summary>
         /// </summary>
@@ -35,6 +32,7 @@ namespace MediaBrowser.Providers.Movies
         /// The _json serializer
         /// The _json serializer
         /// </summary>
         /// </summary>
         private readonly IJsonSerializer _jsonSerializer;
         private readonly IJsonSerializer _jsonSerializer;
+        private readonly IFileSystem _fileSystem;
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="MovieDbImagesProvider"/> class.
         /// Initializes a new instance of the <see cref="MovieDbImagesProvider"/> class.
@@ -43,11 +41,12 @@ namespace MediaBrowser.Providers.Movies
         /// <param name="configurationManager">The configuration manager.</param>
         /// <param name="configurationManager">The configuration manager.</param>
         /// <param name="providerManager">The provider manager.</param>
         /// <param name="providerManager">The provider manager.</param>
         /// <param name="jsonSerializer">The json serializer.</param>
         /// <param name="jsonSerializer">The json serializer.</param>
-        public MovieDbImagesProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager, IJsonSerializer jsonSerializer)
+        public MovieDbImagesProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager, IJsonSerializer jsonSerializer, IFileSystem fileSystem)
             : base(logManager, configurationManager)
             : base(logManager, configurationManager)
         {
         {
             _providerManager = providerManager;
             _providerManager = providerManager;
             _jsonSerializer = jsonSerializer;
             _jsonSerializer = jsonSerializer;
+            _fileSystem = fileSystem;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -65,6 +64,11 @@ namespace MediaBrowser.Providers.Movies
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
         /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
         public override bool Supports(BaseItem item)
         public override bool Supports(BaseItem item)
+        {
+            return SupportsItem(item);
+        }
+
+        public static bool SupportsItem(BaseItem item)
         {
         {
             var trailer = item as Trailer;
             var trailer = item as Trailer;
 
 
@@ -149,7 +153,7 @@ namespace MediaBrowser.Providers.Movies
             {
             {
                 return false;
                 return false;
             }
             }
-            
+
             var path = MovieDbProvider.Current.GetDataFilePath(item, "default");
             var path = MovieDbProvider.Current.GetDataFilePath(item, "default");
 
 
             if (!string.IsNullOrEmpty(path))
             if (!string.IsNullOrEmpty(path))
@@ -158,7 +162,7 @@ namespace MediaBrowser.Providers.Movies
 
 
                 if (fileInfo.Exists)
                 if (fileInfo.Exists)
                 {
                 {
-                    return fileInfo.LastWriteTimeUtc > providerInfo.LastRefreshed;
+                    return _fileSystem.GetLastWriteTimeUtc(fileInfo) > providerInfo.LastRefreshed;
                 }
                 }
             }
             }
 
 
@@ -176,44 +180,18 @@ namespace MediaBrowser.Providers.Movies
         {
         {
             var id = item.GetProviderId(MetadataProviders.Tmdb);
             var id = item.GetProviderId(MetadataProviders.Tmdb);
 
 
-            var status = ProviderRefreshStatus.Success;
-
             if (!string.IsNullOrEmpty(id))
             if (!string.IsNullOrEmpty(id))
             {
             {
-                var images = FetchImages(item);
+                var images = await new ManualMovieDbImageProvider(_jsonSerializer, ConfigurationManager).GetAllImages(item,
+                            cancellationToken).ConfigureAwait(false);
 
 
-                if (images != null)
-                {
-                    status = await ProcessImages(item, images, cancellationToken).ConfigureAwait(false);
-                }
+                await ProcessImages(item, images.ToList(), cancellationToken).ConfigureAwait(false);
             }
             }
 
 
-            SetLastRefreshed(item, DateTime.UtcNow, status);
+            SetLastRefreshed(item, DateTime.UtcNow);
             return true;
             return true;
         }
         }
 
 
-        /// <summary>
-        /// Fetches the images.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns>Task{MovieImages}.</returns>
-        private MovieDbProvider.Images FetchImages(BaseItem item)
-        {
-            var path = MovieDbProvider.Current.GetDataFilePath(item, "default");
-
-            if (!string.IsNullOrEmpty(path))
-            {
-                var fileInfo = new FileInfo(path);
-
-                if (fileInfo.Exists)
-                {
-                    return _jsonSerializer.DeserializeFromFile<MovieDbProvider.CompleteMovieData>(path).images;
-                }
-            }
-
-            return null;
-        }
-
         /// <summary>
         /// <summary>
         /// Processes the images.
         /// Processes the images.
         /// </summary>
         /// </summary>
@@ -221,68 +199,36 @@ namespace MediaBrowser.Providers.Movies
         /// <param name="images">The images.</param>
         /// <param name="images">The images.</param>
         /// <param name="cancellationToken">The cancellation token</param>
         /// <param name="cancellationToken">The cancellation token</param>
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
-        private async Task<ProviderRefreshStatus> ProcessImages(BaseItem item, MovieDbProvider.Images images, CancellationToken cancellationToken)
+        private async Task ProcessImages(BaseItem item, List<RemoteImageInfo> images, CancellationToken cancellationToken)
         {
         {
             cancellationToken.ThrowIfCancellationRequested();
             cancellationToken.ThrowIfCancellationRequested();
 
 
-            var status = ProviderRefreshStatus.Success;
-
-            var eligiblePosters = images.posters == null ?
-                new List<MovieDbProvider.Poster>() :
-                images.posters.Where(i => i.width >= ConfigurationManager.Configuration.MinMoviePosterWidth)
+            var eligiblePosters = images
+                .Where(i => i.Type == ImageType.Primary)
                 .ToList();
                 .ToList();
 
 
-            eligiblePosters = eligiblePosters.OrderByDescending(i => i.vote_average).ToList();
-
             //        poster
             //        poster
             if (eligiblePosters.Count > 0 && !item.HasImage(ImageType.Primary))
             if (eligiblePosters.Count > 0 && !item.HasImage(ImageType.Primary))
             {
             {
-                var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
+                var poster = eligiblePosters[0];
 
 
-                var tmdbImageUrl = tmdbSettings.images.base_url + "original";
-                // get highest rated poster for our language
+                var url = poster.Url;
 
 
-                var poster = eligiblePosters.FirstOrDefault(p => string.Equals(p.iso_639_1, ConfigurationManager.Configuration.PreferredMetadataLanguage, StringComparison.OrdinalIgnoreCase));
-
-                if (poster == null)
+                var img = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions
                 {
                 {
-                    // couldn't find our specific language, find english
-                    poster = eligiblePosters.FirstOrDefault(p => string.Equals(p.iso_639_1, "en", StringComparison.OrdinalIgnoreCase));
-                }
+                    Url = url,
+                    CancellationToken = cancellationToken
 
 
-                if (poster == null)
-                {
-                    //still couldn't find it - try highest rated null one
-                    poster = eligiblePosters.FirstOrDefault(p => p.iso_639_1 == null);
-                }
+                }).ConfigureAwait(false);
 
 
-                if (poster == null)
-                {
-                    //finally - just get the highest rated one
-                    poster = eligiblePosters.FirstOrDefault();
-                }
-
-                if (poster != null)
-                {
-                    var url = tmdbImageUrl + poster.file_path;
-
-                    var img = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions
-                    {
-                        Url = url,
-                        CancellationToken = cancellationToken
-
-                    }).ConfigureAwait(false);
-
-                    await _providerManager.SaveImage(item, img, MimeTypes.GetMimeType(poster.file_path), ImageType.Primary, null, url, cancellationToken)
-                                        .ConfigureAwait(false);
-
-                }
+                await _providerManager.SaveImage(item, img, MimeTypes.GetMimeType(url), ImageType.Primary, null, url, cancellationToken)
+                                    .ConfigureAwait(false);
             }
             }
 
 
             cancellationToken.ThrowIfCancellationRequested();
             cancellationToken.ThrowIfCancellationRequested();
 
 
-            var eligibleBackdrops = images.backdrops == null ? new List<MovieDbProvider.Backdrop>() :
-                images.backdrops.Where(i => i.width >= ConfigurationManager.Configuration.MinMovieBackdropWidth)
+            var eligibleBackdrops = images
+                .Where(i => i.Type == ImageType.Backdrop)
                 .ToList();
                 .ToList();
 
 
             var backdropLimit = ConfigurationManager.Configuration.MaxBackdrops;
             var backdropLimit = ConfigurationManager.Configuration.MaxBackdrops;
@@ -290,13 +236,9 @@ namespace MediaBrowser.Providers.Movies
             // backdrops - only download if earlier providers didn't find any (fanart)
             // backdrops - only download if earlier providers didn't find any (fanart)
             if (eligibleBackdrops.Count > 0 && ConfigurationManager.Configuration.DownloadMovieImages.Backdrops && item.BackdropImagePaths.Count < backdropLimit)
             if (eligibleBackdrops.Count > 0 && ConfigurationManager.Configuration.DownloadMovieImages.Backdrops && item.BackdropImagePaths.Count < backdropLimit)
             {
             {
-                var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
-
-                var tmdbImageUrl = tmdbSettings.images.base_url + "original";
-
                 for (var i = 0; i < eligibleBackdrops.Count; i++)
                 for (var i = 0; i < eligibleBackdrops.Count; i++)
                 {
                 {
-                    var url = tmdbImageUrl + eligibleBackdrops[i].file_path;
+                    var url = eligibleBackdrops[i].Url;
 
 
                     if (!item.ContainsImageWithSourceUrl(url))
                     if (!item.ContainsImageWithSourceUrl(url))
                     {
                     {
@@ -307,7 +249,7 @@ namespace MediaBrowser.Providers.Movies
 
 
                         }).ConfigureAwait(false);
                         }).ConfigureAwait(false);
 
 
-                        await _providerManager.SaveImage(item, img, MimeTypes.GetMimeType(eligibleBackdrops[i].file_path), ImageType.Backdrop, item.BackdropImagePaths.Count, url, cancellationToken)
+                        await _providerManager.SaveImage(item, img, MimeTypes.GetMimeType(url), ImageType.Backdrop, item.BackdropImagePaths.Count, url, cancellationToken)
                           .ConfigureAwait(false);
                           .ConfigureAwait(false);
                     }
                     }
 
 
@@ -317,8 +259,6 @@ namespace MediaBrowser.Providers.Movies
                     }
                     }
                 }
                 }
             }
             }
-
-            return status;
         }
         }
     }
     }
 }
 }

+ 7 - 3
MediaBrowser.Providers/Movies/MovieDbProvider.cs

@@ -1,4 +1,5 @@
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.IO;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
@@ -47,6 +48,7 @@ namespace MediaBrowser.Providers.Movies
         /// </summary>
         /// </summary>
         /// <value>The HTTP client.</value>
         /// <value>The HTTP client.</value>
         protected IHttpClient HttpClient { get; private set; }
         protected IHttpClient HttpClient { get; private set; }
+        private readonly IFileSystem _fileSystem;
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="MovieDbProvider" /> class.
         /// Initializes a new instance of the <see cref="MovieDbProvider" /> class.
@@ -56,12 +58,13 @@ namespace MediaBrowser.Providers.Movies
         /// <param name="jsonSerializer">The json serializer.</param>
         /// <param name="jsonSerializer">The json serializer.</param>
         /// <param name="httpClient">The HTTP client.</param>
         /// <param name="httpClient">The HTTP client.</param>
         /// <param name="providerManager">The provider manager.</param>
         /// <param name="providerManager">The provider manager.</param>
-        public MovieDbProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IHttpClient httpClient, IProviderManager providerManager)
+        public MovieDbProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IHttpClient httpClient, IProviderManager providerManager, IFileSystem fileSystem)
             : base(logManager, configurationManager)
             : base(logManager, configurationManager)
         {
         {
             JsonSerializer = jsonSerializer;
             JsonSerializer = jsonSerializer;
             HttpClient = httpClient;
             HttpClient = httpClient;
             ProviderManager = providerManager;
             ProviderManager = providerManager;
+            _fileSystem = fileSystem;
             Current = this;
             Current = this;
         }
         }
 
 
@@ -189,6 +192,7 @@ namespace MediaBrowser.Providers.Movies
 
 
         static readonly Regex[] NameMatches = new[] {
         static readonly Regex[] NameMatches = new[] {
             new Regex(@"(?<name>.*)\((?<year>\d{4})\)"), // matches "My Movie (2001)" and gives us the name and the year
             new Regex(@"(?<name>.*)\((?<year>\d{4})\)"), // matches "My Movie (2001)" and gives us the name and the year
+            new Regex(@"(?<name>.*)(\.(?<year>\d{4})(\.|$)).*$"), 
             new Regex(@"(?<name>.*)") // last resort matches the whole string as the name
             new Regex(@"(?<name>.*)") // last resort matches the whole string as the name
         };
         };
 
 
@@ -215,7 +219,7 @@ namespace MediaBrowser.Providers.Movies
 
 
                 if (fileInfo.Exists)
                 if (fileInfo.Exists)
                 {
                 {
-                    return fileInfo.LastWriteTimeUtc > providerInfo.LastRefreshed;
+                    return _fileSystem.GetLastWriteTimeUtc(fileInfo) > providerInfo.LastRefreshed;
                 }
                 }
 
 
                 return true;
                 return true;
@@ -320,7 +324,7 @@ namespace MediaBrowser.Providers.Movies
         /// <param name="name">The name.</param>
         /// <param name="name">The name.</param>
         /// <param name="justName">Name of the just.</param>
         /// <param name="justName">Name of the just.</param>
         /// <param name="year">The year.</param>
         /// <param name="year">The year.</param>
-        protected void ParseName(string name, out string justName, out int? year)
+        public static void ParseName(string name, out string justName, out int? year)
         {
         {
             justName = null;
             justName = null;
             year = null;
             year = null;

+ 6 - 4
MediaBrowser.Providers/Movies/MovieProviderFromXml.cs

@@ -1,7 +1,7 @@
-using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.Movies;
-using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
@@ -21,11 +21,13 @@ namespace MediaBrowser.Providers.Movies
     {
     {
         internal static MovieProviderFromXml Current { get; private set; }
         internal static MovieProviderFromXml Current { get; private set; }
         private readonly IItemRepository _itemRepo;
         private readonly IItemRepository _itemRepo;
+        private readonly IFileSystem _fileSystem;
 
 
-        public MovieProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager, IItemRepository itemRepo)
+        public MovieProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager, IItemRepository itemRepo, IFileSystem fileSystem)
             : base(logManager, configurationManager)
             : base(logManager, configurationManager)
         {
         {
             _itemRepo = itemRepo;
             _itemRepo = itemRepo;
+            _fileSystem = fileSystem;
             Current = this;
             Current = this;
         }
         }
 
 
@@ -71,7 +73,7 @@ namespace MediaBrowser.Providers.Movies
                 return false;
                 return false;
             }
             }
 
 
-            return FileSystem.GetLastWriteTimeUtc(xml, Logger) > providerInfo.LastRefreshed;
+            return _fileSystem.GetLastWriteTimeUtc(xml) > providerInfo.LastRefreshed;
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 6 - 3
MediaBrowser.Providers/Movies/MovieUpdatesPrescanTask.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Common.Net;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Progress;
 using MediaBrowser.Common.Progress;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
@@ -35,6 +36,7 @@ namespace MediaBrowser.Providers.Movies
         /// </summary>
         /// </summary>
         private readonly IServerConfigurationManager _config;
         private readonly IServerConfigurationManager _config;
         private readonly IJsonSerializer _json;
         private readonly IJsonSerializer _json;
+        private readonly IFileSystem _fileSystem;
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="MovieUpdatesPreScanTask"/> class.
         /// Initializes a new instance of the <see cref="MovieUpdatesPreScanTask"/> class.
@@ -43,12 +45,13 @@ namespace MediaBrowser.Providers.Movies
         /// <param name="httpClient">The HTTP client.</param>
         /// <param name="httpClient">The HTTP client.</param>
         /// <param name="config">The config.</param>
         /// <param name="config">The config.</param>
         /// <param name="json">The json.</param>
         /// <param name="json">The json.</param>
-        public MovieUpdatesPreScanTask(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config, IJsonSerializer json)
+        public MovieUpdatesPreScanTask(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config, IJsonSerializer json, IFileSystem fileSystem)
         {
         {
             _logger = logger;
             _logger = logger;
             _httpClient = httpClient;
             _httpClient = httpClient;
             _config = config;
             _config = config;
             _json = json;
             _json = json;
+            _fileSystem = fileSystem;
         }
         }
 
 
         protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
         protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
@@ -100,7 +103,7 @@ namespace MediaBrowser.Providers.Movies
             var refreshDays = _config.Configuration.EnableTmdbUpdates ? 1 : 7;
             var refreshDays = _config.Configuration.EnableTmdbUpdates ? 1 : 7;
 
 
             // Don't check for tvdb updates anymore frequently than 24 hours
             // Don't check for tvdb updates anymore frequently than 24 hours
-            if (timestampFileInfo.Exists && (DateTime.UtcNow - timestampFileInfo.LastWriteTimeUtc).TotalDays < refreshDays)
+            if (timestampFileInfo.Exists && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(timestampFileInfo)).TotalDays < refreshDays)
             {
             {
                 return;
                 return;
             }
             }

+ 6 - 3
MediaBrowser.Providers/Movies/PersonProviderFromXml.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
@@ -13,10 +14,12 @@ namespace MediaBrowser.Providers.Movies
     class PersonProviderFromXml : BaseMetadataProvider
     class PersonProviderFromXml : BaseMetadataProvider
     {
     {
         internal static PersonProviderFromXml Current { get; private set; }
         internal static PersonProviderFromXml Current { get; private set; }
+        private readonly IFileSystem _fileSystem;
 
 
-        public PersonProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager)
+        public PersonProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem)
             : base(logManager, configurationManager)
             : base(logManager, configurationManager)
         {
         {
+            _fileSystem = fileSystem;
             Current = this;
             Current = this;
         }
         }
 
 
@@ -49,7 +52,7 @@ namespace MediaBrowser.Providers.Movies
                 return false;
                 return false;
             }
             }
 
 
-            return FileSystem.GetLastWriteTimeUtc(xml, Logger) > providerInfo.LastRefreshed;
+            return _fileSystem.GetLastWriteTimeUtc(xml) > providerInfo.LastRefreshed;
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 6 - 3
MediaBrowser.Providers/Movies/PersonUpdatesPreScanTask.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Common.Net;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
@@ -34,6 +35,7 @@ namespace MediaBrowser.Providers.Movies
         /// </summary>
         /// </summary>
         private readonly IServerConfigurationManager _config;
         private readonly IServerConfigurationManager _config;
         private readonly IJsonSerializer _json;
         private readonly IJsonSerializer _json;
+        private readonly IFileSystem _fileSystem;
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="PersonUpdatesPreScanTask"/> class.
         /// Initializes a new instance of the <see cref="PersonUpdatesPreScanTask"/> class.
@@ -41,12 +43,13 @@ namespace MediaBrowser.Providers.Movies
         /// <param name="logger">The logger.</param>
         /// <param name="logger">The logger.</param>
         /// <param name="httpClient">The HTTP client.</param>
         /// <param name="httpClient">The HTTP client.</param>
         /// <param name="config">The config.</param>
         /// <param name="config">The config.</param>
-        public PersonUpdatesPreScanTask(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config, IJsonSerializer json)
+        public PersonUpdatesPreScanTask(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config, IJsonSerializer json, IFileSystem fileSystem)
         {
         {
             _logger = logger;
             _logger = logger;
             _httpClient = httpClient;
             _httpClient = httpClient;
             _config = config;
             _config = config;
             _json = json;
             _json = json;
+            _fileSystem = fileSystem;
         }
         }
 
 
         protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
         protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
@@ -74,7 +77,7 @@ namespace MediaBrowser.Providers.Movies
             var timestampFileInfo = new FileInfo(timestampFile);
             var timestampFileInfo = new FileInfo(timestampFile);
 
 
             // Don't check for tvdb updates anymore frequently than 24 hours
             // Don't check for tvdb updates anymore frequently than 24 hours
-            if (timestampFileInfo.Exists && (DateTime.UtcNow - timestampFileInfo.LastWriteTimeUtc).TotalDays < 1)
+            if (timestampFileInfo.Exists && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(timestampFileInfo)).TotalDays < 1)
             {
             {
                 return;
                 return;
             }
             }

+ 5 - 3
MediaBrowser.Providers/Movies/TmdbPersonProvider.cs

@@ -30,8 +30,9 @@ namespace MediaBrowser.Providers.Movies
         internal static TmdbPersonProvider Current { get; private set; }
         internal static TmdbPersonProvider Current { get; private set; }
 
 
         const string DataFileName = "info.json";
         const string DataFileName = "info.json";
+        private readonly IFileSystem _fileSystem;
 
 
-        public TmdbPersonProvider(IJsonSerializer jsonSerializer, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager)
+        public TmdbPersonProvider(IJsonSerializer jsonSerializer, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager, IFileSystem fileSystem)
             : base(logManager, configurationManager)
             : base(logManager, configurationManager)
         {
         {
             if (jsonSerializer == null)
             if (jsonSerializer == null)
@@ -40,6 +41,7 @@ namespace MediaBrowser.Providers.Movies
             }
             }
             JsonSerializer = jsonSerializer;
             JsonSerializer = jsonSerializer;
             ProviderManager = providerManager;
             ProviderManager = providerManager;
+            _fileSystem = fileSystem;
             Current = this;
             Current = this;
         }
         }
 
 
@@ -105,7 +107,7 @@ namespace MediaBrowser.Providers.Movies
 
 
                 if (fileInfo.Exists)
                 if (fileInfo.Exists)
                 {
                 {
-                    return fileInfo.LastWriteTimeUtc > providerInfo.LastRefreshed;
+                    return _fileSystem.GetLastWriteTimeUtc(fileInfo) > providerInfo.LastRefreshed;
                 }
                 }
 
 
                 return true;
                 return true;
@@ -270,7 +272,7 @@ namespace MediaBrowser.Providers.Movies
             {
             {
                 Directory.CreateDirectory(personDataPath);
                 Directory.CreateDirectory(personDataPath);
 
 
-                using (var fs = new FileStream(Path.Combine(personDataPath, DataFileName), FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, true))
+                using (var fs = _fileSystem.GetFileStream(Path.Combine(personDataPath, DataFileName), FileMode.Create, FileAccess.Write, FileShare.Read, true))
                 {
                 {
                     await json.CopyToAsync(fs).ConfigureAwait(false);
                     await json.CopyToAsync(fs).ConfigureAwait(false);
                 }
                 }

+ 6 - 3
MediaBrowser.Providers/Music/ArtistProviderFromXml.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.IO;
@@ -15,10 +16,12 @@ namespace MediaBrowser.Providers.Music
     class ArtistProviderFromXml : BaseMetadataProvider
     class ArtistProviderFromXml : BaseMetadataProvider
     {
     {
         public static ArtistProviderFromXml Current;
         public static ArtistProviderFromXml Current;
+        private readonly IFileSystem _fileSystem;
 
 
-        public ArtistProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager)
+        public ArtistProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem)
             : base(logManager, configurationManager)
             : base(logManager, configurationManager)
         {
         {
+            _fileSystem = fileSystem;
             Current = this;
             Current = this;
         }
         }
 
 
@@ -51,7 +54,7 @@ namespace MediaBrowser.Providers.Music
                 return false;
                 return false;
             }
             }
 
 
-            return FileSystem.GetLastWriteTimeUtc(xml, Logger) > providerInfo.LastRefreshed;
+            return _fileSystem.GetLastWriteTimeUtc(xml) > providerInfo.LastRefreshed;
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 6 - 3
MediaBrowser.Providers/Music/FanArtAlbumProvider.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Common.Net;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Audio;
@@ -37,6 +38,7 @@ namespace MediaBrowser.Providers.Music
         protected IHttpClient HttpClient { get; private set; }
         protected IHttpClient HttpClient { get; private set; }
 
 
         internal static FanArtAlbumProvider Current { get; private set; }
         internal static FanArtAlbumProvider Current { get; private set; }
+        private readonly IFileSystem _fileSystem;
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="FanArtAlbumProvider"/> class.
         /// Initializes a new instance of the <see cref="FanArtAlbumProvider"/> class.
@@ -45,10 +47,11 @@ namespace MediaBrowser.Providers.Music
         /// <param name="logManager">The log manager.</param>
         /// <param name="logManager">The log manager.</param>
         /// <param name="configurationManager">The configuration manager.</param>
         /// <param name="configurationManager">The configuration manager.</param>
         /// <param name="providerManager">The provider manager.</param>
         /// <param name="providerManager">The provider manager.</param>
-        public FanArtAlbumProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager)
+        public FanArtAlbumProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager, IFileSystem fileSystem)
             : base(logManager, configurationManager)
             : base(logManager, configurationManager)
         {
         {
             _providerManager = providerManager;
             _providerManager = providerManager;
+            _fileSystem = fileSystem;
             HttpClient = httpClient;
             HttpClient = httpClient;
 
 
             Current = this;
             Current = this;
@@ -140,7 +143,7 @@ namespace MediaBrowser.Providers.Music
 
 
                 if (file.Exists)
                 if (file.Exists)
                 {
                 {
-                    return file.LastWriteTimeUtc;
+                    return _fileSystem.GetLastWriteTimeUtc(file);
                 }
                 }
             } 
             } 
             
             

+ 5 - 7
MediaBrowser.Providers/Music/FanArtArtistByNameProvider.cs

@@ -1,7 +1,9 @@
-using MediaBrowser.Common.Net;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 
 
@@ -15,12 +17,8 @@ namespace MediaBrowser.Providers.Music
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="FanArtArtistByNameProvider" /> class.
         /// Initializes a new instance of the <see cref="FanArtArtistByNameProvider" /> class.
         /// </summary>
         /// </summary>
-        /// <param name="httpClient">The HTTP client.</param>
-        /// <param name="logManager">The log manager.</param>
-        /// <param name="configurationManager">The configuration manager.</param>
-        /// <param name="providerManager">The provider manager.</param>
-        public FanArtArtistByNameProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager)
-            : base(httpClient, logManager, configurationManager, providerManager)
+        public FanArtArtistByNameProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager, IFileSystem fileSystem)
+            : base(httpClient, logManager, configurationManager, providerManager, fileSystem)
         {
         {
         }
         }
 
 

+ 9 - 8
MediaBrowser.Providers/Music/FanArtArtistProvider.cs

@@ -1,24 +1,23 @@
-using System.Net;
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Net;
 using System;
 using System;
-using System.Collections.Generic;
 using System.Globalization;
 using System.Globalization;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
+using System.Net;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
 using System.Xml;
 using System.Xml;
-using MediaBrowser.Model.Net;
 
 
 namespace MediaBrowser.Providers.Music
 namespace MediaBrowser.Providers.Music
 {
 {
@@ -39,6 +38,7 @@ namespace MediaBrowser.Providers.Music
         private readonly IProviderManager _providerManager;
         private readonly IProviderManager _providerManager;
 
 
         internal static FanArtArtistProvider Current;
         internal static FanArtArtistProvider Current;
+        private readonly IFileSystem _fileSystem;
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="FanArtArtistProvider"/> class.
         /// Initializes a new instance of the <see cref="FanArtArtistProvider"/> class.
@@ -48,7 +48,7 @@ namespace MediaBrowser.Providers.Music
         /// <param name="configurationManager">The configuration manager.</param>
         /// <param name="configurationManager">The configuration manager.</param>
         /// <param name="providerManager">The provider manager.</param>
         /// <param name="providerManager">The provider manager.</param>
         /// <exception cref="System.ArgumentNullException">httpClient</exception>
         /// <exception cref="System.ArgumentNullException">httpClient</exception>
-        public FanArtArtistProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager)
+        public FanArtArtistProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager, IFileSystem fileSystem)
             : base(logManager, configurationManager)
             : base(logManager, configurationManager)
         {
         {
             if (httpClient == null)
             if (httpClient == null)
@@ -57,6 +57,7 @@ namespace MediaBrowser.Providers.Music
             }
             }
             HttpClient = httpClient;
             HttpClient = httpClient;
             _providerManager = providerManager;
             _providerManager = providerManager;
+            _fileSystem = fileSystem;
 
 
             Current = this;
             Current = this;
         }
         }
@@ -167,7 +168,7 @@ namespace MediaBrowser.Providers.Music
                 {
                 {
                     var files = new DirectoryInfo(path)
                     var files = new DirectoryInfo(path)
                         .EnumerateFiles("*.xml", SearchOption.TopDirectoryOnly)
                         .EnumerateFiles("*.xml", SearchOption.TopDirectoryOnly)
-                        .Select(i => i.LastWriteTimeUtc)
+                        .Select(i => _fileSystem.GetLastWriteTimeUtc(i))
                         .ToList();
                         .ToList();
 
 
                     if (files.Count > 0)
                     if (files.Count > 0)
@@ -284,7 +285,7 @@ namespace MediaBrowser.Providers.Music
 
 
             }).ConfigureAwait(false))
             }).ConfigureAwait(false))
             {
             {
-                using (var xmlFileStream = new FileStream(xmlPath, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
+                using (var xmlFileStream = _fileSystem.GetFileStream(xmlPath, FileMode.Create, FileAccess.Write, FileShare.Read, true))
                 {
                 {
                     await response.CopyToAsync(xmlFileStream).ConfigureAwait(false);
                     await response.CopyToAsync(xmlFileStream).ConfigureAwait(false);
                 }
                 }

+ 6 - 3
MediaBrowser.Providers/Music/FanArtUpdatesPrescanTask.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Common.Net;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
@@ -31,15 +32,17 @@ namespace MediaBrowser.Providers.Music
         /// </summary>
         /// </summary>
         private readonly IServerConfigurationManager _config;
         private readonly IServerConfigurationManager _config;
         private readonly IJsonSerializer _jsonSerializer;
         private readonly IJsonSerializer _jsonSerializer;
+        private readonly IFileSystem _fileSystem;
 
 
         private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
         private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
 
 
-        public FanArtUpdatesPrescanTask(IJsonSerializer jsonSerializer, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient)
+        public FanArtUpdatesPrescanTask(IJsonSerializer jsonSerializer, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient, IFileSystem fileSystem)
         {
         {
             _jsonSerializer = jsonSerializer;
             _jsonSerializer = jsonSerializer;
             _config = config;
             _config = config;
             _logger = logger;
             _logger = logger;
             _httpClient = httpClient;
             _httpClient = httpClient;
+            _fileSystem = fileSystem;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -65,7 +68,7 @@ namespace MediaBrowser.Providers.Music
             var timestampFileInfo = new FileInfo(timestampFile);
             var timestampFileInfo = new FileInfo(timestampFile);
 
 
             // Don't check for tvdb updates anymore frequently than 24 hours
             // Don't check for tvdb updates anymore frequently than 24 hours
-            if (timestampFileInfo.Exists && (DateTime.UtcNow - timestampFileInfo.LastWriteTimeUtc).TotalDays < 1)
+            if (timestampFileInfo.Exists && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(timestampFileInfo)).TotalDays < 1)
             {
             {
                 return;
                 return;
             }
             }

+ 7 - 3
MediaBrowser.Providers/RefreshIntrosTask.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.IO;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using System;
 using System;
@@ -22,15 +23,18 @@ namespace MediaBrowser.Providers
         /// </summary>
         /// </summary>
         private readonly ILogger _logger;
         private readonly ILogger _logger;
 
 
+        private readonly IFileSystem _fileSystem;
+        
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="RefreshIntrosTask"/> class.
         /// Initializes a new instance of the <see cref="RefreshIntrosTask"/> class.
         /// </summary>
         /// </summary>
         /// <param name="libraryManager">The library manager.</param>
         /// <param name="libraryManager">The library manager.</param>
         /// <param name="logger">The logger.</param>
         /// <param name="logger">The logger.</param>
-        public RefreshIntrosTask(ILibraryManager libraryManager, ILogger logger)
+        public RefreshIntrosTask(ILibraryManager libraryManager, ILogger logger, IFileSystem fileSystem)
         {
         {
             _libraryManager = libraryManager;
             _libraryManager = libraryManager;
             _logger = logger;
             _logger = logger;
+            _fileSystem = fileSystem;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -77,7 +81,7 @@ namespace MediaBrowser.Providers
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
         private async Task RefreshIntro(string path, CancellationToken cancellationToken)
         private async Task RefreshIntro(string path, CancellationToken cancellationToken)
         {
         {
-            var item = _libraryManager.ResolvePath(FileSystem.GetFileSystemInfo(path));
+            var item = _libraryManager.ResolvePath(_fileSystem.GetFileSystemInfo(path));
 
 
             if (item == null)
             if (item == null)
             {
             {

+ 18 - 1
MediaBrowser.Providers/Savers/GameXmlSaver.cs

@@ -1,6 +1,7 @@
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Entities;
 using MediaBrowser.Providers.Movies;
 using MediaBrowser.Providers.Movies;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
@@ -70,6 +71,20 @@ namespace MediaBrowser.Providers.Savers
                 builder.Append("<GameSystem>" + SecurityElement.Escape(game.GameSystem) + "</GameSystem>");
                 builder.Append("<GameSystem>" + SecurityElement.Escape(game.GameSystem) + "</GameSystem>");
             }
             }
 
 
+            var val = game.GetProviderId(MetadataProviders.NesBox);
+
+            if (!string.IsNullOrEmpty(val))
+            {
+                builder.Append("<NesBox>" + SecurityElement.Escape(val) + "</NesBox>");
+            }
+
+            val = game.GetProviderId(MetadataProviders.NesBoxRom);
+
+            if (!string.IsNullOrEmpty(val))
+            {
+                builder.Append("<NesBoxRom>" + SecurityElement.Escape(val) + "</NesBoxRom>");
+            }
+
             XmlSaverHelpers.AddCommonNodes(item, builder);
             XmlSaverHelpers.AddCommonNodes(item, builder);
 
 
             builder.Append("</Item>");
             builder.Append("</Item>");
@@ -79,7 +94,9 @@ namespace MediaBrowser.Providers.Savers
             XmlSaverHelpers.Save(builder, xmlFilePath, new List<string>
             XmlSaverHelpers.Save(builder, xmlFilePath, new List<string>
                 {
                 {
                     "Players",
                     "Players",
-                    "GameSystem"
+                    "GameSystem",
+                    "NesBox",
+                    "NesBoxRom"
                 });
                 });
 
 
             // Set last refreshed so that the provider doesn't trigger after the file save
             // Set last refreshed so that the provider doesn't trigger after the file save

+ 6 - 3
MediaBrowser.Providers/TV/EpisodeProviderFromXml.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.IO;
@@ -20,11 +21,13 @@ namespace MediaBrowser.Providers.TV
     {
     {
         internal static EpisodeProviderFromXml Current { get; private set; }
         internal static EpisodeProviderFromXml Current { get; private set; }
         private readonly IItemRepository _itemRepo;
         private readonly IItemRepository _itemRepo;
+        private readonly IFileSystem _fileSystem;
 
 
-        public EpisodeProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager, IItemRepository itemRepo)
+        public EpisodeProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager, IItemRepository itemRepo, IFileSystem fileSystem)
             : base(logManager, configurationManager)
             : base(logManager, configurationManager)
         {
         {
             _itemRepo = itemRepo;
             _itemRepo = itemRepo;
+            _fileSystem = fileSystem;
             Current = this;
             Current = this;
         }
         }
 
 
@@ -76,7 +79,7 @@ namespace MediaBrowser.Providers.TV
                 return false;
                 return false;
             }
             }
 
 
-            return FileSystem.GetLastWriteTimeUtc(file, Logger) > providerInfo.LastRefreshed;
+            return _fileSystem.GetLastWriteTimeUtc(file) > providerInfo.LastRefreshed;
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 6 - 3
MediaBrowser.Providers/TV/FanArtSeasonProvider.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
@@ -22,6 +23,7 @@ namespace MediaBrowser.Providers.TV
         /// The _provider manager
         /// The _provider manager
         /// </summary>
         /// </summary>
         private readonly IProviderManager _providerManager;
         private readonly IProviderManager _providerManager;
+        private readonly IFileSystem _fileSystem;
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="FanArtSeasonProvider"/> class.
         /// Initializes a new instance of the <see cref="FanArtSeasonProvider"/> class.
@@ -29,10 +31,11 @@ namespace MediaBrowser.Providers.TV
         /// <param name="logManager">The log manager.</param>
         /// <param name="logManager">The log manager.</param>
         /// <param name="configurationManager">The configuration manager.</param>
         /// <param name="configurationManager">The configuration manager.</param>
         /// <param name="providerManager">The provider manager.</param>
         /// <param name="providerManager">The provider manager.</param>
-        public FanArtSeasonProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager)
+        public FanArtSeasonProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager, IFileSystem fileSystem)
             : base(logManager, configurationManager)
             : base(logManager, configurationManager)
         {
         {
             _providerManager = providerManager;
             _providerManager = providerManager;
+            _fileSystem = fileSystem;
         }
         }
 
 
         public override ItemUpdateType ItemUpdateType
         public override ItemUpdateType ItemUpdateType
@@ -76,7 +79,7 @@ namespace MediaBrowser.Providers.TV
 
 
                 if (imagesFileInfo.Exists)
                 if (imagesFileInfo.Exists)
                 {
                 {
-                    return imagesFileInfo.LastWriteTimeUtc;
+                    return _fileSystem.GetLastWriteTimeUtc(imagesFileInfo);
                 }
                 }
             }
             }
 
 

+ 6 - 3
MediaBrowser.Providers/TV/FanArtTVProvider.cs

@@ -4,6 +4,7 @@ using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
@@ -31,8 +32,9 @@ namespace MediaBrowser.Providers.TV
         protected IHttpClient HttpClient { get; private set; }
         protected IHttpClient HttpClient { get; private set; }
 
 
         private readonly IProviderManager _providerManager;
         private readonly IProviderManager _providerManager;
+        private readonly IFileSystem _fileSystem;
 
 
-        public FanArtTvProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager)
+        public FanArtTvProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager, IFileSystem fileSystem)
             : base(logManager, configurationManager)
             : base(logManager, configurationManager)
         {
         {
             if (httpClient == null)
             if (httpClient == null)
@@ -41,6 +43,7 @@ namespace MediaBrowser.Providers.TV
             }
             }
             HttpClient = httpClient;
             HttpClient = httpClient;
             _providerManager = providerManager;
             _providerManager = providerManager;
+            _fileSystem = fileSystem;
             Current = this;
             Current = this;
         }
         }
 
 
@@ -115,7 +118,7 @@ namespace MediaBrowser.Providers.TV
                 {
                 {
                     var files = new DirectoryInfo(path)
                     var files = new DirectoryInfo(path)
                         .EnumerateFiles("*.xml", SearchOption.TopDirectoryOnly)
                         .EnumerateFiles("*.xml", SearchOption.TopDirectoryOnly)
-                        .Select(i => i.LastWriteTimeUtc)
+                        .Select(i => _fileSystem.GetLastWriteTimeUtc(i))
                         .ToList();
                         .ToList();
 
 
                     if (files.Count > 0)
                     if (files.Count > 0)
@@ -353,7 +356,7 @@ namespace MediaBrowser.Providers.TV
 
 
             }).ConfigureAwait(false))
             }).ConfigureAwait(false))
             {
             {
-                using (var xmlFileStream = new FileStream(xmlPath, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
+                using (var xmlFileStream = _fileSystem.GetFileStream(xmlPath, FileMode.Create, FileAccess.Write, FileShare.Read, true))
                 {
                 {
                     await response.CopyToAsync(xmlFileStream).ConfigureAwait(false);
                     await response.CopyToAsync(xmlFileStream).ConfigureAwait(false);
                 }
                 }

+ 6 - 3
MediaBrowser.Providers/TV/FanArtTvUpdatesPrescanTask.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Common.Net;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
@@ -32,15 +33,17 @@ namespace MediaBrowser.Providers.TV
         /// </summary>
         /// </summary>
         private readonly IServerConfigurationManager _config;
         private readonly IServerConfigurationManager _config;
         private readonly IJsonSerializer _jsonSerializer;
         private readonly IJsonSerializer _jsonSerializer;
+        private readonly IFileSystem _fileSystem;
 
 
         private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
         private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
 
 
-        public FanArtTvUpdatesPrescanTask(IJsonSerializer jsonSerializer, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient)
+        public FanArtTvUpdatesPrescanTask(IJsonSerializer jsonSerializer, IServerConfigurationManager config, ILogger logger, IHttpClient httpClient, IFileSystem fileSystem)
         {
         {
             _jsonSerializer = jsonSerializer;
             _jsonSerializer = jsonSerializer;
             _config = config;
             _config = config;
             _logger = logger;
             _logger = logger;
             _httpClient = httpClient;
             _httpClient = httpClient;
+            _fileSystem = fileSystem;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -66,7 +69,7 @@ namespace MediaBrowser.Providers.TV
             var timestampFileInfo = new FileInfo(timestampFile);
             var timestampFileInfo = new FileInfo(timestampFile);
 
 
             // Don't check for tvdb updates anymore frequently than 24 hours
             // Don't check for tvdb updates anymore frequently than 24 hours
-            if (timestampFileInfo.Exists && (DateTime.UtcNow - timestampFileInfo.LastWriteTimeUtc).TotalDays < 1)
+            if (timestampFileInfo.Exists && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(timestampFileInfo)).TotalDays < 1)
             {
             {
                 return;
                 return;
             }
             }

+ 11 - 19
MediaBrowser.Providers/TV/RemoteEpisodeProvider.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Common.Net;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Entities.TV;
@@ -36,6 +37,7 @@ namespace MediaBrowser.Providers.TV
         /// </summary>
         /// </summary>
         /// <value>The HTTP client.</value>
         /// <value>The HTTP client.</value>
         protected IHttpClient HttpClient { get; private set; }
         protected IHttpClient HttpClient { get; private set; }
+        private readonly IFileSystem _fileSystem;
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="RemoteEpisodeProvider" /> class.
         /// Initializes a new instance of the <see cref="RemoteEpisodeProvider" /> class.
@@ -44,11 +46,12 @@ namespace MediaBrowser.Providers.TV
         /// <param name="logManager">The log manager.</param>
         /// <param name="logManager">The log manager.</param>
         /// <param name="configurationManager">The configuration manager.</param>
         /// <param name="configurationManager">The configuration manager.</param>
         /// <param name="providerManager">The provider manager.</param>
         /// <param name="providerManager">The provider manager.</param>
-        public RemoteEpisodeProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager)
+        public RemoteEpisodeProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager, IFileSystem fileSystem)
             : base(logManager, configurationManager)
             : base(logManager, configurationManager)
         {
         {
             HttpClient = httpClient;
             HttpClient = httpClient;
             _providerManager = providerManager;
             _providerManager = providerManager;
+            _fileSystem = fileSystem;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -149,7 +152,7 @@ namespace MediaBrowser.Providers.TV
 
 
                 if (files.Count > 0)
                 if (files.Count > 0)
                 {
                 {
-                    return files.Select(i => i.LastWriteTimeUtc).Max() > providerInfo.LastRefreshed;
+                    return files.Select(i => _fileSystem.GetLastWriteTimeUtc(i)).Max() > providerInfo.LastRefreshed;
                 }
                 }
             }
             }
             
             
@@ -240,17 +243,16 @@ namespace MediaBrowser.Providers.TV
         {
         {
             cancellationToken.ThrowIfCancellationRequested();
             cancellationToken.ThrowIfCancellationRequested();
 
 
+            var status = ProviderRefreshStatus.Success;
+
             var episode = (Episode)item;
             var episode = (Episode)item;
 
 
             var seriesId = episode.Series != null ? episode.Series.GetProviderId(MetadataProviders.Tvdb) : null;
             var seriesId = episode.Series != null ? episode.Series.GetProviderId(MetadataProviders.Tvdb) : null;
 
 
             if (!string.IsNullOrEmpty(seriesId))
             if (!string.IsNullOrEmpty(seriesId))
             {
             {
-                var seriesDataPath = RemoteSeriesProvider.GetSeriesDataPath(ConfigurationManager.ApplicationPaths,
-                                                                            seriesId);
+                var seriesDataPath = RemoteSeriesProvider.GetSeriesDataPath(ConfigurationManager.ApplicationPaths, seriesId);
 
 
-                var status = ProviderRefreshStatus.Success;
-                
                 try
                 try
                 {
                 {
                     status = await FetchEpisodeData(episode, seriesDataPath, cancellationToken).ConfigureAwait(false);
                     status = await FetchEpisodeData(episode, seriesDataPath, cancellationToken).ConfigureAwait(false);
@@ -259,20 +261,10 @@ namespace MediaBrowser.Providers.TV
                 {
                 {
                     // Don't fail the provider because this will just keep on going and going.
                     // Don't fail the provider because this will just keep on going and going.
                 }
                 }
-
-                BaseProviderInfo data;
-                if (!item.ProviderData.TryGetValue(Id, out data))
-                {
-                    data = new BaseProviderInfo();
-                    item.ProviderData[Id] = data;
-                }
-
-                SetLastRefreshed(item, DateTime.UtcNow, status);
-                return true;
             }
             }
 
 
-            Logger.Info("Episode provider not fetching because series does not have a tvdb id: " + item.Path);
-            return false;
+            SetLastRefreshed(item, DateTime.UtcNow, status);
+            return true;
         }
         }
 
 
 
 

+ 34 - 5
MediaBrowser.Providers/TV/RemoteSeasonProvider.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
@@ -23,6 +24,7 @@ namespace MediaBrowser.Providers.TV
         /// The _provider manager
         /// The _provider manager
         /// </summary>
         /// </summary>
         private readonly IProviderManager _providerManager;
         private readonly IProviderManager _providerManager;
+        private readonly IFileSystem _fileSystem;
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="RemoteSeasonProvider"/> class.
         /// Initializes a new instance of the <see cref="RemoteSeasonProvider"/> class.
@@ -31,10 +33,11 @@ namespace MediaBrowser.Providers.TV
         /// <param name="configurationManager">The configuration manager.</param>
         /// <param name="configurationManager">The configuration manager.</param>
         /// <param name="providerManager">The provider manager.</param>
         /// <param name="providerManager">The provider manager.</param>
         /// <exception cref="System.ArgumentNullException">httpClient</exception>
         /// <exception cref="System.ArgumentNullException">httpClient</exception>
-        public RemoteSeasonProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager)
+        public RemoteSeasonProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager, IFileSystem fileSystem)
             : base(logManager, configurationManager)
             : base(logManager, configurationManager)
         {
         {
             _providerManager = providerManager;
             _providerManager = providerManager;
+            _fileSystem = fileSystem;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -115,7 +118,7 @@ namespace MediaBrowser.Providers.TV
 
 
                 if (imagesFileInfo.Exists)
                 if (imagesFileInfo.Exists)
                 {
                 {
-                    return imagesFileInfo.LastWriteTimeUtc > providerInfo.LastRefreshed;
+                    return _fileSystem.GetLastWriteTimeUtc(imagesFileInfo) > providerInfo.LastRefreshed;
                 }
                 }
             }
             }
             return false;
             return false;
@@ -275,6 +278,7 @@ namespace MediaBrowser.Providers.TV
             string url = null;
             string url = null;
             int? bannerSeason = null;
             int? bannerSeason = null;
             string resolution = null;
             string resolution = null;
+            string language = null;
 
 
             while (reader.Read())
             while (reader.Read())
             {
             {
@@ -282,6 +286,12 @@ namespace MediaBrowser.Providers.TV
                 {
                 {
                     switch (reader.Name)
                     switch (reader.Name)
                     {
                     {
+                        case "Language":
+                            {
+                                language = reader.ReadElementContentAsString() ?? string.Empty;
+                                break;
+                            }
+
                         case "BannerType":
                         case "BannerType":
                             {
                             {
                                 bannerType = reader.ReadElementContentAsString() ?? string.Empty;
                                 bannerType = reader.ReadElementContentAsString() ?? string.Empty;
@@ -325,11 +335,30 @@ namespace MediaBrowser.Providers.TV
                 {
                 {
                     if (string.Equals(bannerType2, "season", StringComparison.OrdinalIgnoreCase))
                     if (string.Equals(bannerType2, "season", StringComparison.OrdinalIgnoreCase))
                     {
                     {
-                        data.Poster = url;
+                        // Just grab the first
+                        if (string.IsNullOrWhiteSpace(data.Poster))
+                        {
+                            data.Poster = url;
+                        }
                     }
                     }
                     else if (string.Equals(bannerType2, "seasonwide", StringComparison.OrdinalIgnoreCase))
                     else if (string.Equals(bannerType2, "seasonwide", StringComparison.OrdinalIgnoreCase))
                     {
                     {
-                        data.Banner = url;
+                        if (string.IsNullOrWhiteSpace(language) || string.Equals(language, "en", StringComparison.OrdinalIgnoreCase))
+                        {
+                            // Just grab the first
+                            if (string.IsNullOrWhiteSpace(data.Banner))
+                            {
+                                data.Banner = url;
+                            }
+                        }
+                        else if (string.Equals(language, ConfigurationManager.Configuration.PreferredMetadataLanguage, StringComparison.OrdinalIgnoreCase))
+                        {
+                            // Just grab the first
+                            if (string.IsNullOrWhiteSpace(data.LanguageBanner))
+                            {
+                                data.LanguageBanner = url;
+                            }
+                        }
                     }
                     }
                 }
                 }
                 else if (string.Equals(bannerType, "fanart", StringComparison.OrdinalIgnoreCase))
                 else if (string.Equals(bannerType, "fanart", StringComparison.OrdinalIgnoreCase))

+ 32 - 12
MediaBrowser.Providers/TV/RemoteSeriesProvider.cs

@@ -4,6 +4,7 @@ using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
@@ -49,6 +50,8 @@ namespace MediaBrowser.Providers.TV
         /// <value>The HTTP client.</value>
         /// <value>The HTTP client.</value>
         protected IHttpClient HttpClient { get; private set; }
         protected IHttpClient HttpClient { get; private set; }
 
 
+        private readonly IFileSystem _fileSystem;
+
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="RemoteSeriesProvider" /> class.
         /// Initializes a new instance of the <see cref="RemoteSeriesProvider" /> class.
         /// </summary>
         /// </summary>
@@ -57,7 +60,7 @@ namespace MediaBrowser.Providers.TV
         /// <param name="configurationManager">The configuration manager.</param>
         /// <param name="configurationManager">The configuration manager.</param>
         /// <param name="zipClient">The zip client.</param>
         /// <param name="zipClient">The zip client.</param>
         /// <exception cref="System.ArgumentNullException">httpClient</exception>
         /// <exception cref="System.ArgumentNullException">httpClient</exception>
-        public RemoteSeriesProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IZipClient zipClient)
+        public RemoteSeriesProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IZipClient zipClient, IFileSystem fileSystem)
             : base(logManager, configurationManager)
             : base(logManager, configurationManager)
         {
         {
             if (httpClient == null)
             if (httpClient == null)
@@ -66,6 +69,7 @@ namespace MediaBrowser.Providers.TV
             }
             }
             HttpClient = httpClient;
             HttpClient = httpClient;
             _zipClient = zipClient;
             _zipClient = zipClient;
+            _fileSystem = fileSystem;
             Current = this;
             Current = this;
         }
         }
 
 
@@ -176,7 +180,7 @@ namespace MediaBrowser.Providers.TV
                 {
                 {
                     var files = new DirectoryInfo(path)
                     var files = new DirectoryInfo(path)
                         .EnumerateFiles("*.xml", SearchOption.TopDirectoryOnly)
                         .EnumerateFiles("*.xml", SearchOption.TopDirectoryOnly)
-                        .Select(i => i.LastWriteTimeUtc)
+                        .Select(i => _fileSystem.GetLastWriteTimeUtc(i))
                         .ToList();
                         .ToList();
 
 
                     if (files.Count > 0)
                     if (files.Count > 0)
@@ -344,7 +348,7 @@ namespace MediaBrowser.Providers.TV
         {
         {
             string validXml;
             string validXml;
 
 
-            using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, true))
+            using (var fileStream = _fileSystem.GetFileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read, true))
             {
             {
                 using (var reader = new StreamReader(fileStream))
                 using (var reader = new StreamReader(fileStream))
                 {
                 {
@@ -354,7 +358,7 @@ namespace MediaBrowser.Providers.TV
                 }
                 }
             }
             }
 
 
-            using (var fileStream = new FileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, true))
+            using (var fileStream = _fileSystem.GetFileStream(file, FileMode.Create, FileAccess.Write, FileShare.Read, true))
             {
             {
                 using (var writer = new StreamWriter(fileStream))
                 using (var writer = new StreamWriter(fileStream))
                 {
                 {
@@ -1109,21 +1113,37 @@ namespace MediaBrowser.Providers.TV
                 var nodes = doc.SelectNodes("//Series");
                 var nodes = doc.SelectNodes("//Series");
                 var comparableName = GetComparableName(name);
                 var comparableName = GetComparableName(name);
                 if (nodes != null)
                 if (nodes != null)
+                {
                     foreach (XmlNode node in nodes)
                     foreach (XmlNode node in nodes)
                     {
                     {
-                        var n = node.SelectSingleNode("./SeriesName");
-                        if (n != null && string.Equals(GetComparableName(n.InnerText), comparableName, StringComparison.OrdinalIgnoreCase))
+                        var titles = new List<string>();
+
+                        var nameNode = node.SelectSingleNode("./SeriesName");
+                        if (nameNode != null)
                         {
                         {
-                            n = node.SelectSingleNode("./seriesid");
-                            if (n != null)
-                                return n.InnerText;
+                            titles.Add(GetComparableName(nameNode.InnerText));
                         }
                         }
-                        else
+
+                        var aliasNode = node.SelectSingleNode("./AliasNames");
+                        if (aliasNode != null)
                         {
                         {
-                            if (n != null)
-                                Logger.Info("TVDb Provider - " + n.InnerText + " did not match " + comparableName);
+                            var alias = aliasNode.InnerText.Split('|').Select(GetComparableName);
+                            titles.AddRange(alias);
+                        }
+
+                        if (titles.Any(t => string.Equals(t, comparableName, StringComparison.OrdinalIgnoreCase)))
+                        {
+                            var id = node.SelectSingleNode("./seriesid");
+                            if (id != null)
+                                return id.InnerText;
+                        }
+
+                        foreach (var title in titles)
+                        {
+                            Logger.Info("TVDb Provider - " + title + " did not match " + comparableName);
                         }
                         }
                     }
                     }
+                }
             }
             }
 
 
             // Try stripping off the year if it was supplied
             // Try stripping off the year if it was supplied

+ 6 - 3
MediaBrowser.Providers/TV/SeasonProviderFromXml.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.IO;
@@ -18,10 +19,12 @@ namespace MediaBrowser.Providers.TV
     public class SeasonProviderFromXml : BaseMetadataProvider
     public class SeasonProviderFromXml : BaseMetadataProvider
     {
     {
         public static SeasonProviderFromXml Current;
         public static SeasonProviderFromXml Current;
+        private readonly IFileSystem _fileSystem;
 
 
-        public SeasonProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager)
+        public SeasonProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem)
             : base(logManager, configurationManager)
             : base(logManager, configurationManager)
         {
         {
+            _fileSystem = fileSystem;
             Current = this;
             Current = this;
         }
         }
 
 
@@ -54,7 +57,7 @@ namespace MediaBrowser.Providers.TV
                 return false;
                 return false;
             }
             }
 
 
-            return FileSystem.GetLastWriteTimeUtc(xml, Logger) > providerInfo.LastRefreshed;
+            return _fileSystem.GetLastWriteTimeUtc(xml) > providerInfo.LastRefreshed;
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 19 - 6
MediaBrowser.Providers/TV/SeriesPostScanTask.cs

@@ -39,6 +39,13 @@ namespace MediaBrowser.Providers.TV
 
 
         private async Task RunInternal(IProgress<double> progress, CancellationToken cancellationToken)
         private async Task RunInternal(IProgress<double> progress, CancellationToken cancellationToken)
         {
         {
+            if (!_config.Configuration.EnableInternetProviders ||
+                _config.Configuration.InternetProviderExcludeTypes.Contains(typeof(Series).Name, StringComparer.OrdinalIgnoreCase))
+            {
+                progress.Report(100);
+                return;
+            }
+
             var seriesList = _libraryManager.RootFolder
             var seriesList = _libraryManager.RootFolder
                 .RecursiveChildren
                 .RecursiveChildren
                 .OfType<Series>()
                 .OfType<Series>()
@@ -136,21 +143,27 @@ namespace MediaBrowser.Providers.TV
                 .Where(i => i.Item1 != -1 && i.Item2 != -1)
                 .Where(i => i.Item1 != -1 && i.Item2 != -1)
                 .ToList();
                 .ToList();
 
 
-            var anySeasonsRemoved = await RemoveObsoleteOrMissingSeasons(series, episodeLookup, cancellationToken).ConfigureAwait(false);
+            var anySeasonsRemoved = await RemoveObsoleteOrMissingSeasons(series, episodeLookup, cancellationToken)
+                .ConfigureAwait(false);
 
 
-            var anyEpisodesRemoved = await RemoveObsoleteOrMissingEpisodes(series, episodeLookup, cancellationToken).ConfigureAwait(false);
+            var anyEpisodesRemoved = await RemoveObsoleteOrMissingEpisodes(series, episodeLookup, cancellationToken)
+                .ConfigureAwait(false);
 
 
             var hasNewEpisodes = false;
             var hasNewEpisodes = false;
 
 
             if (_config.Configuration.EnableInternetProviders)
             if (_config.Configuration.EnableInternetProviders)
             {
             {
-                hasNewEpisodes = await AddMissingEpisodes(series, seriesDataPath, episodeLookup, cancellationToken).ConfigureAwait(false);
+                hasNewEpisodes = await AddMissingEpisodes(series, seriesDataPath, episodeLookup, cancellationToken)
+                    .ConfigureAwait(false);
             }
             }
 
 
             if (hasNewEpisodes || anySeasonsRemoved || anyEpisodesRemoved)
             if (hasNewEpisodes || anySeasonsRemoved || anyEpisodesRemoved)
             {
             {
-                await series.RefreshMetadata(cancellationToken, true).ConfigureAwait(false);
-                await series.ValidateChildren(new Progress<double>(), cancellationToken, true).ConfigureAwait(false);
+                await series.RefreshMetadata(cancellationToken, true)
+                    .ConfigureAwait(false);
+
+                await series.ValidateChildren(new Progress<double>(), cancellationToken, true)
+                    .ConfigureAwait(false);
             }
             }
         }
         }
 
 
@@ -211,7 +224,7 @@ namespace MediaBrowser.Providers.TV
                 else if (airDate.Value > now)
                 else if (airDate.Value > now)
                 {
                 {
                     // tvdb has a lot of nearly blank episodes
                     // tvdb has a lot of nearly blank episodes
-                    _logger.Info("Creating virtual future episode {0} {1}x{2}", series.Name, tuple.Item1, tuple.Item2);
+                    _logger.Info("Creating virtual unaired episode {0} {1}x{2}", series.Name, tuple.Item1, tuple.Item2);
 
 
                     await AddEpisode(series, tuple.Item1, tuple.Item2, cancellationToken).ConfigureAwait(false);
                     await AddEpisode(series, tuple.Item1, tuple.Item2, cancellationToken).ConfigureAwait(false);
 
 

+ 7 - 4
MediaBrowser.Providers/TV/SeriesProviderFromXml.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.IO;
@@ -18,10 +19,12 @@ namespace MediaBrowser.Providers.TV
     public class SeriesProviderFromXml : BaseMetadataProvider
     public class SeriesProviderFromXml : BaseMetadataProvider
     {
     {
         internal static SeriesProviderFromXml Current { get; private set; }
         internal static SeriesProviderFromXml Current { get; private set; }
-        
-        public SeriesProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager)
+        private readonly IFileSystem _fileSystem;
+
+        public SeriesProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem)
             : base(logManager, configurationManager)
             : base(logManager, configurationManager)
         {
         {
+            _fileSystem = fileSystem;
             Current = this;
             Current = this;
         }
         }
 
 
@@ -54,7 +57,7 @@ namespace MediaBrowser.Providers.TV
                 return false;
                 return false;
             }
             }
 
 
-            return FileSystem.GetLastWriteTimeUtc(xml, Logger) > providerInfo.LastRefreshed;
+            return _fileSystem.GetLastWriteTimeUtc(xml) > providerInfo.LastRefreshed;
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 9 - 4
MediaBrowser.Providers/TV/TvdbPrescanTask.cs

@@ -1,11 +1,13 @@
-using System.Globalization;
+using MediaBrowser.Common.IO;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Net;
 using MediaBrowser.Model.Net;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Globalization;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
 using System.Text;
 using System.Text;
@@ -42,6 +44,7 @@ namespace MediaBrowser.Providers.TV
         /// The _config
         /// The _config
         /// </summary>
         /// </summary>
         private readonly IServerConfigurationManager _config;
         private readonly IServerConfigurationManager _config;
+        private readonly IFileSystem _fileSystem;
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="TvdbPrescanTask"/> class.
         /// Initializes a new instance of the <see cref="TvdbPrescanTask"/> class.
@@ -49,11 +52,12 @@ namespace MediaBrowser.Providers.TV
         /// <param name="logger">The logger.</param>
         /// <param name="logger">The logger.</param>
         /// <param name="httpClient">The HTTP client.</param>
         /// <param name="httpClient">The HTTP client.</param>
         /// <param name="config">The config.</param>
         /// <param name="config">The config.</param>
-        public TvdbPrescanTask(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config)
+        public TvdbPrescanTask(ILogger logger, IHttpClient httpClient, IServerConfigurationManager config, IFileSystem fileSystem)
         {
         {
             _logger = logger;
             _logger = logger;
             _httpClient = httpClient;
             _httpClient = httpClient;
             _config = config;
             _config = config;
+            _fileSystem = fileSystem;
         }
         }
 
 
         protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
         protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
@@ -66,7 +70,8 @@ namespace MediaBrowser.Providers.TV
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
         public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
         public async Task Run(IProgress<double> progress, CancellationToken cancellationToken)
         {
         {
-            if (!_config.Configuration.EnableInternetProviders)
+            if (!_config.Configuration.EnableInternetProviders || 
+                _config.Configuration.InternetProviderExcludeTypes.Contains(typeof(Series).Name, StringComparer.OrdinalIgnoreCase))
             {
             {
                 progress.Report(100);
                 progress.Report(100);
                 return;
                 return;
@@ -81,7 +86,7 @@ namespace MediaBrowser.Providers.TV
             var timestampFileInfo = new FileInfo(timestampFile);
             var timestampFileInfo = new FileInfo(timestampFile);
 
 
             // Don't check for tvdb updates anymore frequently than 24 hours
             // Don't check for tvdb updates anymore frequently than 24 hours
-            if (timestampFileInfo.Exists && (DateTime.UtcNow - timestampFileInfo.LastWriteTimeUtc).TotalDays < 1)
+            if (timestampFileInfo.Exists && (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(timestampFileInfo)).TotalDays < 1)
             {
             {
                 return;
                 return;
             }
             }

+ 17 - 5
MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs

@@ -1,8 +1,9 @@
-using System.Linq;
+using MediaBrowser.Common.IO;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
@@ -11,6 +12,7 @@ using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Globalization;
 using System.Globalization;
 using System.IO;
 using System.IO;
+using System.Linq;
 using System.Text;
 using System.Text;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
@@ -30,6 +32,7 @@ namespace MediaBrowser.Providers.TV
         /// The _provider manager
         /// The _provider manager
         /// </summary>
         /// </summary>
         private readonly IProviderManager _providerManager;
         private readonly IProviderManager _providerManager;
+        private readonly IFileSystem _fileSystem;
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="TvdbSeriesImageProvider"/> class.
         /// Initializes a new instance of the <see cref="TvdbSeriesImageProvider"/> class.
@@ -39,7 +42,7 @@ namespace MediaBrowser.Providers.TV
         /// <param name="configurationManager">The configuration manager.</param>
         /// <param name="configurationManager">The configuration manager.</param>
         /// <param name="providerManager">The provider manager.</param>
         /// <param name="providerManager">The provider manager.</param>
         /// <exception cref="System.ArgumentNullException">httpClient</exception>
         /// <exception cref="System.ArgumentNullException">httpClient</exception>
-        public TvdbSeriesImageProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager)
+        public TvdbSeriesImageProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager, IFileSystem fileSystem)
             : base(logManager, configurationManager)
             : base(logManager, configurationManager)
         {
         {
             if (httpClient == null)
             if (httpClient == null)
@@ -48,6 +51,7 @@ namespace MediaBrowser.Providers.TV
             }
             }
             HttpClient = httpClient;
             HttpClient = httpClient;
             _providerManager = providerManager;
             _providerManager = providerManager;
+            _fileSystem = fileSystem;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -127,7 +131,7 @@ namespace MediaBrowser.Providers.TV
 
 
                 if (imagesFileInfo.Exists)
                 if (imagesFileInfo.Exists)
                 {
                 {
-                    return imagesFileInfo.LastWriteTimeUtc;
+                    return _fileSystem.GetLastWriteTimeUtc(imagesFileInfo);
                 }
                 }
             }
             }
 
 
@@ -373,11 +377,19 @@ namespace MediaBrowser.Providers.TV
             {
             {
                 if (string.Equals(type, "poster", StringComparison.OrdinalIgnoreCase))
                 if (string.Equals(type, "poster", StringComparison.OrdinalIgnoreCase))
                 {
                 {
-                    data.Poster = url;
+                    // Just grab the first
+                    if (string.IsNullOrWhiteSpace(data.Poster))
+                    {
+                        data.Poster = url;
+                    }
                 }
                 }
                 else if (string.Equals(type, "series", StringComparison.OrdinalIgnoreCase))
                 else if (string.Equals(type, "series", StringComparison.OrdinalIgnoreCase))
                 {
                 {
-                    data.Banner = url;
+                    // Just grab the first
+                    if (string.IsNullOrWhiteSpace(data.Banner))
+                    {
+                        data.Banner = url;
+                    }
                 }
                 }
                 else if (string.Equals(type, "fanart", StringComparison.OrdinalIgnoreCase))
                 else if (string.Equals(type, "fanart", StringComparison.OrdinalIgnoreCase))
                 {
                 {

+ 6 - 3
MediaBrowser.Server.Implementations/Drawing/ImageHeader.cs

@@ -1,4 +1,6 @@
-using MediaBrowser.Model.Logging;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.IO;
+using MediaBrowser.Model.Logging;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Drawing;
 using System.Drawing;
@@ -40,9 +42,10 @@ namespace MediaBrowser.Server.Implementations.Drawing
         /// </summary>
         /// </summary>
         /// <param name="path">The path of the image to get the dimensions of.</param>
         /// <param name="path">The path of the image to get the dimensions of.</param>
         /// <param name="logger">The logger.</param>
         /// <param name="logger">The logger.</param>
+        /// <param name="fileSystem">The file system.</param>
         /// <returns>The dimensions of the specified image.</returns>
         /// <returns>The dimensions of the specified image.</returns>
         /// <exception cref="ArgumentException">The image was of an unrecognised format.</exception>
         /// <exception cref="ArgumentException">The image was of an unrecognised format.</exception>
-        public static Size GetDimensions(string path, ILogger logger)
+        public static Size GetDimensions(string path, ILogger logger, IFileSystem fileSystem)
         {
         {
             try
             try
             {
             {
@@ -60,7 +63,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
             }
             }
 
 
             // Buffer to memory stream to avoid image locking file
             // Buffer to memory stream to avoid image locking file
-            using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
+            using (var fs = fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
             {
             {
                 using (var memoryStream = new MemoryStream())
                 using (var memoryStream = new MemoryStream())
                 {
                 {

+ 13 - 10
MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs

@@ -3,6 +3,7 @@ using MediaBrowser.Common.IO;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller;
 using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Drawing;
 using MediaBrowser.Model.Drawing;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
@@ -51,16 +52,18 @@ namespace MediaBrowser.Server.Implementations.Drawing
         /// The _app paths
         /// The _app paths
         /// </summary>
         /// </summary>
         private readonly IServerApplicationPaths _appPaths;
         private readonly IServerApplicationPaths _appPaths;
+        private readonly IFileSystem _fileSystem;
 
 
         private readonly string _imageSizeCachePath;
         private readonly string _imageSizeCachePath;
         private readonly string _croppedWhitespaceImageCachePath;
         private readonly string _croppedWhitespaceImageCachePath;
         private readonly string _enhancedImageCachePath;
         private readonly string _enhancedImageCachePath;
         private readonly string _resizedImageCachePath;
         private readonly string _resizedImageCachePath;
 
 
-        public ImageProcessor(ILogger logger, IServerApplicationPaths appPaths)
+        public ImageProcessor(ILogger logger, IServerApplicationPaths appPaths, IFileSystem fileSystem)
         {
         {
             _logger = logger;
             _logger = logger;
             _appPaths = appPaths;
             _appPaths = appPaths;
+            _fileSystem = fileSystem;
 
 
             _imageSizeCachePath = Path.Combine(_appPaths.ImageCachePath, "image-sizes");
             _imageSizeCachePath = Path.Combine(_appPaths.ImageCachePath, "image-sizes");
             _croppedWhitespaceImageCachePath = Path.Combine(_appPaths.ImageCachePath, "cropped-images");
             _croppedWhitespaceImageCachePath = Path.Combine(_appPaths.ImageCachePath, "cropped-images");
@@ -113,7 +116,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
 
 
             try
             try
             {
             {
-                using (var fileStream = new FileStream(cacheFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
+                using (var fileStream = _fileSystem.GetFileStream(cacheFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, true))
                 {
                 {
                     await fileStream.CopyToAsync(toStream).ConfigureAwait(false);
                     await fileStream.CopyToAsync(toStream).ConfigureAwait(false);
                     return;
                     return;
@@ -131,7 +134,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
             // Check again in case of lock contention
             // Check again in case of lock contention
             try
             try
             {
             {
-                using (var fileStream = new FileStream(cacheFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
+                using (var fileStream = _fileSystem.GetFileStream(cacheFilePath, FileMode.Open, FileAccess.Read, FileShare.Read, true))
                 {
                 {
                     await fileStream.CopyToAsync(toStream).ConfigureAwait(false);
                     await fileStream.CopyToAsync(toStream).ConfigureAwait(false);
                     semaphore.Release();
                     semaphore.Release();
@@ -150,7 +153,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
 
 
             try
             try
             {
             {
-                using (var fileStream = new FileStream(originalImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, true))
+                using (var fileStream = _fileSystem.GetFileStream(originalImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true))
                 {
                 {
                     // Copy to memory stream to avoid Image locking file
                     // Copy to memory stream to avoid Image locking file
                     using (var memoryStream = new MemoryStream())
                     using (var memoryStream = new MemoryStream())
@@ -228,7 +231,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
                     Directory.CreateDirectory(parentPath);
                     Directory.CreateDirectory(parentPath);
 
 
                     // Save to the cache location
                     // Save to the cache location
-                    using (var cacheFileStream = new FileStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
+                    using (var cacheFileStream = _fileSystem.GetFileStream(cacheFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, true))
                     {
                     {
                         // Save to the filestream
                         // Save to the filestream
                         await cacheFileStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
                         await cacheFileStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
@@ -359,7 +362,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
 
 
             try
             try
             {
             {
-                using (var fileStream = new FileStream(originalImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, true))
+                using (var fileStream = _fileSystem.GetFileStream(originalImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true))
                 {
                 {
                     // Copy to memory stream to avoid Image locking file
                     // Copy to memory stream to avoid Image locking file
                     using (var memoryStream = new MemoryStream())
                     using (var memoryStream = new MemoryStream())
@@ -376,7 +379,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
 
 
                                 Directory.CreateDirectory(parentPath);
                                 Directory.CreateDirectory(parentPath);
 
 
-                                using (var outputStream = new FileStream(croppedImagePath, FileMode.Create, FileAccess.Write, FileShare.Read))
+                                using (var outputStream = _fileSystem.GetFileStream(croppedImagePath, FileMode.Create, FileAccess.Write, FileShare.Read, false))
                                 {
                                 {
                                     croppedImage.Save(outputFormat, outputStream, 100);
                                     croppedImage.Save(outputFormat, outputStream, 100);
                                 }
                                 }
@@ -525,7 +528,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
                     // Cache file doesn't exist no biggie
                     // Cache file doesn't exist no biggie
                 }
                 }
 
 
-                var size = ImageHeader.GetDimensions(path, _logger);
+                var size = ImageHeader.GetDimensions(path, _logger, _fileSystem);
 
 
                 var parentPath = Path.GetDirectoryName(fullCachePath);
                 var parentPath = Path.GetDirectoryName(fullCachePath);
 
 
@@ -685,7 +688,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
 
 
             try
             try
             {
             {
-                using (var fileStream = new FileStream(originalImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, true))
+                using (var fileStream = _fileSystem.GetFileStream(originalImagePath, FileMode.Open, FileAccess.Read, FileShare.Read, true))
                 {
                 {
                     // Copy to memory stream to avoid Image locking file
                     // Copy to memory stream to avoid Image locking file
                     using (var memoryStream = new MemoryStream())
                     using (var memoryStream = new MemoryStream())
@@ -702,7 +705,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
                                 Directory.CreateDirectory(parentDirectory);
                                 Directory.CreateDirectory(parentDirectory);
 
 
                                 //And then save it in the cache
                                 //And then save it in the cache
-                                using (var outputStream = new FileStream(enhancedImagePath, FileMode.Create, FileAccess.Write, FileShare.Read))
+                                using (var outputStream = _fileSystem.GetFileStream(enhancedImagePath, FileMode.Create, FileAccess.Write, FileShare.Read, false))
                                 {
                                 {
                                     newImage.Save(ImageFormat.Png, outputStream, 100);
                                     newImage.Save(ImageFormat.Png, outputStream, 100);
                                 }
                                 }

+ 12 - 0
MediaBrowser.Server.Implementations/Dto/DtoService.cs

@@ -373,6 +373,11 @@ namespace MediaBrowser.Server.Implementations.Dto
             dto.GameSystem = item.GameSystem;
             dto.GameSystem = item.GameSystem;
         }
         }
 
 
+        private void SetGameSystemProperties(BaseItemDto dto, GameSystem item)
+        {
+            dto.GameSystem = item.GameSystemName;
+        }
+
         /// <summary>
         /// <summary>
         /// Gets the backdrop image tags.
         /// Gets the backdrop image tags.
         /// </summary>
         /// </summary>
@@ -1064,6 +1069,13 @@ namespace MediaBrowser.Server.Implementations.Dto
                 SetGameProperties(dto, game);
                 SetGameProperties(dto, game);
             }
             }
 
 
+            var gameSystem = item as GameSystem;
+
+            if (gameSystem != null)
+            {
+                SetGameSystemProperties(dto, gameSystem);
+            }
+
             var musicVideo = item as MusicVideo;
             var musicVideo = item as MusicVideo;
 
 
             if (musicVideo != null)
             if (musicVideo != null)

+ 6 - 2
MediaBrowser.Server.Implementations/EntryPoints/Notifications/RemoteNotifications.cs

@@ -1,5 +1,7 @@
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.IO;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Notifications;
 using MediaBrowser.Controller.Notifications;
 using MediaBrowser.Controller.Plugins;
 using MediaBrowser.Controller.Plugins;
@@ -26,11 +28,12 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
         private readonly IJsonSerializer _json;
         private readonly IJsonSerializer _json;
         private readonly INotificationsRepository _notificationsRepo;
         private readonly INotificationsRepository _notificationsRepo;
         private readonly IUserManager _userManager;
         private readonly IUserManager _userManager;
+        private readonly IFileSystem _fileSystem;
 
 
         private readonly TimeSpan _frequency = TimeSpan.FromHours(6);
         private readonly TimeSpan _frequency = TimeSpan.FromHours(6);
         private readonly TimeSpan _maxAge = TimeSpan.FromDays(31);
         private readonly TimeSpan _maxAge = TimeSpan.FromDays(31);
 
 
-        public RemoteNotifications(IApplicationPaths appPaths, ILogger logger, IHttpClient httpClient, IJsonSerializer json, INotificationsRepository notificationsRepo, IUserManager userManager)
+        public RemoteNotifications(IApplicationPaths appPaths, ILogger logger, IHttpClient httpClient, IJsonSerializer json, INotificationsRepository notificationsRepo, IUserManager userManager, IFileSystem fileSystem)
         {
         {
             _appPaths = appPaths;
             _appPaths = appPaths;
             _logger = logger;
             _logger = logger;
@@ -38,6 +41,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
             _json = json;
             _json = json;
             _notificationsRepo = notificationsRepo;
             _notificationsRepo = notificationsRepo;
             _userManager = userManager;
             _userManager = userManager;
+            _fileSystem = fileSystem;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -56,7 +60,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints.Notifications
         {
         {
             var dataPath = Path.Combine(_appPaths.DataPath, "remotenotifications.json");
             var dataPath = Path.Combine(_appPaths.DataPath, "remotenotifications.json");
 
 
-            var lastRunTime = File.Exists(dataPath) ? File.GetLastWriteTimeUtc(dataPath) : DateTime.MinValue;
+            var lastRunTime = File.Exists(dataPath) ? _fileSystem.GetLastWriteTimeUtc(dataPath) : DateTime.MinValue;
 
 
             try
             try
             {
             {

+ 6 - 3
MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs

@@ -1,6 +1,7 @@
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.IO;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using ServiceStack.Common;
 using ServiceStack.Common;
 using ServiceStack.Common.Web;
 using ServiceStack.Common.Web;
@@ -25,13 +26,15 @@ namespace MediaBrowser.Server.Implementations.HttpServer
         /// The _logger
         /// The _logger
         /// </summary>
         /// </summary>
         private readonly ILogger _logger;
         private readonly ILogger _logger;
+        private readonly IFileSystem _fileSystem;
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="HttpResultFactory"/> class.
         /// Initializes a new instance of the <see cref="HttpResultFactory"/> class.
         /// </summary>
         /// </summary>
         /// <param name="logManager">The log manager.</param>
         /// <param name="logManager">The log manager.</param>
-        public HttpResultFactory(ILogManager logManager)
+        public HttpResultFactory(ILogManager logManager, IFileSystem fileSystem)
         {
         {
+            _fileSystem = fileSystem;
             _logger = logManager.GetLogger("HttpResultFactory");
             _logger = logManager.GetLogger("HttpResultFactory");
         }
         }
 
 
@@ -288,7 +291,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
                 throw new ArgumentException("FileShare must be either Read or ReadWrite");
                 throw new ArgumentException("FileShare must be either Read or ReadWrite");
             }
             }
 
 
-            var dateModified = File.GetLastWriteTimeUtc(path);
+            var dateModified = _fileSystem.GetLastWriteTimeUtc(path);
 
 
             var cacheKey = path + dateModified.Ticks;
             var cacheKey = path + dateModified.Ticks;
 
 
@@ -303,7 +306,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
         /// <returns>Stream.</returns>
         /// <returns>Stream.</returns>
         private Stream GetFileStream(string path, FileShare fileShare)
         private Stream GetFileStream(string path, FileShare fileShare)
         {
         {
-            return new FileStream(path, FileMode.Open, FileAccess.Read, fileShare, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous);
+            return _fileSystem.GetFileStream(path, FileMode.Open, FileAccess.Read, fileShare, true);
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 20 - 4
MediaBrowser.Server.Implementations/IO/DirectoryWatchers.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Common.ScheduledTasks;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Common.ScheduledTasks;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.IO;
@@ -87,10 +88,12 @@ namespace MediaBrowser.Server.Implementations.IO
         private ILibraryManager LibraryManager { get; set; }
         private ILibraryManager LibraryManager { get; set; }
         private IServerConfigurationManager ConfigurationManager { get; set; }
         private IServerConfigurationManager ConfigurationManager { get; set; }
 
 
+        private readonly IFileSystem _fileSystem;
+        
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="DirectoryWatchers" /> class.
         /// Initializes a new instance of the <see cref="DirectoryWatchers" /> class.
         /// </summary>
         /// </summary>
-        public DirectoryWatchers(ILogManager logManager, ITaskManager taskManager, ILibraryManager libraryManager, IServerConfigurationManager configurationManager)
+        public DirectoryWatchers(ILogManager logManager, ITaskManager taskManager, ILibraryManager libraryManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem)
         {
         {
             if (taskManager == null)
             if (taskManager == null)
             {
             {
@@ -101,6 +104,7 @@ namespace MediaBrowser.Server.Implementations.IO
             TaskManager = taskManager;
             TaskManager = taskManager;
             Logger = logManager.GetLogger("DirectoryWatchers");
             Logger = logManager.GetLogger("DirectoryWatchers");
             ConfigurationManager = configurationManager;
             ConfigurationManager = configurationManager;
+            _fileSystem = fileSystem;
 
 
             SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
             SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
         }
         }
@@ -318,6 +322,18 @@ namespace MediaBrowser.Server.Implementations.IO
         /// <param name="sender">The source of the event.</param>
         /// <param name="sender">The source of the event.</param>
         /// <param name="e">The <see cref="FileSystemEventArgs" /> instance containing the event data.</param>
         /// <param name="e">The <see cref="FileSystemEventArgs" /> instance containing the event data.</param>
         void watcher_Changed(object sender, FileSystemEventArgs e)
         void watcher_Changed(object sender, FileSystemEventArgs e)
+        {
+            try
+            {
+                OnWatcherChanged(e);
+            }
+            catch (IOException ex)
+            {
+                Logger.ErrorException("IOException in watcher changed", ex);
+            }
+        }
+
+        private void OnWatcherChanged(FileSystemEventArgs e)
         {
         {
             var name = e.Name;
             var name = e.Name;
 
 
@@ -418,7 +434,7 @@ namespace MediaBrowser.Server.Implementations.IO
         {
         {
             try
             try
             {
             {
-                var data = FileSystem.GetFileSystemInfo(path);
+                var data = _fileSystem.GetFileSystemInfo(path);
 
 
                 if (!data.Exists
                 if (!data.Exists
                     || data.Attributes.HasFlag(FileAttributes.Directory)
                     || data.Attributes.HasFlag(FileAttributes.Directory)
@@ -434,7 +450,7 @@ namespace MediaBrowser.Server.Implementations.IO
 
 
             try
             try
             {
             {
-                using (new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
+                using (_fileSystem.GetFileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
                 {
                 {
                     //file is not locked
                     //file is not locked
                     return false;
                     return false;

+ 12 - 8
MediaBrowser.Server.Implementations/Library/LibraryManager.cs

@@ -1,4 +1,5 @@
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.IO;
 using MediaBrowser.Common.Progress;
 using MediaBrowser.Common.Progress;
 using MediaBrowser.Common.ScheduledTasks;
 using MediaBrowser.Common.ScheduledTasks;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Configuration;
@@ -169,6 +170,8 @@ namespace MediaBrowser.Server.Implementations.Library
         private readonly ConcurrentDictionary<string, UserRootFolder> _userRootFolders =
         private readonly ConcurrentDictionary<string, UserRootFolder> _userRootFolders =
             new ConcurrentDictionary<string, UserRootFolder>();
             new ConcurrentDictionary<string, UserRootFolder>();
 
 
+        private readonly IFileSystem _fileSystem;
+
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="LibraryManager" /> class.
         /// Initializes a new instance of the <see cref="LibraryManager" /> class.
         /// </summary>
         /// </summary>
@@ -177,7 +180,7 @@ namespace MediaBrowser.Server.Implementations.Library
         /// <param name="userManager">The user manager.</param>
         /// <param name="userManager">The user manager.</param>
         /// <param name="configurationManager">The configuration manager.</param>
         /// <param name="configurationManager">The configuration manager.</param>
         /// <param name="userDataRepository">The user data repository.</param>
         /// <param name="userDataRepository">The user data repository.</param>
-        public LibraryManager(ILogger logger, ITaskManager taskManager, IUserManager userManager, IServerConfigurationManager configurationManager, IUserDataManager userDataRepository, Func<IDirectoryWatchers> directoryWatchersFactory)
+        public LibraryManager(ILogger logger, ITaskManager taskManager, IUserManager userManager, IServerConfigurationManager configurationManager, IUserDataManager userDataRepository, Func<IDirectoryWatchers> directoryWatchersFactory, IFileSystem fileSystem)
         {
         {
             _logger = logger;
             _logger = logger;
             _taskManager = taskManager;
             _taskManager = taskManager;
@@ -185,6 +188,7 @@ namespace MediaBrowser.Server.Implementations.Library
             ConfigurationManager = configurationManager;
             ConfigurationManager = configurationManager;
             _userDataRepository = userDataRepository;
             _userDataRepository = userDataRepository;
             _directoryWatchersFactory = directoryWatchersFactory;
             _directoryWatchersFactory = directoryWatchersFactory;
+            _fileSystem = fileSystem;
             ByReferenceItems = new ConcurrentDictionary<Guid, BaseItem>();
             ByReferenceItems = new ConcurrentDictionary<Guid, BaseItem>();
 
 
             ConfigurationManager.ConfigurationUpdated += ConfigurationUpdated;
             ConfigurationManager.ConfigurationUpdated += ConfigurationUpdated;
@@ -417,7 +421,7 @@ namespace MediaBrowser.Server.Implementations.Library
 
 
             if (item != null)
             if (item != null)
             {
             {
-                ResolverHelper.SetInitialItemValues(item, args);
+                ResolverHelper.SetInitialItemValues(item, args, _fileSystem);
 
 
                 // Now handle the issue with posibly having the same item referenced from multiple physical
                 // Now handle the issue with posibly having the same item referenced from multiple physical
                 // places within the library.  Be sure we always end up with just one instance.
                 // places within the library.  Be sure we always end up with just one instance.
@@ -482,7 +486,7 @@ namespace MediaBrowser.Server.Implementations.Library
                 // When resolving the root, we need it's grandchildren (children of user views)
                 // When resolving the root, we need it's grandchildren (children of user views)
                 var flattenFolderDepth = isPhysicalRoot ? 2 : 0;
                 var flattenFolderDepth = isPhysicalRoot ? 2 : 0;
 
 
-                args.FileSystemDictionary = FileData.GetFilteredFileSystemEntries(args.Path, _logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf);
+                args.FileSystemDictionary = FileData.GetFilteredFileSystemEntries(args.Path, _fileSystem, _logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf);
 
 
                 // Need to remove subpaths that may have been resolved from shortcuts
                 // Need to remove subpaths that may have been resolved from shortcuts
                 // Example: if \\server\movies exists, then strip out \\server\movies\action
                 // Example: if \\server\movies exists, then strip out \\server\movies\action
@@ -701,7 +705,7 @@ namespace MediaBrowser.Server.Implementations.Library
                 throw new ArgumentNullException();
                 throw new ArgumentNullException();
             }
             }
 
 
-            var validFilename = FileSystem.GetValidFilename(name).Trim();
+            var validFilename = _fileSystem.GetValidFilename(name).Trim();
 
 
             string subFolderPrefix = null;
             string subFolderPrefix = null;
 
 
@@ -768,8 +772,8 @@ namespace MediaBrowser.Server.Implementations.Library
                 {
                 {
                     Name = name,
                     Name = name,
                     Id = id,
                     Id = id,
-                    DateCreated = fileInfo.CreationTimeUtc,
-                    DateModified = fileInfo.LastWriteTimeUtc,
+                    DateCreated = _fileSystem.GetCreationTimeUtc(fileInfo),
+                    DateModified = _fileSystem.GetLastWriteTimeUtc(fileInfo),
                     Path = path
                     Path = path
                 };
                 };
                 isNew = true;
                 isNew = true;
@@ -1066,7 +1070,7 @@ namespace MediaBrowser.Server.Implementations.Library
                     Name = Path.GetFileName(dir),
                     Name = Path.GetFileName(dir),
 
 
                     Locations = Directory.EnumerateFiles(dir, "*.mblink", SearchOption.TopDirectoryOnly)
                     Locations = Directory.EnumerateFiles(dir, "*.mblink", SearchOption.TopDirectoryOnly)
-                                .Select(FileSystem.ResolveShortcut)
+                                .Select(_fileSystem.ResolveShortcut)
                                 .OrderBy(i => i)
                                 .OrderBy(i => i)
                                 .ToList(),
                                 .ToList(),
 
 
@@ -1150,7 +1154,7 @@ namespace MediaBrowser.Server.Implementations.Library
                 try
                 try
                 {
                 {
                     // Try to resolve the path into a video 
                     // Try to resolve the path into a video 
-                    video = ResolvePath(FileSystem.GetFileSystemInfo(info.Path)) as Video;
+                    video = ResolvePath(_fileSystem.GetFileSystemInfo(info.Path)) as Video;
 
 
                     if (video == null)
                     if (video == null)
                     {
                     {

+ 5 - 2
MediaBrowser.Server.Implementations/Library/ResolverHelper.cs

@@ -1,5 +1,7 @@
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.IO;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Resolvers;
 using MediaBrowser.Controller.Resolvers;
 using System;
 using System;
@@ -18,7 +20,8 @@ namespace MediaBrowser.Server.Implementations.Library
         /// </summary>
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <param name="args">The args.</param>
         /// <param name="args">The args.</param>
-        public static void SetInitialItemValues(BaseItem item, ItemResolveArgs args)
+        /// <param name="fileSystem">The file system.</param>
+        public static void SetInitialItemValues(BaseItem item, ItemResolveArgs args, IFileSystem fileSystem)
         {
         {
             item.ResetResolveArgs(args);
             item.ResetResolveArgs(args);
 
 
@@ -48,7 +51,7 @@ namespace MediaBrowser.Server.Implementations.Library
             item.DontFetchMeta = item.Path.IndexOf("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) != -1;
             item.DontFetchMeta = item.Path.IndexOf("[dontfetchmeta]", StringComparison.OrdinalIgnoreCase) != -1;
 
 
             // Make sure DateCreated and DateModified have values
             // Make sure DateCreated and DateModified have values
-            EntityResolutionHelper.EnsureDates(item, args, true);
+            EntityResolutionHelper.EnsureDates(fileSystem, item, args, true);
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 2 - 1
MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs

@@ -52,7 +52,8 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
 
 
                 // If there's a collection type and it's not tv, it can't be a series
                 // If there's a collection type and it's not tv, it can't be a series
                 if (!string.IsNullOrEmpty(collectionType) &&
                 if (!string.IsNullOrEmpty(collectionType) &&
-                    !string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase))
+                    !string.Equals(collectionType, CollectionType.TvShows, StringComparison.OrdinalIgnoreCase) &&
+                    !string.Equals(collectionType, CollectionType.BoxSets, StringComparison.OrdinalIgnoreCase))
                 {
                 {
                     return null;
                     return null;
                 }
                 }

+ 8 - 3
MediaBrowser.Server.Implementations/Localization/LocalizationManager.cs

@@ -1,4 +1,6 @@
-using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.Localization;
 using MediaBrowser.Controller.Localization;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Globalization;
 using MediaBrowser.Model.Globalization;
@@ -30,13 +32,16 @@ namespace MediaBrowser.Server.Implementations.Localization
         private readonly ConcurrentDictionary<string, Dictionary<string, ParentalRating>> _allParentalRatings =
         private readonly ConcurrentDictionary<string, Dictionary<string, ParentalRating>> _allParentalRatings =
             new ConcurrentDictionary<string, Dictionary<string, ParentalRating>>(StringComparer.OrdinalIgnoreCase);
             new ConcurrentDictionary<string, Dictionary<string, ParentalRating>>(StringComparer.OrdinalIgnoreCase);
 
 
+        private readonly IFileSystem _fileSystem;
+        
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="LocalizationManager"/> class.
         /// Initializes a new instance of the <see cref="LocalizationManager"/> class.
         /// </summary>
         /// </summary>
         /// <param name="configurationManager">The configuration manager.</param>
         /// <param name="configurationManager">The configuration manager.</param>
-        public LocalizationManager(IServerConfigurationManager configurationManager)
+        public LocalizationManager(IServerConfigurationManager configurationManager, IFileSystem fileSystem)
         {
         {
             _configurationManager = configurationManager;
             _configurationManager = configurationManager;
+            _fileSystem = fileSystem;
 
 
             ExtractAll();
             ExtractAll();
         }
         }
@@ -65,7 +70,7 @@ namespace MediaBrowser.Server.Implementations.Localization
                 {
                 {
                     using (var stream = type.Assembly.GetManifestResourceStream(resource))
                     using (var stream = type.Assembly.GetManifestResourceStream(resource))
                     {
                     {
-                        using (var fs = new FileStream(Path.Combine(localizationPath, filename), FileMode.Create, FileAccess.Write, FileShare.Read))
+                        using (var fs = _fileSystem.GetFileStream(Path.Combine(localizationPath, filename), FileMode.Create, FileAccess.Write, FileShare.Read))
                         {
                         {
                             stream.CopyTo(fs);
                             stream.CopyTo(fs);
                         }
                         }

+ 8 - 6
MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj

@@ -46,6 +46,14 @@
     </Reference>
     </Reference>
     <Reference Include="System" />
     <Reference Include="System" />
     <Reference Include="System.Core" />
     <Reference Include="System.Core" />
+    <Reference Include="System.Data.SQLite, Version=1.0.89.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=x86">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\packages\System.Data.SQLite.x86.1.0.89.0\lib\net45\System.Data.SQLite.dll</HintPath>
+    </Reference>
+    <Reference Include="System.Data.SQLite.Linq, Version=1.0.89.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>..\packages\System.Data.SQLite.x86.1.0.89.0\lib\net45\System.Data.SQLite.Linq.dll</HintPath>
+    </Reference>
     <Reference Include="System.Drawing" />
     <Reference Include="System.Drawing" />
     <Reference Include="System.Reactive.Core">
     <Reference Include="System.Reactive.Core">
       <HintPath>..\packages\Rx-Core.2.1.30214.0\lib\Net45\System.Reactive.Core.dll</HintPath>
       <HintPath>..\packages\Rx-Core.2.1.30214.0\lib\Net45\System.Reactive.Core.dll</HintPath>
@@ -88,12 +96,6 @@
     <Reference Include="ServiceStack.Text">
     <Reference Include="ServiceStack.Text">
       <HintPath>..\packages\ServiceStack.Text.3.9.62\lib\net35\ServiceStack.Text.dll</HintPath>
       <HintPath>..\packages\ServiceStack.Text.3.9.62\lib\net35\ServiceStack.Text.dll</HintPath>
     </Reference>
     </Reference>
-    <Reference Include="System.Data.SQLite">
-      <HintPath>..\packages\System.Data.SQLite.x86.1.0.88.0\lib\net45\System.Data.SQLite.dll</HintPath>
-    </Reference>
-    <Reference Include="System.Data.SQLite.Linq">
-      <HintPath>..\packages\System.Data.SQLite.x86.1.0.88.0\lib\net45\System.Data.SQLite.Linq.dll</HintPath>
-    </Reference>
     <Reference Include="Mono.Data.Sqlite">
     <Reference Include="Mono.Data.Sqlite">
       <HintPath>..\packages\ServiceStack.OrmLite.Sqlite.Mono.3.9.64\lib\net35\Mono.Data.Sqlite.dll</HintPath>
       <HintPath>..\packages\ServiceStack.OrmLite.Sqlite.Mono.3.9.64\lib\net35\Mono.Data.Sqlite.dll</HintPath>
     </Reference>
     </Reference>

+ 6 - 4
MediaBrowser.Server.Implementations/MediaEncoder/MediaEncoder.cs

@@ -1,6 +1,7 @@
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Common.MediaInfo;
 using MediaBrowser.Common.MediaInfo;
+using MediaBrowser.Controller.IO;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Serialization;
 using MediaBrowser.Model.Serialization;
@@ -53,6 +54,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
         /// The FF probe resource pool
         /// The FF probe resource pool
         /// </summary>
         /// </summary>
         private readonly SemaphoreSlim _ffProbeResourcePool = new SemaphoreSlim(1, 1);
         private readonly SemaphoreSlim _ffProbeResourcePool = new SemaphoreSlim(1, 1);
+        private readonly IFileSystem _fileSystem;
 
 
         public string FFMpegPath { get; private set; }
         public string FFMpegPath { get; private set; }
 
 
@@ -61,12 +63,13 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
         public string Version { get; private set; }
         public string Version { get; private set; }
 
 
         public MediaEncoder(ILogger logger, IApplicationPaths appPaths,
         public MediaEncoder(ILogger logger, IApplicationPaths appPaths,
-                            IJsonSerializer jsonSerializer, string ffMpegPath, string ffProbePath, string version)
+                            IJsonSerializer jsonSerializer, string ffMpegPath, string ffProbePath, string version, IFileSystem fileSystem)
         {
         {
             _logger = logger;
             _logger = logger;
             _appPaths = appPaths;
             _appPaths = appPaths;
             _jsonSerializer = jsonSerializer;
             _jsonSerializer = jsonSerializer;
             Version = version;
             Version = version;
+            _fileSystem = fileSystem;
             FFProbePath = ffProbePath;
             FFProbePath = ffProbePath;
             FFMpegPath = ffMpegPath;
             FFMpegPath = ffMpegPath;
         }
         }
@@ -458,8 +461,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
 
 
             var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "ffmpeg-sub-convert-" + Guid.NewGuid() + ".txt");
             var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "ffmpeg-sub-convert-" + Guid.NewGuid() + ".txt");
 
 
-            var logFileStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read,
-                                               StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous);
+            var logFileStream = _fileSystem.GetFileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, true);
 
 
             try
             try
             {
             {
@@ -685,7 +687,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder
 
 
             var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "ffmpeg-sub-extract-" + Guid.NewGuid() + ".txt");
             var logFilePath = Path.Combine(_appPaths.LogDirectoryPath, "ffmpeg-sub-extract-" + Guid.NewGuid() + ".txt");
 
 
-            var logFileStream = new FileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous);
+            var logFileStream = _fileSystem.GetFileStream(logFilePath, FileMode.Create, FileAccess.Write, FileShare.Read, true);
 
 
             try
             try
             {
             {

+ 11 - 19
MediaBrowser.Server.Implementations/Providers/ImageSaver.cs

@@ -35,16 +35,18 @@ namespace MediaBrowser.Server.Implementations.Providers
         /// The _directory watchers
         /// The _directory watchers
         /// </summary>
         /// </summary>
         private readonly IDirectoryWatchers _directoryWatchers;
         private readonly IDirectoryWatchers _directoryWatchers;
+        private readonly IFileSystem _fileSystem;
 
 
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="ImageSaver"/> class.
         /// Initializes a new instance of the <see cref="ImageSaver"/> class.
         /// </summary>
         /// </summary>
         /// <param name="config">The config.</param>
         /// <param name="config">The config.</param>
         /// <param name="directoryWatchers">The directory watchers.</param>
         /// <param name="directoryWatchers">The directory watchers.</param>
-        public ImageSaver(IServerConfigurationManager config, IDirectoryWatchers directoryWatchers)
+        public ImageSaver(IServerConfigurationManager config, IDirectoryWatchers directoryWatchers, IFileSystem fileSystem)
         {
         {
             _config = config;
             _config = config;
             _directoryWatchers = directoryWatchers;
             _directoryWatchers = directoryWatchers;
+            _fileSystem = fileSystem;
             _remoteImageCache = new FileSystemRepository(config.ApplicationPaths.DownloadedImagesDataPath);
             _remoteImageCache = new FileSystemRepository(config.ApplicationPaths.DownloadedImagesDataPath);
         }
         }
 
 
@@ -67,30 +69,20 @@ namespace MediaBrowser.Server.Implementations.Providers
                 throw new ArgumentNullException("mimeType");
                 throw new ArgumentNullException("mimeType");
             }
             }
 
 
-            var saveLocally = _config.Configuration.SaveLocalMeta;
+            var saveLocally = _config.Configuration.SaveLocalMeta || item is IItemByName || item is User;
 
 
-            if (item is IItemByName)
-            {
-                saveLocally = true;
-            }
-            else if (item is User)
-            {
-                saveLocally = true;
-            }
-            else if (item is Audio || item.Parent == null || string.IsNullOrEmpty(item.MetaLocation))
+            if (item is Audio || item.Parent == null)
             {
             {
                 saveLocally = false;
                 saveLocally = false;
             }
             }
 
 
-            if (type != ImageType.Primary)
+            if (type != ImageType.Primary && item is Episode)
             {
             {
-                if (item is Episode)
-                {
-                    saveLocally = false;
-                }
+                saveLocally = false;
             }
             }
 
 
-            if (item.LocationType == LocationType.Remote || item.LocationType == LocationType.Virtual)
+            var locationType = item.LocationType;
+            if (locationType == LocationType.Remote || locationType == LocationType.Virtual)
             {
             {
                 saveLocally = false;
                 saveLocally = false;
             }
             }
@@ -186,7 +178,7 @@ namespace MediaBrowser.Server.Implementations.Providers
                     }
                     }
                 }
                 }
 
 
-                using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
+                using (var fs = _fileSystem.GetFileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, true))
                 {
                 {
                     await source.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false);
                     await source.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false);
                 }
                 }
@@ -373,7 +365,7 @@ namespace MediaBrowser.Server.Implementations.Providers
                     path = GetSavePathForItemInMixedFolder(item, type, filename, extension);
                     path = GetSavePathForItemInMixedFolder(item, type, filename, extension);
                 }
                 }
 
 
-                if (string.IsNullOrEmpty(path) && !string.IsNullOrEmpty(item.MetaLocation))
+                if (string.IsNullOrEmpty(path))
                 {
                 {
                     path = Path.Combine(item.MetaLocation, filename + extension);
                     path = Path.Combine(item.MetaLocation, filename + extension);
                 }
                 }

+ 65 - 5
MediaBrowser.Server.Implementations/Providers/ProviderManager.cs

@@ -13,6 +13,7 @@ using System.IO;
 using System.Linq;
 using System.Linq;
 using System.Threading;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
+using MediaBrowser.Model.Providers;
 
 
 namespace MediaBrowser.Server.Implementations.Providers
 namespace MediaBrowser.Server.Implementations.Providers
 {
 {
@@ -48,6 +49,9 @@ namespace MediaBrowser.Server.Implementations.Providers
         /// <value>The metadata providers enumerable.</value>
         /// <value>The metadata providers enumerable.</value>
         private BaseMetadataProvider[] MetadataProviders { get; set; }
         private BaseMetadataProvider[] MetadataProviders { get; set; }
 
 
+        private IImageProvider[] ImageProviders { get; set; }
+        private readonly IFileSystem _fileSystem;
+
         /// <summary>
         /// <summary>
         /// Initializes a new instance of the <see cref="ProviderManager" /> class.
         /// Initializes a new instance of the <see cref="ProviderManager" /> class.
         /// </summary>
         /// </summary>
@@ -55,22 +59,25 @@ namespace MediaBrowser.Server.Implementations.Providers
         /// <param name="configurationManager">The configuration manager.</param>
         /// <param name="configurationManager">The configuration manager.</param>
         /// <param name="directoryWatchers">The directory watchers.</param>
         /// <param name="directoryWatchers">The directory watchers.</param>
         /// <param name="logManager">The log manager.</param>
         /// <param name="logManager">The log manager.</param>
-        /// <param name="libraryManager">The library manager.</param>
-        public ProviderManager(IHttpClient httpClient, IServerConfigurationManager configurationManager, IDirectoryWatchers directoryWatchers, ILogManager logManager, ILibraryManager libraryManager)
+        public ProviderManager(IHttpClient httpClient, IServerConfigurationManager configurationManager, IDirectoryWatchers directoryWatchers, ILogManager logManager, IFileSystem fileSystem)
         {
         {
             _logger = logManager.GetLogger("ProviderManager");
             _logger = logManager.GetLogger("ProviderManager");
             _httpClient = httpClient;
             _httpClient = httpClient;
             ConfigurationManager = configurationManager;
             ConfigurationManager = configurationManager;
             _directoryWatchers = directoryWatchers;
             _directoryWatchers = directoryWatchers;
+            _fileSystem = fileSystem;
         }
         }
 
 
         /// <summary>
         /// <summary>
         /// Adds the metadata providers.
         /// Adds the metadata providers.
         /// </summary>
         /// </summary>
         /// <param name="providers">The providers.</param>
         /// <param name="providers">The providers.</param>
-        public void AddParts(IEnumerable<BaseMetadataProvider> providers)
+        /// <param name="imageProviders">The image providers.</param>
+        public void AddParts(IEnumerable<BaseMetadataProvider> providers, IEnumerable<IImageProvider> imageProviders)
         {
         {
             MetadataProviders = providers.OrderBy(e => e.Priority).ToArray();
             MetadataProviders = providers.OrderBy(e => e.Priority).ToArray();
+
+            ImageProviders = imageProviders.ToArray();
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -288,7 +295,7 @@ namespace MediaBrowser.Server.Implementations.Providers
             {
             {
                 using (dataToSave)
                 using (dataToSave)
                 {
                 {
-                    using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
+                    using (var fs = _fileSystem.GetFileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, true))
                     {
                     {
                         await dataToSave.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false);
                         await dataToSave.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false);
                     }
                     }
@@ -342,7 +349,60 @@ namespace MediaBrowser.Server.Implementations.Providers
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
         public Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, string sourceUrl, CancellationToken cancellationToken)
         public Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, string sourceUrl, CancellationToken cancellationToken)
         {
         {
-            return new ImageSaver(ConfigurationManager, _directoryWatchers).SaveImage(item, source, mimeType, type, imageIndex, sourceUrl, cancellationToken);
+            return new ImageSaver(ConfigurationManager, _directoryWatchers, _fileSystem).SaveImage(item, source, mimeType, type, imageIndex, sourceUrl, cancellationToken);
+        }
+
+        /// <summary>
+        /// Gets the available remote images.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <param name="type">The type.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task{IEnumerable{RemoteImageInfo}}.</returns>
+        public async Task<IEnumerable<RemoteImageInfo>> GetAvailableRemoteImages(BaseItem item, ImageType type, CancellationToken cancellationToken)
+        {
+            var providers = GetSupportedImageProviders(item, type);
+
+            var tasks = providers.Select(i => Task.Run(async () =>
+            {
+                try
+                {
+                    var result = await i.GetAvailableImages(item, type, cancellationToken).ConfigureAwait(false);
+                    return result.ToList();
+                }
+                catch (Exception ex)
+                {
+                    _logger.ErrorException("{0} failed in GetAvailableImages for type {1}", ex, i.GetType().Name, item.GetType().Name);
+                    return new List<RemoteImageInfo>();
+                }
+            }));
+
+            var results = await Task.WhenAll(tasks).ConfigureAwait(false);
+
+            return results.SelectMany(i => i);
+        }
+
+        /// <summary>
+        /// Gets the supported image providers.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <param name="type">The type.</param>
+        /// <returns>IEnumerable{IImageProvider}.</returns>
+        private IEnumerable<IImageProvider> GetSupportedImageProviders(BaseItem item, ImageType type)
+        {
+            return ImageProviders.Where(i =>
+            {
+                try
+                {
+                    return i.Supports(item, type);
+                }
+                catch (Exception ex)
+                {
+                    _logger.ErrorException("{0} failed in Supports for type {1}", ex, i.GetType().Name, item.GetType().Name);
+                    return false;
+                }
+
+            });
         }
         }
     }
     }
 }
 }

+ 1 - 1
MediaBrowser.Server.Implementations/packages.config

@@ -14,5 +14,5 @@
   <package id="ServiceStack.OrmLite.SqlServer" version="3.9.43" targetFramework="net45" />
   <package id="ServiceStack.OrmLite.SqlServer" version="3.9.43" targetFramework="net45" />
   <package id="ServiceStack.Redis" version="3.9.43" targetFramework="net45" />
   <package id="ServiceStack.Redis" version="3.9.43" targetFramework="net45" />
   <package id="ServiceStack.Text" version="3.9.62" targetFramework="net45" />
   <package id="ServiceStack.Text" version="3.9.62" targetFramework="net45" />
-  <package id="System.Data.SQLite.x86" version="1.0.88.0" targetFramework="net45" />
+  <package id="System.Data.SQLite.x86" version="1.0.89.0" targetFramework="net45" />
 </packages>
 </packages>

+ 21 - 0
MediaBrowser.Server.Mono/IO/FileSystemFactory.cs

@@ -0,0 +1,21 @@
+using MediaBrowser.Common.IO;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Common.Implementations.IO;
+
+namespace MediaBrowser.ServerApplication.IO
+{
+	/// <summary>
+	/// Class FileSystemFactory
+	/// </summary>
+	public static class FileSystemFactory
+	{
+		/// <summary>
+		/// Creates the file system manager.
+		/// </summary>
+		/// <returns>IFileSystem.</returns>
+		public static IFileSystem CreateFileSystemManager(ILogManager logManager)
+		{
+			return new CommonFileSystem(logManager.GetLogger("FileSystem"), false);
+		}
+	}
+}

+ 2 - 0
MediaBrowser.Server.Mono/MediaBrowser.Server.Mono.csproj

@@ -88,6 +88,7 @@
       <Link>FFMpeg\FFMpegDownloader.cs</Link>
       <Link>FFMpeg\FFMpegDownloader.cs</Link>
     </Compile>
     </Compile>
     <Compile Include="FFMpeg\FFMpegDownloadInfo.cs" />
     <Compile Include="FFMpeg\FFMpegDownloadInfo.cs" />
+    <Compile Include="IO\FileSystemFactory.cs" />
   </ItemGroup>
   </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   <ItemGroup>
   <ItemGroup>
@@ -129,6 +130,7 @@
     <Folder Include="Native\" />
     <Folder Include="Native\" />
     <Folder Include="FFMpeg\" />
     <Folder Include="FFMpeg\" />
     <Folder Include="Networking\" />
     <Folder Include="Networking\" />
+    <Folder Include="IO\" />
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <None Include="app.config" />
     <None Include="app.config" />

+ 33 - 0
MediaBrowser.Server.Mono/Program.cs

@@ -17,6 +17,7 @@ using System.Security.Cryptography.X509Certificates;
 using Gtk;
 using Gtk;
 using Gdk;
 using Gdk;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
+using System.Reflection;
 
 
 namespace MediaBrowser.Server.Mono
 namespace MediaBrowser.Server.Mono
 {
 {
@@ -203,6 +204,8 @@ namespace MediaBrowser.Server.Mono
 
 
 			logger.Info("Server: {0}", Environment.MachineName);
 			logger.Info("Server: {0}", Environment.MachineName);
 			logger.Info("Operating system: {0}", Environment.OSVersion.ToString());
 			logger.Info("Operating system: {0}", Environment.OSVersion.ToString());
+
+			MonoBug11817WorkAround.Apply ();
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -280,4 +283,34 @@ namespace MediaBrowser.Server.Mono
 			return true;
 			return true;
 		}
 		}
 	}
 	}
+
+	public class MonoBug11817WorkAround
+	{
+		public static void Apply()
+		{
+			var property = typeof(TimeZoneInfo).GetProperty("TimeZoneDirectory", BindingFlags.Static | BindingFlags.NonPublic);
+
+			if (property == null) return;
+
+			var zoneInfo = FindZoneInfoFolder();
+			property.SetValue(null, zoneInfo, new object[0]);
+		}
+
+		public static string FindZoneInfoFolder()
+		{
+			var current = new DirectoryInfo(Directory.GetCurrentDirectory());
+
+			while(current != null)
+			{
+				var zoneinfoTestPath = Path.Combine(current.FullName, "zoneinfo");
+
+				if (Directory.Exists(zoneinfoTestPath))
+					return zoneinfoTestPath;
+
+				current = current.Parent;
+			}
+
+			return null;
+		}
+	}
 }
 }

部分文件因文件數量過多而無法顯示