Jelajahi Sumber

fixed remote control flyout

Luke Pulverenti 11 tahun lalu
induk
melakukan
4e38c35373
32 mengubah file dengan 594 tambahan dan 509 penghapusan
  1. 7 11
      MediaBrowser.Api/ItemUpdateService.cs
  2. 9 3
      MediaBrowser.Api/Playback/Progressive/BaseProgressiveStreamingService.cs
  3. 1 1
      MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
  4. 10 0
      MediaBrowser.Controller/Entities/BaseItem.cs
  5. 1 1
      MediaBrowser.Controller/Entities/Folder.cs
  6. 6 0
      MediaBrowser.Controller/Entities/IHasImages.cs
  7. 1 1
      MediaBrowser.Controller/Entities/Movies/BoxSet.cs
  8. 3 2
      MediaBrowser.Controller/MediaBrowser.Controller.csproj
  9. 1 1
      MediaBrowser.Controller/Providers/BaseItemXmlParser.cs
  10. 0 268
      MediaBrowser.Controller/Providers/BaseMetadataProvider.cs
  11. 0 51
      MediaBrowser.Controller/Providers/BaseProviderInfo.cs
  12. 10 0
      MediaBrowser.Controller/Providers/IForcedProvider.cs
  13. 29 0
      MediaBrowser.Controller/Providers/IImageSaver.cs
  14. 1 4
      MediaBrowser.Controller/Providers/IMetadataProvider.cs
  15. 3 1
      MediaBrowser.Controller/Providers/IProviderManager.cs
  16. 18 3
      MediaBrowser.Controller/Providers/IRemoteMetadataProvider.cs
  17. 22 0
      MediaBrowser.Controller/Providers/ProviderRefreshStatus.cs
  18. 1 1
      MediaBrowser.Model/Dto/BaseItemDto.cs
  19. 4 4
      MediaBrowser.Model/Entities/LocationType.cs
  20. 3 3
      MediaBrowser.Model/Notifications/NotificationLevel.cs
  21. 1 7
      MediaBrowser.Providers/Genres/GenreMetadataService.cs
  22. 74 111
      MediaBrowser.Providers/Manager/ProviderManager.cs
  23. 1 0
      MediaBrowser.Providers/MediaBrowser.Providers.csproj
  24. 2 1
      MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs
  25. 75 19
      MediaBrowser.Providers/People/MovieDbPersonProvider.cs
  26. 4 1
      MediaBrowser.Providers/Savers/XmlSaverHelpers.cs
  27. 272 0
      MediaBrowser.Providers/Xbmc/XbmcImageSaver.cs
  28. 1 1
      MediaBrowser.Server.Implementations/Dto/DtoService.cs
  29. 29 9
      MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs
  30. 1 1
      MediaBrowser.Server.Implementations/HttpServer/ResponseFilter.cs
  31. 1 2
      MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs
  32. 3 2
      MediaBrowser.ServerApplication/ApplicationHost.cs

+ 7 - 11
MediaBrowser.Api/ItemUpdateService.cs

@@ -5,7 +5,6 @@ using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.LiveTv;
 using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
 using ServiceStack;
 using System;
 using System.Linq;
@@ -94,8 +93,8 @@ namespace MediaBrowser.Api
         {
             var item = _dtoService.GetItemByDtoId(request.ItemId);
 
-            var newEnableInternetProviders = request.EnableInternetProviders ?? true;
-            var dontFetchMetaChanged = item.DontFetchMeta != !newEnableInternetProviders;
+            var newLockData = request.LockData ?? false;
+            var dontFetchMetaChanged = item.DontFetchMeta != newLockData;
 
             UpdateItem(request, item);
 
@@ -107,7 +106,7 @@ namespace MediaBrowser.Api
 
                 foreach (var child in folder.RecursiveChildren.ToList())
                 {
-                    child.DontFetchMeta = !newEnableInternetProviders;
+                    child.DontFetchMeta = newLockData;
                     await _libraryManager.UpdateItem(child, ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
                 }
             }
@@ -307,16 +306,13 @@ namespace MediaBrowser.Api
             {
                 hasAspectRatio.AspectRatio = request.AspectRatio;
             }
-            
-            item.DontFetchMeta = !(request.EnableInternetProviders ?? true);
-            if (request.EnableInternetProviders ?? true)
+
+            item.DontFetchMeta = (request.LockData ?? false);
+
+            if (request.LockedFields != null)
             {
                 item.LockedFields = request.LockedFields;
             }
-            else
-            {
-                item.LockedFields.Clear();
-            }
 
             // Only allow this for series. Runtimes for media comes from ffprobe.
             if (item is Series)

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

@@ -240,6 +240,13 @@ namespace MediaBrowser.Api.Playback.Progressive
 
             responseHeaders["Accept-Ranges"] = "none";
 
+            var length = response.Headers["Content-Length"];
+
+            if (!string.IsNullOrEmpty(length))
+            {
+                responseHeaders["Content-Length"] = length;
+            }
+
             if (isHeadRequest)
             {
                 using (response.Content)
@@ -273,13 +280,13 @@ namespace MediaBrowser.Api.Playback.Progressive
             // Use the command line args with a dummy playlist path
             var outputPath = GetOutputFilePath(state);
 
+            responseHeaders["Accept-Ranges"] = "none";
+
             var contentType = MimeTypes.GetMimeType(outputPath);
 
             // Headers only
             if (isHeadRequest)
             {
-                responseHeaders["Accept-Ranges"] = "none";
-
                 return ResultFactory.GetResult(new byte[] { }, contentType, responseHeaders);
             }
 
@@ -294,7 +301,6 @@ namespace MediaBrowser.Api.Playback.Progressive
 
             var result = new ProgressiveStreamWriter(outputPath, Logger, FileSystem);
 
-            result.Options["Accept-Ranges"] = "none";
             result.Options["Content-Type"] = contentType;
 
             // Add the response headers to the result object

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

@@ -179,7 +179,7 @@ namespace MediaBrowser.Controller.Entities.Audio
             // Refresh all non-songs
             foreach (var item in others)
             {
-                if (tasks.Count > 3)
+                if (tasks.Count >= 3)
                 {
                     await Task.WhenAll(tasks).ConfigureAwait(false);
                     tasks.Clear();

+ 10 - 0
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -216,8 +216,18 @@ namespace MediaBrowser.Controller.Entities
         /// Returns true if this item should not attempt to fetch metadata
         /// </summary>
         /// <value><c>true</c> if [dont fetch meta]; otherwise, <c>false</c>.</value>
+        [Obsolete("Please use IsLocked instead of DontFetchMeta")]
         public bool DontFetchMeta { get; set; }
 
+        [IgnoreDataMember]
+        public bool IsLocked
+        {
+            get
+            {
+                return DontFetchMeta;
+            }
+        }
+
         /// <summary>
         /// Gets or sets the locked fields.
         /// </summary>

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

@@ -521,7 +521,7 @@ namespace MediaBrowser.Controller.Entities
 
             foreach (var child in children)
             {
-                if (tasks.Count >= 4)
+                if (tasks.Count >= 3)
                 {
                     await Task.WhenAll(tasks).ConfigureAwait(false);
                     tasks.Clear();

+ 6 - 0
MediaBrowser.Controller/Entities/IHasImages.cs

@@ -148,6 +148,12 @@ namespace MediaBrowser.Controller.Entities
         /// </summary>
         /// <value><c>true</c> if this instance is in mixed folder; otherwise, <c>false</c>.</value>
         bool IsInMixedFolder { get; }
+
+        /// <summary>
+        /// Gets a value indicating whether this instance is locked.
+        /// </summary>
+        /// <value><c>true</c> if this instance is locked; otherwise, <c>false</c>.</value>
+        bool IsLocked { get; }
     }
 
     public static class HasImagesExtensions

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

@@ -99,7 +99,7 @@ namespace MediaBrowser.Controller.Entities.Movies
             // Refresh songs
             foreach (var item in items)
             {
-                if (tasks.Count >= 4)
+                if (tasks.Count >= 3)
                 {
                     await Task.WhenAll(tasks).ConfigureAwait(false);
                     tasks.Clear();

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

@@ -147,9 +147,11 @@
     <Compile Include="Persistence\MediaStreamQuery.cs" />
     <Compile Include="Providers\DirectoryService.cs" />
     <Compile Include="Providers\ICustomMetadataProvider.cs" />
+    <Compile Include="Providers\IForcedProvider.cs" />
     <Compile Include="Providers\IHasChangeMonitor.cs" />
     <Compile Include="Entities\IHasMetadata.cs" />
     <Compile Include="Providers\IImageProvider.cs" />
+    <Compile Include="Providers\IImageSaver.cs" />
     <Compile Include="Providers\ILocalMetadataProvider.cs" />
     <Compile Include="Providers\IProviderRepository.cs" />
     <Compile Include="Providers\IRemoteImageProvider.cs" />
@@ -216,7 +218,7 @@
     <Compile Include="Plugins\IPluginConfigurationPage.cs" />
     <Compile Include="Plugins\IServerEntryPoint.cs" />
     <Compile Include="Providers\IImageEnhancer.cs" />
-    <Compile Include="Providers\BaseProviderInfo.cs" />
+    <Compile Include="Providers\ProviderRefreshStatus.cs" />
     <Compile Include="Resolvers\IResolverIgnoreRule.cs" />
     <Compile Include="Resolvers\EntityResolutionHelper.cs" />
     <Compile Include="Resolvers\ResolverPriority.cs" />
@@ -224,7 +226,6 @@
     <Compile Include="Library\ItemResolveArgs.cs" />
     <Compile Include="IO\FileData.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
-    <Compile Include="Providers\BaseMetadataProvider.cs" />
     <Compile Include="Session\ISessionController.cs" />
     <Compile Include="Session\ISessionControllerFactory.cs" />
     <Compile Include="Session\PlaybackInfo.cs" />

+ 1 - 1
MediaBrowser.Controller/Providers/BaseItemXmlParser.cs

@@ -719,7 +719,7 @@ namespace MediaBrowser.Controller.Providers
                         }
                         break;
                     }
-                case "TvRageId":
+                case "TVRageId":
                     {
                         var id = reader.ReadElementContentAsString();
                         if (!string.IsNullOrWhiteSpace(id))

+ 0 - 268
MediaBrowser.Controller/Providers/BaseMetadataProvider.cs

@@ -1,268 +0,0 @@
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Model.Logging;
-using System;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Controller.Providers
-{
-    /// <summary>
-    /// Class BaseMetadataProvider
-    /// </summary>
-    public abstract class BaseMetadataProvider
-    {
-        /// <summary>
-        /// Gets the logger.
-        /// </summary>
-        /// <value>The logger.</value>
-        protected ILogger Logger { get; set; }
-
-        protected ILogManager LogManager { get; set; }
-
-        /// <summary>
-        /// Gets the configuration manager.
-        /// </summary>
-        /// <value>The configuration manager.</value>
-        protected IServerConfigurationManager ConfigurationManager { get; private set; }
-
-        /// <summary>
-        /// The _id
-        /// </summary>
-        public readonly Guid Id;
-
-        /// <summary>
-        /// The true task result
-        /// </summary>
-        protected static readonly Task<bool> TrueTaskResult = Task.FromResult(true);
-
-        protected static readonly Task<bool> FalseTaskResult = Task.FromResult(false);
-
-        /// <summary>
-        /// Supportses the specified item.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
-        public abstract bool Supports(BaseItem item);
-
-        /// <summary>
-        /// Gets a value indicating whether [requires internet].
-        /// </summary>
-        /// <value><c>true</c> if [requires internet]; otherwise, <c>false</c>.</value>
-        public virtual bool RequiresInternet
-        {
-            get
-            {
-                return false;
-            }
-        }
-
-        /// <summary>
-        /// Gets the provider version.
-        /// </summary>
-        /// <value>The provider version.</value>
-        protected virtual string ProviderVersion
-        {
-            get
-            {
-                return null;
-            }
-        }
-
-        public virtual ItemUpdateType ItemUpdateType
-        {
-            get { return RequiresInternet ? ItemUpdateType.MetadataDownload : ItemUpdateType.MetadataImport; }
-        }
-
-        /// <summary>
-        /// Gets a value indicating whether [refresh on version change].
-        /// </summary>
-        /// <value><c>true</c> if [refresh on version change]; otherwise, <c>false</c>.</value>
-        protected virtual bool RefreshOnVersionChange
-        {
-            get
-            {
-                return false;
-            }
-        }
-
-        /// <summary>
-        /// Determines if this provider is relatively slow and, therefore, should be skipped
-        /// in certain instances.  Default is whether or not it requires internet.  Can be overridden
-        /// for explicit designation.
-        /// </summary>
-        /// <value><c>true</c> if this instance is slow; otherwise, <c>false</c>.</value>
-        public virtual bool IsSlow
-        {
-            get { return RequiresInternet; }
-        }
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="BaseMetadataProvider" /> class.
-        /// </summary>
-        protected BaseMetadataProvider(ILogManager logManager, IServerConfigurationManager configurationManager)
-        {
-            Logger = logManager.GetLogger(GetType().Name);
-            LogManager = logManager;
-            ConfigurationManager = configurationManager;
-            Id = GetType().FullName.GetMD5();
-
-            Initialize();
-        }
-
-        /// <summary>
-        /// Initializes this instance.
-        /// </summary>
-        protected virtual void Initialize()
-        {
-        }
-
-        /// <summary>
-        /// Sets the persisted last refresh date on the item for this provider.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="value">The value.</param>
-        /// <param name="providerVersion">The provider version.</param>
-        /// <param name="providerInfo">The provider information.</param>
-        /// <param name="status">The status.</param>
-        /// <exception cref="System.ArgumentNullException">item</exception>
-        public virtual void SetLastRefreshed(BaseItem item, DateTime value, string providerVersion,
-            BaseProviderInfo providerInfo, ProviderRefreshStatus status = ProviderRefreshStatus.Success)
-        {
-            if (item == null)
-            {
-                throw new ArgumentNullException("item");
-            }
-
-            providerInfo.LastRefreshed = value;
-            providerInfo.LastRefreshStatus = status;
-            providerInfo.ProviderVersion = providerVersion;
-        }
-
-        /// <summary>
-        /// Sets the last refreshed.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="value">The value.</param>
-        /// <param name="providerInfo">The provider information.</param>
-        /// <param name="status">The status.</param>
-        public void SetLastRefreshed(BaseItem item, DateTime value,
-            BaseProviderInfo providerInfo, ProviderRefreshStatus status = ProviderRefreshStatus.Success)
-        {
-            SetLastRefreshed(item, value, ProviderVersion, providerInfo, status);
-        }
-
-        /// <summary>
-        /// Returns whether or not this provider should be re-fetched.  Default functionality can
-        /// compare a provided date with a last refresh time.  This can be overridden for more complex
-        /// determinations.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
-        /// <exception cref="System.ArgumentNullException"></exception>
-        public bool NeedsRefresh(BaseItem item, BaseProviderInfo data)
-        {
-            if (item == null)
-            {
-                throw new ArgumentNullException();
-            }
-
-            return NeedsRefreshInternal(item, data);
-        }
-
-        /// <summary>
-        /// Gets a value indicating whether [enforce dont fetch metadata].
-        /// </summary>
-        /// <value><c>true</c> if [enforce dont fetch metadata]; otherwise, <c>false</c>.</value>
-        public virtual bool EnforceDontFetchMetadata
-        {
-            get
-            {
-                return true;
-            }
-        }
-
-        /// <summary>
-        /// Needses the refresh internal.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="providerInfo">The provider info.</param>
-        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
-        /// <exception cref="System.ArgumentNullException"></exception>
-        protected virtual bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
-        {
-            if (item == null)
-            {
-                throw new ArgumentNullException("item");
-            }
-
-            if (providerInfo == null)
-            {
-                throw new ArgumentNullException("providerInfo");
-            }
-
-            if (providerInfo.LastRefreshed == default(DateTime))
-            {
-                return true;
-            }
-            
-            if (NeedsRefreshBasedOnCompareDate(item, providerInfo))
-            {
-                return true;
-            }
-
-            if (RefreshOnVersionChange && !String.Equals(ProviderVersion, providerInfo.ProviderVersion))
-            {
-                return true;
-            }
-
-            if (providerInfo.LastRefreshStatus != ProviderRefreshStatus.Success)
-            {
-                return true;
-            }
-
-            return false;
-        }
-
-        /// <summary>
-        /// Needses the refresh based on compare date.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="providerInfo">The provider info.</param>
-        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
-        protected virtual bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo)
-        {
-            return CompareDate(item) > providerInfo.LastRefreshed;
-        }
-
-        /// <summary>
-        /// Override this to return the date that should be compared to the last refresh date
-        /// to determine if this provider should be re-fetched.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns>DateTime.</returns>
-        protected virtual DateTime CompareDate(BaseItem item)
-        {
-            return DateTime.MinValue.AddMinutes(1); // want this to be greater than mindate so new items will refresh
-        }
-
-        /// <summary>
-        /// Fetches metadata and returns true or false indicating if any work that requires persistence was done
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="force">if set to <c>true</c> [force].</param>
-        /// <param name="providerInfo">The provider information.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task{System.Boolean}.</returns>
-        /// <exception cref="System.ArgumentNullException"></exception>
-        public abstract Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken);
-
-        /// <summary>
-        /// Gets the priority.
-        /// </summary>
-        /// <value>The priority.</value>
-        public abstract MetadataProviderPriority Priority { get; }
-    }
-}

+ 0 - 51
MediaBrowser.Controller/Providers/BaseProviderInfo.cs

@@ -1,51 +0,0 @@
-using System;
-
-namespace MediaBrowser.Controller.Providers
-{
-    /// <summary>
-    /// Class BaseProviderInfo
-    /// </summary>
-    public class BaseProviderInfo
-    {
-        public Guid ProviderId { get; set; }
-        /// <summary>
-        /// Gets or sets the last refreshed.
-        /// </summary>
-        /// <value>The last refreshed.</value>
-        public DateTime LastRefreshed { get; set; }
-        /// <summary>
-        /// Gets or sets the file system stamp.
-        /// </summary>
-        /// <value>The file system stamp.</value>
-        public Guid FileStamp { get; set; }
-        /// <summary>
-        /// Gets or sets the last refresh status.
-        /// </summary>
-        /// <value>The last refresh status.</value>
-        public ProviderRefreshStatus LastRefreshStatus { get; set; }
-        /// <summary>
-        /// Gets or sets the provider version.
-        /// </summary>
-        /// <value>The provider version.</value>
-        public string ProviderVersion { get; set; }
-    }
-
-    /// <summary>
-    /// Enum ProviderRefreshStatus
-    /// </summary>
-    public enum ProviderRefreshStatus
-    {
-        /// <summary>
-        /// The success
-        /// </summary>
-        Success = 0,
-        /// <summary>
-        /// The completed with errors
-        /// </summary>
-        CompletedWithErrors = 1,
-         /// <summary>
-        /// The failure
-        /// </summary>
-        Failure = 2
-   }
-}

+ 10 - 0
MediaBrowser.Controller/Providers/IForcedProvider.cs

@@ -0,0 +1,10 @@
+
+namespace MediaBrowser.Controller.Providers
+{
+    /// <summary>
+    /// This is a marker interface that will cause a provider to run even if IsLocked=true
+    /// </summary>
+    public interface IForcedProvider
+    {
+    }
+}

+ 29 - 0
MediaBrowser.Controller/Providers/IImageSaver.cs

@@ -0,0 +1,29 @@
+using MediaBrowser.Controller.Drawing;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Model.Entities;
+using System.Collections.Generic;
+
+namespace MediaBrowser.Controller.Providers
+{
+    public interface IImageSaver
+    {
+        /// <summary>
+        /// Gets the name.
+        /// </summary>
+        /// <value>The name.</value>
+        string Name { get; }
+    }
+
+    public interface IImageFileSaver : IImageSaver
+    {
+        /// <summary>
+        /// Gets the save paths.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <param name="type">The type.</param>
+        /// <param name="format">The format.</param>
+        /// <param name="index">The index.</param>
+        /// <returns>IEnumerable{System.String}.</returns>
+        IEnumerable<string> GetSavePaths(IHasImages item, ImageType type, ImageFormat format, int index);
+    }
+}

+ 1 - 4
MediaBrowser.Controller/Providers/IMetadataProvider.cs

@@ -1,5 +1,4 @@
-using System;
-using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities;
 
 namespace MediaBrowser.Controller.Providers
 {
@@ -26,10 +25,8 @@ namespace MediaBrowser.Controller.Providers
     }
 
     public class MetadataResult<T>
-        where T : IHasMetadata
     {
         public bool HasMetadata { get; set; }
         public T Item { get; set; }
     }
-
 }

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

@@ -55,8 +55,10 @@ namespace MediaBrowser.Controller.Providers
         /// <param name="metadataServices">The metadata services.</param>
         /// <param name="metadataProviders">The metadata providers.</param>
         /// <param name="savers">The savers.</param>
+        /// <param name="imageSavers">The image savers.</param>
         void AddParts(IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices, IEnumerable<IMetadataProvider> metadataProviders,
-            IEnumerable<IMetadataSaver> savers);
+            IEnumerable<IMetadataSaver> savers,
+            IEnumerable<IImageSaver> imageSavers);
 
         /// <summary>
         /// Gets the available remote images.

+ 18 - 3
MediaBrowser.Controller/Providers/IRemoteMetadataProvider.cs

@@ -1,6 +1,7 @@
-using System.Threading;
+using MediaBrowser.Controller.Entities;
+using System.Collections.Generic;
+using System.Threading;
 using System.Threading.Tasks;
-using MediaBrowser.Controller.Entities;
 
 namespace MediaBrowser.Controller.Providers
 {
@@ -8,10 +9,24 @@ namespace MediaBrowser.Controller.Providers
     {
     }
 
-    public interface IRemoteMetadataProvider<TItemType, in TLookupInfoType> : IMetadataProvider<TItemType>, IRemoteMetadataProvider
+    public interface IRemoteMetadataProvider<TItemType, TLookupInfoType> : IMetadataProvider<TItemType>, IRemoteMetadataProvider
         where TItemType : IHasMetadata, IHasLookupInfo<TLookupInfoType>
         where TLookupInfoType : ItemLookupInfo, new()
     {
         Task<MetadataResult<TItemType>> GetMetadata(TLookupInfoType info, CancellationToken cancellationToken);
     }
+
+    public interface IRemoteSearchProvider<TLookupInfoType>
+        where TLookupInfoType : ItemLookupInfo
+    {
+        Task<IEnumerable<SearchResult<TLookupInfoType>>> GetSearchResults(TLookupInfoType searchInfo, CancellationToken cancellationToken);
+    }
+    
+    public class SearchResult<T>
+        where T : ItemLookupInfo
+    {
+        public T Item { get; set; }
+
+        public string ImageUrl { get; set; }
+    }
 }

+ 22 - 0
MediaBrowser.Controller/Providers/ProviderRefreshStatus.cs

@@ -0,0 +1,22 @@
+
+namespace MediaBrowser.Controller.Providers
+{
+    /// <summary>
+    /// Enum ProviderRefreshStatus
+    /// </summary>
+    public enum ProviderRefreshStatus
+    {
+        /// <summary>
+        /// The success
+        /// </summary>
+        Success = 0,
+        /// <summary>
+        /// The completed with errors
+        /// </summary>
+        CompletedWithErrors = 1,
+         /// <summary>
+        /// The failure
+        /// </summary>
+        Failure = 2
+   }
+}

+ 1 - 1
MediaBrowser.Model/Dto/BaseItemDto.cs

@@ -657,7 +657,7 @@ namespace MediaBrowser.Model.Dto
         /// Gets or sets a value indicating whether [enable internet providers].
         /// </summary>
         /// <value><c>true</c> if [enable internet providers]; otherwise, <c>false</c>.</value>
-        public bool? EnableInternetProviders { get; set; }
+        public bool? LockData { get; set; }
 
         /// <summary>
         /// Gets a value indicating whether this instance can resume.

+ 4 - 4
MediaBrowser.Model/Entities/LocationType.cs

@@ -9,18 +9,18 @@ namespace MediaBrowser.Model.Entities
         /// <summary>
         /// The file system
         /// </summary>
-        FileSystem = 1,
+        FileSystem = 0,
         /// <summary>
         /// The remote
         /// </summary>
-        Remote = 2,
+        Remote = 1,
         /// <summary>
         /// The virtual
         /// </summary>
-        Virtual = 3,
+        Virtual = 2,
         /// <summary>
         /// The offline
         /// </summary>
-        Offline = 4
+        Offline = 3
     }
 }

+ 3 - 3
MediaBrowser.Model/Notifications/NotificationLevel.cs

@@ -3,8 +3,8 @@ namespace MediaBrowser.Model.Notifications
 {
     public enum NotificationLevel
     {
-        Normal = 1,
-        Warning = 2,
-        Error = 3
+        Normal = 0,
+        Warning = 1,
+        Error = 2
     }
 }

+ 1 - 7
MediaBrowser.Providers/Genres/GenreMetadataService.cs

@@ -1,25 +1,19 @@
 using MediaBrowser.Common.IO;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Providers.Manager;
 using System.Collections.Generic;
-using System.Threading;
-using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.Genres
 {
     public class GenreMetadataService : MetadataService<Genre, ItemLookupInfo>
     {
-        private readonly ILibraryManager _libraryManager;
-
-        public GenreMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager)
+        public GenreMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem)
             : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem)
         {
-            _libraryManager = libraryManager;
         }
 
         /// <summary>

+ 74 - 111
MediaBrowser.Providers/Manager/ProviderManager.cs

@@ -52,11 +52,10 @@ namespace MediaBrowser.Providers.Manager
 
         private readonly IFileSystem _fileSystem;
 
-        private readonly IProviderRepository _providerRepo;
-
         private IMetadataService[] _metadataServices = { };
         private IMetadataProvider[] _metadataProviders = { };
         private IEnumerable<IMetadataSaver> _savers;
+        private IImageSaver[] _imageSavers;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="ProviderManager" /> class.
@@ -66,15 +65,13 @@ namespace MediaBrowser.Providers.Manager
         /// <param name="libraryMonitor">The directory watchers.</param>
         /// <param name="logManager">The log manager.</param>
         /// <param name="fileSystem">The file system.</param>
-        /// <param name="providerRepo">The provider repo.</param>
-        public ProviderManager(IHttpClient httpClient, IServerConfigurationManager configurationManager, ILibraryMonitor libraryMonitor, ILogManager logManager, IFileSystem fileSystem, IProviderRepository providerRepo)
+        public ProviderManager(IHttpClient httpClient, IServerConfigurationManager configurationManager, ILibraryMonitor libraryMonitor, ILogManager logManager, IFileSystem fileSystem)
         {
             _logger = logManager.GetLogger("ProviderManager");
             _httpClient = httpClient;
             ConfigurationManager = configurationManager;
             _libraryMonitor = libraryMonitor;
             _fileSystem = fileSystem;
-            _providerRepo = providerRepo;
         }
 
         /// <summary>
@@ -84,13 +81,16 @@ namespace MediaBrowser.Providers.Manager
         /// <param name="metadataServices">The metadata services.</param>
         /// <param name="metadataProviders">The metadata providers.</param>
         /// <param name="metadataSavers">The metadata savers.</param>
-        public void AddParts(IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices, IEnumerable<IMetadataProvider> metadataProviders, IEnumerable<IMetadataSaver> metadataSavers)
+        /// <param name="imageSavers">The image savers.</param>
+        public void AddParts(IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices, IEnumerable<IMetadataProvider> metadataProviders, IEnumerable<IMetadataSaver> metadataSavers,
+            IEnumerable<IImageSaver> imageSavers)
         {
             ImageProviders = imageProviders.ToArray();
 
             _metadataServices = metadataServices.OrderBy(i => i.Order).ToArray();
             _metadataProviders = metadataProviders.ToArray();
             _savers = metadataSavers.ToArray();
+            _imageSavers = imageSavers.ToArray();
         }
 
         public Task RefreshMetadata(IHasMetadata item, MetadataRefreshOptions options, CancellationToken cancellationToken)
@@ -106,62 +106,6 @@ namespace MediaBrowser.Providers.Manager
             return Task.FromResult(true);
         }
 
-        /// <summary>
-        /// Saves to library filesystem.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="path">The path.</param>
-        /// <param name="dataToSave">The data to save.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        /// <exception cref="System.ArgumentNullException"></exception>
-        public async Task SaveToLibraryFilesystem(BaseItem item, string path, Stream dataToSave, CancellationToken cancellationToken)
-        {
-            if (item == null)
-            {
-                throw new ArgumentNullException();
-            }
-            if (string.IsNullOrEmpty(path))
-            {
-                throw new ArgumentNullException();
-            }
-            if (dataToSave == null)
-            {
-                throw new ArgumentNullException();
-            }
-
-            if (cancellationToken.IsCancellationRequested)
-            {
-                dataToSave.Dispose();
-                cancellationToken.ThrowIfCancellationRequested();
-            }
-
-            //Tell the watchers to ignore
-            _libraryMonitor.ReportFileSystemChangeBeginning(path);
-
-            if (dataToSave.CanSeek)
-            {
-                dataToSave.Position = 0;
-            }
-
-            try
-            {
-                using (dataToSave)
-                {
-                    using (var fs = _fileSystem.GetFileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, true))
-                    {
-                        await dataToSave.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false);
-                    }
-                }
-            }
-            finally
-            {
-                //Remove the ignore
-                _libraryMonitor.ReportFileSystemChangeComplete(path, false);
-            }
-        }
-
-
         /// <summary>
         /// Saves the image.
         /// </summary>
@@ -252,8 +196,13 @@ namespace MediaBrowser.Providers.Manager
                     result = result.Where(i => i.Type == type.Value);
                 }
 
-                return string.IsNullOrEmpty(preferredLanguage) ? result :
-                    FilterImages(result, preferredLanguage);
+                if (string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase))
+                {
+                    result = result.Where(i => string.IsNullOrEmpty(i.Language) ||
+                                               string.Equals(i.Language, "en", StringComparison.OrdinalIgnoreCase));
+                }
+
+                return result;
             }
             catch (Exception ex)
             {
@@ -262,17 +211,6 @@ namespace MediaBrowser.Providers.Manager
             }
         }
 
-        private IEnumerable<RemoteImageInfo> FilterImages(IEnumerable<RemoteImageInfo> images, string preferredLanguage)
-        {
-            if (string.Equals(preferredLanguage, "en", StringComparison.OrdinalIgnoreCase))
-            {
-                images = images.Where(i => string.IsNullOrEmpty(i.Language) ||
-                                           string.Equals(i.Language, "en", StringComparison.OrdinalIgnoreCase));
-            }
-
-            return images;
-        }
-
         /// <summary>
         /// Gets the supported image providers.
         /// </summary>
@@ -294,13 +232,16 @@ namespace MediaBrowser.Providers.Manager
 
         private IEnumerable<IImageProvider> GetImageProviders(IHasImages item, MetadataOptions options, bool includeDisabled)
         {
+            // Avoid implicitly captured closure
+            var currentOptions = options;
+
             return ImageProviders.Where(i => CanRefresh(i, item, options, includeDisabled))
             .OrderBy(i =>
             {
                 // See if there's a user-defined order
                 if (!(i is ILocalImageProvider))
                 {
-                    var index = Array.IndexOf(options.ImageFetcherOrder, i.Name);
+                    var index = Array.IndexOf(currentOptions.ImageFetcherOrder, i.Name);
 
                     if (index != -1)
                     {
@@ -325,36 +266,13 @@ namespace MediaBrowser.Providers.Manager
         private IEnumerable<IMetadataProvider<T>> GetMetadataProvidersInternal<T>(IHasMetadata item, MetadataOptions options, bool includeDisabled)
             where T : IHasMetadata
         {
-            return _metadataProviders.OfType<IMetadataProvider<T>>()
-                .Where(i => CanRefresh(i, item, options, includeDisabled))
-                .OrderBy(i =>
-                {
-                    // See if there's a user-defined order
-                    if (i is ILocalMetadataProvider)
-                    {
-                        var index = Array.IndexOf(options.LocalMetadataReaderOrder, i.Name);
-
-                        if (index != -1)
-                        {
-                            return index;
-                        }
-                    }
-
-                    // See if there's a user-defined order
-                    if (i is IRemoteMetadataProvider)
-                    {
-                        var index = Array.IndexOf(options.MetadataFetcherOrder, i.Name);
-
-                        if (index != -1)
-                        {
-                            return index;
-                        }
-                    }
+            // Avoid implicitly captured closure
+            var currentOptions = options;
 
-                    // Not configured. Just return some high number to put it at the end.
-                    return 100;
-                })
-                .ThenBy(GetOrder);
+            return _metadataProviders.OfType<IMetadataProvider<T>>()
+                .Where(i => CanRefresh(i, item, currentOptions, includeDisabled))
+                .OrderBy(i => GetConfiguredOrder(i, options))
+                .ThenBy(GetDefaultOrder);
         }
 
         private IEnumerable<IRemoteImageProvider> GetRemoteImageProviders(IHasImages item, bool includeDisabled)
@@ -368,6 +286,12 @@ namespace MediaBrowser.Providers.Manager
         {
             if (!includeDisabled)
             {
+                // If locked only allow local providers
+                if (item.IsLocked && !(provider is ILocalMetadataProvider) && !(provider is IForcedProvider))
+                {
+                    return false;
+                }
+
                 if (provider is IRemoteMetadataProvider)
                 {
                     if (Array.IndexOf(options.DisabledMetadataFetchers, provider.Name) != -1)
@@ -398,6 +322,12 @@ namespace MediaBrowser.Providers.Manager
         {
             if (!includeDisabled)
             {
+                // If locked only allow local providers
+                if (item.IsLocked && !(provider is ILocalImageProvider))
+                {
+                    return false;
+                }
+                
                 if (provider is IRemoteImageProvider)
                 {
                     if (Array.IndexOf(options.DisabledImageFetchers, provider.Name) != -1)
@@ -440,12 +370,35 @@ namespace MediaBrowser.Providers.Manager
             return hasOrder.Order;
         }
 
-        /// <summary>
-        /// Gets the order.
-        /// </summary>
-        /// <param name="provider">The provider.</param>
-        /// <returns>System.Int32.</returns>
-        private int GetOrder(IMetadataProvider provider)
+        private int GetConfiguredOrder(IMetadataProvider provider, MetadataOptions options)
+        {
+            // See if there's a user-defined order
+            if (provider is ILocalMetadataProvider)
+            {
+                var index = Array.IndexOf(options.LocalMetadataReaderOrder, provider.Name);
+
+                if (index != -1)
+                {
+                    return index;
+                }
+            }
+
+            // See if there's a user-defined order
+            if (provider is IRemoteMetadataProvider)
+            {
+                var index = Array.IndexOf(options.MetadataFetcherOrder, provider.Name);
+
+                if (index != -1)
+                {
+                    return index;
+                }
+            }
+
+            // Not configured. Just return some high number to put it at the end.
+            return 100;
+        }
+        
+        private int GetDefaultOrder(IMetadataProvider provider)
         {
             var hasOrder = provider as IHasOrder;
 
@@ -578,6 +531,7 @@ namespace MediaBrowser.Providers.Manager
         }
 
         private readonly ConcurrentDictionary<string, SemaphoreSlim> _fileLocks = new ConcurrentDictionary<string, SemaphoreSlim>();
+        
         /// <summary>
         /// Saves the metadata.
         /// </summary>
@@ -658,5 +612,14 @@ namespace MediaBrowser.Providers.Manager
                 return false;
             }
         }
+
+        //private IEnumerable<TLookupType> GetRemoteSearchResults<TLookupType>(TLookupType searchInfo,
+        //    CancellationToken cancellationToken)
+        //    where TLookupType : ItemLookupInfo
+        //{
+        //    var providers = _metadataProviders.OfType<IRemoteSearchProvider<TLookupType>>();
+
+
+        //}
     }
 }

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

@@ -198,6 +198,7 @@
     <Compile Include="TV\TvdbPrescanTask.cs" />
     <Compile Include="Users\UserMetadataService.cs" />
     <Compile Include="Videos\VideoMetadataService.cs" />
+    <Compile Include="Xbmc\XbmcImageSaver.cs" />
     <Compile Include="Years\YearMetadataService.cs" />
   </ItemGroup>
   <ItemGroup>

+ 2 - 1
MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs

@@ -31,7 +31,8 @@ namespace MediaBrowser.Providers.MediaInfo
         ICustomMetadataProvider<Video>,
         ICustomMetadataProvider<Audio>,
         IHasChangeMonitor,
-        IHasOrder
+        IHasOrder,
+        IForcedProvider
     {
         private readonly ILogger _logger;
         private readonly IIsoManager _isoManager;

+ 75 - 19
MediaBrowser.Providers/People/MovieDbPersonProvider.cs

@@ -12,6 +12,7 @@ using System;
 using System.Collections.Generic;
 using System.Globalization;
 using System.IO;
+using System.Linq;
 using System.Net;
 using System.Threading;
 using System.Threading.Tasks;
@@ -21,9 +22,9 @@ namespace MediaBrowser.Providers.People
     public class MovieDbPersonProvider : IRemoteMetadataProvider<Person, PersonLookupInfo>
     {
         const string DataFileName = "info.json";
-        
+
         internal static MovieDbPersonProvider Current { get; private set; }
-        
+
         private readonly IJsonSerializer _jsonSerializer;
         private readonly IFileSystem _fileSystem;
         private readonly IServerConfigurationManager _configurationManager;
@@ -41,6 +42,73 @@ namespace MediaBrowser.Providers.People
             get { return "TheMovieDb"; }
         }
 
+        public async Task<IEnumerable<SearchResult<PersonLookupInfo>>> GetSearchResults(PersonLookupInfo searchInfo, CancellationToken cancellationToken)
+        {
+            var tmdbId = searchInfo.GetProviderId(MetadataProviders.Tmdb);
+
+            var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
+
+            var tmdbImageUrl = tmdbSettings.images.base_url + "original";
+
+            if (!string.IsNullOrEmpty(tmdbId))
+            {
+                await EnsurePersonInfo(tmdbId, cancellationToken).ConfigureAwait(false);
+
+                var dataFilePath = GetPersonDataFilePath(_configurationManager.ApplicationPaths, tmdbId);
+                var info = _jsonSerializer.DeserializeFromFile<PersonResult>(dataFilePath);
+
+                var images = (info.images ?? new Images()).profiles ?? new List<Profile>();
+
+                var result = new SearchResult<PersonLookupInfo>
+                {
+                    Item = new PersonLookupInfo
+                    {
+                        Name = info.name
+                    },
+
+                    ImageUrl = images.Count == 0 ? null : (tmdbImageUrl + images[0].file_path)
+                };
+
+                result.Item.SetProviderId(MetadataProviders.Tmdb, info.id.ToString(_usCulture));
+                result.Item.SetProviderId(MetadataProviders.Imdb, info.imdb_id.ToString(_usCulture));
+
+                return new[] { result };
+            }
+
+            var url = string.Format(@"http://api.themoviedb.org/3/search/person?api_key={1}&query={0}", WebUtility.UrlEncode(searchInfo.Name), MovieDbProvider.ApiKey);
+
+            using (var json = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions
+            {
+                Url = url,
+                CancellationToken = cancellationToken,
+                AcceptHeader = MovieDbProvider.AcceptHeader
+
+            }).ConfigureAwait(false))
+            {
+                var result = _jsonSerializer.DeserializeFromStream<PersonSearchResults>(json) ??
+                             new PersonSearchResults();
+
+                return result.Results.Select(i => GetSearchResult(i, tmdbImageUrl));
+            }
+        }
+
+        private SearchResult<PersonLookupInfo> GetSearchResult(PersonSearchResult i, string baseImageUrl)
+        {
+            var result = new SearchResult<PersonLookupInfo>
+            {
+                Item = new PersonLookupInfo
+                {
+                    Name = i.Name
+                },
+
+                ImageUrl = string.IsNullOrEmpty(i.Profile_Path) ? null : (baseImageUrl + i.Profile_Path)
+            };
+
+            result.Item.SetProviderId(MetadataProviders.Tmdb, i.Id.ToString(_usCulture));
+
+            return result;
+        }
+
         public async Task<MetadataResult<Person>> GetMetadata(PersonLookupInfo id, CancellationToken cancellationToken)
         {
             var tmdbId = id.GetProviderId(MetadataProviders.Tmdb);
@@ -48,7 +116,7 @@ namespace MediaBrowser.Providers.People
             // We don't already have an Id, need to fetch it
             if (string.IsNullOrEmpty(tmdbId))
             {
-                tmdbId = await GetTmdbId(id.Name, cancellationToken).ConfigureAwait(false);
+                tmdbId = await GetTmdbId(id, cancellationToken).ConfigureAwait(false);
             }
 
             var result = new MetadataResult<Person>();
@@ -100,26 +168,14 @@ namespace MediaBrowser.Providers.People
         /// <summary>
         /// Gets the TMDB id.
         /// </summary>
-        /// <param name="name">The name.</param>
+        /// <param name="info">The information.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task{System.String}.</returns>
-        private async Task<string> GetTmdbId(string name, CancellationToken cancellationToken)
+        private async Task<string> GetTmdbId(PersonLookupInfo info, CancellationToken cancellationToken)
         {
-            string url = string.Format(@"http://api.themoviedb.org/3/search/person?api_key={1}&query={0}", WebUtility.UrlEncode(name), MovieDbProvider.ApiKey);
-            PersonSearchResults searchResult = null;
-
-            using (var json = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions
-            {
-                Url = url,
-                CancellationToken = cancellationToken,
-                AcceptHeader = MovieDbProvider.AcceptHeader
-
-            }).ConfigureAwait(false))
-            {
-                searchResult = _jsonSerializer.DeserializeFromStream<PersonSearchResults>(json);
-            }
+            var results = await GetSearchResults(info, cancellationToken).ConfigureAwait(false);
 
-            return searchResult != null && searchResult.Total_Results > 0 ? searchResult.Results[0].Id.ToString(_usCulture) : null;
+            return results.Select(i => i.Item.GetProviderId(MetadataProviders.Tmdb)).FirstOrDefault();
         }
 
         internal async Task EnsurePersonInfo(string id, CancellationToken cancellationToken)

+ 4 - 1
MediaBrowser.Providers/Savers/XmlSaverHelpers.cs

@@ -22,6 +22,8 @@ namespace MediaBrowser.Providers.Savers
                
                     "Added",
                     "AspectRatio",
+                    "AudioDbAlbumId",
+                    "AudioDbArtistId",
                     "AwardSummary",
                     "BirthDate",
                     "Budget",
@@ -73,6 +75,7 @@ namespace MediaBrowser.Providers.Savers
                     "TVcomId",
                     "TvDbId",
                     "Type",
+                    "TVRageId",
                     "VoteCount",
                     "Website",
                     "Zap2ItId"
@@ -477,7 +480,7 @@ namespace MediaBrowser.Providers.Savers
 
             if (!string.IsNullOrEmpty(externalId))
             {
-                builder.Append("<TvRageId>" + SecurityElement.Escape(externalId) + "</TvRageId>");
+                builder.Append("<TVRageId>" + SecurityElement.Escape(externalId) + "</TVRageId>");
             }
 
             var hasTagline = item as IHasTaglines;

+ 272 - 0
MediaBrowser.Providers/Xbmc/XbmcImageSaver.cs

@@ -0,0 +1,272 @@
+using MediaBrowser.Controller.Drawing;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+
+namespace MediaBrowser.Providers.Xbmc
+{
+    public class XbmcImageSaver : IImageFileSaver
+    {
+        private readonly CultureInfo _usCulture = new CultureInfo("en-US");
+
+        public IEnumerable<string> GetSavePaths(IHasImages item, ImageType type, ImageFormat format, int index)
+        {
+            var season = item as Season;
+
+            if (!SupportsItem(item, type, season))
+            {
+                return new string[] { };
+            }
+
+            var extension = "." + format.ToString().ToLower();
+
+            // Backdrop paths
+            if (type == ImageType.Backdrop)
+            {
+                if (index == 0)
+                {
+                    if (item.IsInMixedFolder)
+                    {
+                        return new[] { GetSavePathForItemInMixedFolder(item, type, "fanart", extension) };
+                    }
+
+                    if (season != null && season.IndexNumber.HasValue)
+                    {
+                        var seriesFolder = season.SeriesPath;
+
+                        var seasonMarker = season.IndexNumber.Value == 0
+                                               ? "-specials"
+                                               : season.IndexNumber.Value.ToString("00", _usCulture);
+
+                        var imageFilename = "season" + seasonMarker + "-fanart" + extension;
+
+                        return new[] { Path.Combine(seriesFolder, imageFilename) };
+                    }
+
+                    return new[]
+                        {
+                            Path.Combine(item.ContainingFolderPath, "fanart" + extension)
+                        };
+                }
+
+                if (item.IsInMixedFolder)
+                {
+                    return new[] { GetSavePathForItemInMixedFolder(item, type, "fanart" + index.ToString(_usCulture), extension) };
+                }
+
+                var extraFanartFilename = GetBackdropSaveFilename(item.GetImages(ImageType.Backdrop), "fanart", "fanart", index);
+
+                return new[]
+                    {
+                        Path.Combine(item.ContainingFolderPath, "extrafanart", extraFanartFilename + extension),
+                        Path.Combine(item.ContainingFolderPath, "extrathumbs", "thumb" + index.ToString(_usCulture) + extension)
+                    };
+            }
+
+            if (type == ImageType.Primary)
+            {
+                if (season != null && season.IndexNumber.HasValue)
+                {
+                    var seriesFolder = season.SeriesPath;
+
+                    var seasonMarker = season.IndexNumber.Value == 0
+                                           ? "-specials"
+                                           : season.IndexNumber.Value.ToString("00", _usCulture);
+
+                    var imageFilename = "season" + seasonMarker + "-poster" + extension;
+
+                    return new[] { Path.Combine(seriesFolder, imageFilename) };
+                }
+
+                if (item is Episode)
+                {
+                    var seasonFolder = Path.GetDirectoryName(item.Path);
+
+                    var imageFilename = Path.GetFileNameWithoutExtension(item.Path) + "-thumb" + extension;
+
+                    return new[] { Path.Combine(seasonFolder, imageFilename) };
+                }
+
+                if (item.IsInMixedFolder || item is MusicVideo)
+                {
+                    return new[] { GetSavePathForItemInMixedFolder(item, type, string.Empty, extension) };
+                }
+
+                if (item is MusicAlbum || item is MusicArtist)
+                {
+                    return new[] { Path.Combine(item.ContainingFolderPath, "folder" + extension) };
+                }
+
+                return new[] { Path.Combine(item.ContainingFolderPath, "poster" + extension) };
+            }
+
+            if (type == ImageType.Banner)
+            {
+                if (season != null && season.IndexNumber.HasValue)
+                {
+                    var seriesFolder = season.SeriesPath;
+
+                    var seasonMarker = season.IndexNumber.Value == 0
+                                           ? "-specials"
+                                           : season.IndexNumber.Value.ToString("00", _usCulture);
+
+                    var imageFilename = "season" + seasonMarker + "-banner" + extension;
+
+                    return new[] { Path.Combine(seriesFolder, imageFilename) };
+                }
+            }
+
+            if (type == ImageType.Thumb)
+            {
+                if (season != null && season.IndexNumber.HasValue)
+                {
+                    var seriesFolder = season.SeriesPath;
+
+                    var seasonMarker = season.IndexNumber.Value == 0
+                                           ? "-specials"
+                                           : season.IndexNumber.Value.ToString("00", _usCulture);
+
+                    var imageFilename = "season" + seasonMarker + "-landscape" + extension;
+
+                    return new[] { Path.Combine(seriesFolder, imageFilename) };
+                }
+
+                if (item.IsInMixedFolder)
+                {
+                    return new[] { GetSavePathForItemInMixedFolder(item, type, "landscape", extension) };
+                }
+
+                return new[] { Path.Combine(item.ContainingFolderPath, "landscape" + extension) };
+            }
+
+            return GetStandardSavePaths(item, type, index, extension);
+        }
+
+        private IEnumerable<string> GetStandardSavePaths(IHasImages item, ImageType type, int imageIndex, string extension)
+        {
+            string filename;
+
+            switch (type)
+            {
+                case ImageType.Art:
+                    filename = "clearart";
+                    break;
+                case ImageType.BoxRear:
+                    filename = "back";
+                    break;
+                case ImageType.Disc:
+                    filename = item is MusicAlbum ? "cdart" : "disc";
+                    break;
+                case ImageType.Screenshot:
+                    filename = GetBackdropSaveFilename(item.GetImages(type), "screenshot", "screenshot", imageIndex);
+                    break;
+                default:
+                    filename = type.ToString().ToLower();
+                    break;
+            }
+
+            string path = null;
+
+            if (item.IsInMixedFolder)
+            {
+                path = GetSavePathForItemInMixedFolder(item, type, filename, extension);
+            }
+
+            if (string.IsNullOrEmpty(path))
+            {
+                path = Path.Combine(item.ContainingFolderPath, filename + extension);
+            }
+
+            if (string.IsNullOrEmpty(path))
+            {
+                return new string[] { };
+            }
+
+            return new[] { path };
+        }
+
+
+        private string GetSavePathForItemInMixedFolder(IHasImages item, ImageType type, string imageFilename, string extension)
+        {
+            if (type == ImageType.Primary)
+            {
+                imageFilename = "poster";
+            }
+            var folder = Path.GetDirectoryName(item.Path);
+
+            return Path.Combine(folder, Path.GetFileNameWithoutExtension(item.Path) + "-" + imageFilename + extension);
+        }
+
+        private bool SupportsItem(IHasImages item, ImageType type, Season season)
+        {
+            if (item.IsOwnedItem || item is Audio || item is User)
+            {
+                return false;
+            }
+
+            if (type != ImageType.Primary && item is Episode)
+            {
+                return false;
+            }
+
+            if (!item.SupportsLocalMetadata)
+            {
+                return false;
+            }
+
+            var locationType = item.LocationType;
+            if (locationType == LocationType.Remote || locationType == LocationType.Virtual)
+            {
+                var allowSaving = false;
+
+                // If season is virtual under a physical series, save locally if using compatible convention
+                if (season != null)
+                {
+                    var series = season.Series;
+
+                    if (series != null && series.SupportsLocalMetadata)
+                    {
+                        allowSaving = true;
+                    }
+                }
+
+                if (!allowSaving)
+                {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        private string GetBackdropSaveFilename(IEnumerable<ItemImageInfo> images, string zeroIndexFilename, string numberedIndexPrefix, int? index)
+        {
+            if (index.HasValue && index.Value == 0)
+            {
+                return zeroIndexFilename;
+            }
+
+            var filenames = images.Select(i => Path.GetFileNameWithoutExtension(i.Path)).ToList();
+
+            var current = 1;
+            while (filenames.Contains(numberedIndexPrefix + current.ToString(_usCulture), StringComparer.OrdinalIgnoreCase))
+            {
+                current++;
+            }
+
+            return numberedIndexPrefix + current.ToString(_usCulture);
+        }
+
+        public string Name
+        {
+            get { return "MB3/Plex/Xbmc Images"; }
+        }
+    }
+}

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

@@ -664,7 +664,7 @@ namespace MediaBrowser.Server.Implementations.Dto
             if (fields.Contains(ItemFields.Settings))
             {
                 dto.LockedFields = item.LockedFields;
-                dto.EnableInternetProviders = !item.DontFetchMeta;
+                dto.LockData = item.DontFetchMeta;
             }
 
             var hasBudget = item as IHasBudget;

+ 29 - 9
MediaBrowser.Server.Implementations/HttpServer/HttpResultFactory.cs

@@ -435,10 +435,10 @@ namespace MediaBrowser.Server.Implementations.HttpServer
 
             if (!compress || string.IsNullOrEmpty(requestedCompressionType))
             {
-                var stream = await factoryFn().ConfigureAwait(false);
-
                 var rangeHeader = requestContext.GetHeader("Range");
 
+                var stream = await factoryFn().ConfigureAwait(false);
+
                 if (!string.IsNullOrEmpty(rangeHeader))
                 {
                     return new RangeRequestWriter(rangeHeader, stream, contentType, isHeadRequest);
@@ -448,34 +448,54 @@ namespace MediaBrowser.Server.Implementations.HttpServer
 
                 if (isHeadRequest)
                 {
+                    stream.Dispose();
+
                     return GetHttpResult(new byte[] { }, contentType);
                 }
 
                 return new StreamWriter(stream, contentType, _logger);
             }
 
-            if (isHeadRequest)
-            {
-                return GetHttpResult(new byte[] { }, contentType);
-            }
-
             string content;
+            long originalContentLength = 0;
 
             using (var stream = await factoryFn().ConfigureAwait(false))
             {
-                using (var reader = new StreamReader(stream))
+                using (var memoryStream = new MemoryStream())
                 {
-                    content = await reader.ReadToEndAsync().ConfigureAwait(false);
+                    await stream.CopyToAsync(memoryStream).ConfigureAwait(false);
+                    memoryStream.Position = 0;
+
+                    originalContentLength = memoryStream.Length;
+
+                    using (var reader = new StreamReader(memoryStream))
+                    {
+                        content = await reader.ReadToEndAsync().ConfigureAwait(false);
+                    }
                 }
             }
 
             if (!SupportsCompression)
             {
+                responseHeaders["Content-Length"] = originalContentLength.ToString(UsCulture);
+                
+                if (isHeadRequest)
+                {
+                    return GetHttpResult(new byte[] { }, contentType);
+                }
+
                 return new HttpResult(content, contentType);
             }
 
             var contents = content.Compress(requestedCompressionType);
 
+            responseHeaders["Content-Length"] = contents.Length.ToString(UsCulture);
+
+            if (isHeadRequest)
+            {
+                return GetHttpResult(new byte[] { }, contentType);
+            }
+            
             return new CompressedResult(contents, requestedCompressionType, contentType);
         }
 

+ 1 - 1
MediaBrowser.Server.Implementations/HttpServer/ResponseFilter.cs

@@ -69,7 +69,7 @@ namespace MediaBrowser.Server.Implementations.HttpServer
                         var response = (HttpListenerResponse)res.OriginalResponse;
 
                         response.ContentLength64 = length;
-
+                       
                         // Disable chunked encoding. Technically this is only needed when using Content-Range, but
                         // anytime we know the content length there's no need for it
                         response.SendChunked = false;

+ 1 - 2
MediaBrowser.Server.Implementations/ServerManager/ServerManager.cs

@@ -8,7 +8,6 @@ using MediaBrowser.Model.Serialization;
 using System;
 using System.Collections.Generic;
 using System.Linq;
-using System.Net;
 using System.Net.Sockets;
 using System.Threading;
 using System.Threading.Tasks;
@@ -171,7 +170,7 @@ namespace MediaBrowser.Server.Implementations.ServerManager
 
                 throw;
             }
-            catch (HttpListenerException ex)
+            catch (Exception ex)
             {
                 _logger.ErrorException("Error starting Http Server", ex);
 

+ 3 - 2
MediaBrowser.ServerApplication/ApplicationHost.cs

@@ -336,7 +336,7 @@ namespace MediaBrowser.ServerApplication
             LibraryMonitor = new LibraryMonitor(LogManager, TaskManager, LibraryManager, ServerConfigurationManager, FileSystemManager);
             RegisterSingleInstance(LibraryMonitor);
 
-            ProviderManager = new ProviderManager(HttpClient, ServerConfigurationManager, LibraryMonitor, LogManager, FileSystemManager, ProviderRepository);
+            ProviderManager = new ProviderManager(HttpClient, ServerConfigurationManager, LibraryMonitor, LogManager, FileSystemManager);
             RegisterSingleInstance(ProviderManager);
 
             RegisterSingleInstance<ISearchEngine>(() => new SearchEngine(LogManager, LibraryManager, UserManager));
@@ -548,7 +548,8 @@ namespace MediaBrowser.ServerApplication
                                     GetExports<ILibraryPostScanTask>());
 
             ProviderManager.AddParts(GetExports<IImageProvider>(), GetExports<IMetadataService>(), GetExports<IMetadataProvider>(),
-                                    GetExports<IMetadataSaver>());
+                                    GetExports<IMetadataSaver>(),
+                                    GetExports<IImageSaver>());
 
             ImageProcessor.AddParts(GetExports<IImageEnhancer>());