2
0
Эх сурвалжийг харах

tightened up image saving to reduce knowledge of file names

Luke Pulverenti 12 жил өмнө
parent
commit
8a1b12b7d8
25 өөрчлөгдсөн 576 нэмэгдсэн , 402 устгасан
  1. 16 116
      MediaBrowser.Api/Images/ImageService.cs
  2. 86 0
      MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs
  3. 0 46
      MediaBrowser.Common.Implementations/HttpClientManager/HttpResponseInfo.cs
  4. 0 1
      MediaBrowser.Common.Implementations/MediaBrowser.Common.Implementations.csproj
  5. 1 0
      MediaBrowser.Common/MediaBrowser.Common.csproj
  6. 29 0
      MediaBrowser.Common/Net/HttpResponseInfo.cs
  7. 7 0
      MediaBrowser.Common/Net/IHttpClient.cs
  8. 3 0
      MediaBrowser.Controller/Entities/Folder.cs
  9. 21 37
      MediaBrowser.Controller/Providers/IProviderManager.cs
  10. 11 9
      MediaBrowser.Providers/Movies/FanArtMovieProvider.cs
  11. 8 5
      MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs
  12. 20 38
      MediaBrowser.Providers/Movies/MovieDbProvider.cs
  13. 7 16
      MediaBrowser.Providers/Movies/TmdbPersonProvider.cs
  14. 4 2
      MediaBrowser.Providers/Music/FanArtAlbumProvider.cs
  15. 10 5
      MediaBrowser.Providers/Music/FanArtArtistProvider.cs
  16. 9 6
      MediaBrowser.Providers/Savers/SeriesXmlSaver.cs
  17. 2 1
      MediaBrowser.Providers/TV/FanArtSeasonProvider.cs
  18. 10 5
      MediaBrowser.Providers/TV/FanArtTVProvider.cs
  19. 4 1
      MediaBrowser.Providers/TV/RemoteEpisodeProvider.cs
  20. 15 11
      MediaBrowser.Providers/TV/RemoteSeasonProvider.cs
  21. 0 7
      MediaBrowser.Providers/TV/RemoteSeriesProvider.cs
  22. 11 6
      MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs
  23. 1 0
      MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj
  24. 255 0
      MediaBrowser.Server.Implementations/Providers/ImageSaver.cs
  25. 46 90
      MediaBrowser.Server.Implementations/Providers/ProviderManager.cs

+ 16 - 116
MediaBrowser.Api/Images/ImageService.cs

@@ -760,145 +760,45 @@ namespace MediaBrowser.Api.Images
                 var bytes = Convert.FromBase64String(text);
                 var bytes = Convert.FromBase64String(text);
 
 
                 // Validate first
                 // Validate first
-                using (var memoryStream = new MemoryStream(bytes))
+                using (var validationStream = new MemoryStream(bytes))
                 {
                 {
-                    using (var image = Image.FromStream(memoryStream))
+                    using (var image = Image.FromStream(validationStream))
                     {
                     {
                         Logger.Info("New image is {0}x{1}", image.Width, image.Height);
                         Logger.Info("New image is {0}x{1}", image.Width, image.Height);
                     }
                     }
                 }
                 }
 
 
-                string filename;
+                var memoryStream = new MemoryStream(bytes);
 
 
-                switch (imageType)
-                {
-                    case ImageType.Art:
-                        filename = "clearart";
-                        break;
-                    case ImageType.Primary:
-                        filename = entity is Episode ? Path.GetFileNameWithoutExtension(entity.Path) : "folder";
-                        break;
-                    case ImageType.Backdrop:
-                        filename = GetBackdropFilenameToSave(entity);
-                        break;
-                    case ImageType.Screenshot:
-                        filename = GetScreenshotFilenameToSave(entity);
-                        break;
-                    default:
-                        filename = imageType.ToString().ToLower();
-                        break;
-                }
+                memoryStream.Position = 0;
 
 
+                var imageIndex = 0;
 
 
-                var extension = mimeType.Split(';').First().Split('/').Last();
-
-                string oldImagePath;
-                switch (imageType)
+                if (imageType == ImageType.Screenshot)
                 {
                 {
-                    case ImageType.Backdrop:
-                    case ImageType.Screenshot:
-                        oldImagePath = null;
-                        break;
-                    default:
-                        oldImagePath = entity.GetImage(imageType);
-                        break;
+                    imageIndex = entity.ScreenshotImagePaths.Count;
                 }
                 }
-
-                // Don't save locally if there's no parent (special feature, trailer, etc)
-                var saveLocally = !(entity is Audio) && entity.Parent != null && !string.IsNullOrEmpty(entity.MetaLocation) || entity is User;
-
-                if (imageType != ImageType.Primary)
+                else if (imageType == ImageType.Backdrop)
                 {
                 {
-                    if (entity is Episode)
-                    {
-                        saveLocally = false;
-                    }
+                    imageIndex = entity.BackdropImagePaths.Count;
                 }
                 }
 
 
-                if (entity.LocationType != LocationType.FileSystem)
-                {
-                    saveLocally = false;
-                }
+                await _providerManager.SaveImage(entity, memoryStream, mimeType, imageType, imageIndex, CancellationToken.None).ConfigureAwait(false);
 
 
-                var imagePath = _providerManager.GetSavePath(entity, filename + "." + extension, saveLocally);
+                var user = entity as User;
 
 
-                // Save to file system
-                using (var fs = new FileStream(imagePath, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, true))
+                if (user != null)
                 {
                 {
-                    await fs.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
-                }
-
-                if (imageType == ImageType.Screenshot)
-                {
-                    entity.ScreenshotImagePaths.Add(imagePath);
-                }
-                else if (imageType == ImageType.Backdrop)
-                {
-                    entity.BackdropImagePaths.Add(imagePath);
+                    await _userManager.UpdateUser(user).ConfigureAwait(false);
                 }
                 }
                 else
                 else
                 {
                 {
-                    // Set the image
-                    entity.SetImage(imageType, imagePath);
-                }
-
-                // If the new and old paths are different, delete the old one
-                if (!string.IsNullOrEmpty(oldImagePath) && !oldImagePath.Equals(imagePath, StringComparison.OrdinalIgnoreCase))
-                {
-                    File.Delete(oldImagePath);
+                    await _libraryManager.UpdateItem(entity, ItemUpdateType.ImageUpdate, CancellationToken.None)
+                                       .ConfigureAwait(false);
                 }
                 }
 
 
-                // Directory watchers should repeat this, but do a quick refresh first
-                await entity.RefreshMetadata(CancellationToken.None, forceSave: true, allowSlowProviders: false).ConfigureAwait(false);
+                await entity.RefreshMetadata(CancellationToken.None, allowSlowProviders: false).ConfigureAwait(false);
             }
             }
         }
         }
-
-        /// <summary>
-        /// Gets the backdrop filename to save.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns>System.String.</returns>
-        private string GetBackdropFilenameToSave(BaseItem item)
-        {
-            var paths = item.BackdropImagePaths.ToList();
-
-            if (!paths.Any(i => string.Equals(Path.GetFileNameWithoutExtension(i), "backdrop", StringComparison.OrdinalIgnoreCase)))
-            {
-                return "screenshot";
-            }
-
-            var index = 1;
-
-            while (paths.Any(i => string.Equals(Path.GetFileNameWithoutExtension(i), "backdrop" + index, StringComparison.OrdinalIgnoreCase)))
-            {
-                index++;
-            }
-
-            return "backdrop" + index;
-        }
-
-        /// <summary>
-        /// Gets the screenshot filename to save.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns>System.String.</returns>
-        private string GetScreenshotFilenameToSave(BaseItem item)
-        {
-            var paths = item.ScreenshotImagePaths.ToList();
-
-            if (!paths.Any(i => string.Equals(Path.GetFileNameWithoutExtension(i), "screenshot", StringComparison.OrdinalIgnoreCase)))
-            {
-                return "screenshot";
-            }
-
-            var index = 1;
-
-            while (paths.Any(i => string.Equals(Path.GetFileNameWithoutExtension(i), "screenshot" + index, StringComparison.OrdinalIgnoreCase)))
-            {
-                index++;
-            }
-
-            return "screenshot" + index;
-        }
     }
     }
 }
 }

+ 86 - 0
MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs

@@ -104,6 +104,92 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager
             return client;
             return client;
         }
         }
 
 
+        public async Task<HttpResponseInfo> GetResponse(HttpRequestOptions options)
+        {
+            ValidateParams(options.Url, options.CancellationToken);
+
+            options.CancellationToken.ThrowIfCancellationRequested();
+
+            var client = GetHttpClient(GetHostFromUrl(options.Url), options.EnableHttpCompression);
+
+            if ((DateTime.UtcNow - client.LastTimeout).TotalSeconds < 30)
+            {
+                throw new HttpException(string.Format("Cancelling connection to {0} due to a previous timeout.", options.Url)) { IsTimedOut = true };
+            }
+
+            using (var message = GetHttpRequestMessage(options))
+            {
+                if (options.ResourcePool != null)
+                {
+                    await options.ResourcePool.WaitAsync(options.CancellationToken).ConfigureAwait(false);
+                }
+
+                if ((DateTime.UtcNow - client.LastTimeout).TotalSeconds < 30)
+                {
+                    if (options.ResourcePool != null)
+                    {
+                        options.ResourcePool.Release();
+                    }
+
+                    throw new HttpException(string.Format("Connection to {0} timed out", options.Url)) { IsTimedOut = true };
+                }
+
+                _logger.Info("HttpClientManager.Get url: {0}", options.Url);
+
+                try
+                {
+                    options.CancellationToken.ThrowIfCancellationRequested();
+
+                    var response = await client.HttpClient.SendAsync(message, HttpCompletionOption.ResponseContentRead, options.CancellationToken).ConfigureAwait(false);
+
+                    EnsureSuccessStatusCode(response);
+
+                    options.CancellationToken.ThrowIfCancellationRequested();
+
+                    return new HttpResponseInfo
+                    {
+                        Content = await response.Content.ReadAsStreamAsync().ConfigureAwait(false),
+
+                        StatusCode = response.StatusCode,
+
+                        ContentType = response.Content.Headers.ContentType.MediaType
+                    };
+                }
+                catch (OperationCanceledException ex)
+                {
+                    var exception = GetCancellationException(options.Url, options.CancellationToken, ex);
+
+                    var httpException = exception as HttpException;
+
+                    if (httpException != null && httpException.IsTimedOut)
+                    {
+                        client.LastTimeout = DateTime.UtcNow;
+                    }
+
+                    throw exception;
+                }
+                catch (HttpRequestException ex)
+                {
+                    _logger.ErrorException("Error getting response from " + options.Url, ex);
+
+                    throw new HttpException(ex.Message, ex);
+                }
+                catch (Exception ex)
+                {
+                    _logger.ErrorException("Error getting response from " + options.Url, ex);
+
+                    throw;
+                }
+                finally
+                {
+                    if (options.ResourcePool != null)
+                    {
+                        options.ResourcePool.Release();
+                    }
+                }
+            }
+        }
+
         /// <summary>
         /// <summary>
         /// Performs a GET request and returns the resulting stream
         /// Performs a GET request and returns the resulting stream
         /// </summary>
         /// </summary>

+ 0 - 46
MediaBrowser.Common.Implementations/HttpClientManager/HttpResponseInfo.cs

@@ -1,46 +0,0 @@
-using System;
-
-namespace MediaBrowser.Common.Implementations.HttpClientManager
-{
-    /// <summary>
-    /// Class HttpResponseOutput
-    /// </summary>
-    public class HttpResponseInfo
-    {
-        /// <summary>
-        /// Gets or sets the URL.
-        /// </summary>
-        /// <value>The URL.</value>
-        public string Url { get; set; }
-
-        /// <summary>
-        /// Gets or sets the etag.
-        /// </summary>
-        /// <value>The etag.</value>
-        public string Etag { get; set; }
-
-        /// <summary>
-        /// Gets or sets the last modified.
-        /// </summary>
-        /// <value>The last modified.</value>
-        public DateTime? LastModified { get; set; }
-
-        /// <summary>
-        /// Gets or sets the expires.
-        /// </summary>
-        /// <value>The expires.</value>
-        public DateTime? Expires { get; set; }
-
-        /// <summary>
-        /// Gets or sets a value indicating whether [must revalidate].
-        /// </summary>
-        /// <value><c>true</c> if [must revalidate]; otherwise, <c>false</c>.</value>
-        public bool MustRevalidate { get; set; }
-
-        /// <summary>
-        /// Gets or sets the request date.
-        /// </summary>
-        /// <value>The request date.</value>
-        public DateTime RequestDate { get; set; }
-    }
-}

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

@@ -66,7 +66,6 @@
     <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="HttpClientManager\HttpResponseInfo.cs" />
     <Compile Include="Logging\LogHelper.cs" />
     <Compile Include="Logging\LogHelper.cs" />
     <Compile Include="Logging\NLogger.cs" />
     <Compile Include="Logging\NLogger.cs" />
     <Compile Include="Logging\NlogManager.cs" />
     <Compile Include="Logging\NlogManager.cs" />

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

@@ -74,6 +74,7 @@
     <Compile Include="Net\BasePeriodicWebSocketListener.cs" />
     <Compile Include="Net\BasePeriodicWebSocketListener.cs" />
     <Compile Include="Configuration\IApplicationPaths.cs" />
     <Compile Include="Configuration\IApplicationPaths.cs" />
     <Compile Include="Net\HttpRequestOptions.cs" />
     <Compile Include="Net\HttpRequestOptions.cs" />
+    <Compile Include="Net\HttpResponseInfo.cs" />
     <Compile Include="Net\IHasResultFactory.cs" />
     <Compile Include="Net\IHasResultFactory.cs" />
     <Compile Include="Net\IHttpResultFactory.cs" />
     <Compile Include="Net\IHttpResultFactory.cs" />
     <Compile Include="Net\IServerManager.cs" />
     <Compile Include="Net\IServerManager.cs" />

+ 29 - 0
MediaBrowser.Common/Net/HttpResponseInfo.cs

@@ -0,0 +1,29 @@
+using System.IO;
+using System.Net;
+
+namespace MediaBrowser.Common.Net
+{
+    /// <summary>
+    /// Class HttpResponseInfo
+    /// </summary>
+    public class HttpResponseInfo
+    {
+        /// <summary>
+        /// Gets or sets the type of the content.
+        /// </summary>
+        /// <value>The type of the content.</value>
+        public string ContentType { get; set; }
+
+        /// <summary>
+        /// Gets or sets the content.
+        /// </summary>
+        /// <value>The content.</value>
+        public Stream Content { get; set; }
+
+        /// <summary>
+        /// Gets or sets the status code.
+        /// </summary>
+        /// <value>The status code.</value>
+        public HttpStatusCode StatusCode { get; set; }
+    }
+}

+ 7 - 0
MediaBrowser.Common/Net/IHttpClient.cs

@@ -11,6 +11,13 @@ namespace MediaBrowser.Common.Net
     /// </summary>
     /// </summary>
     public interface IHttpClient : IDisposable
     public interface IHttpClient : IDisposable
     {
     {
+        /// <summary>
+        /// Gets the response.
+        /// </summary>
+        /// <param name="options">The options.</param>
+        /// <returns>Task{HttpResponseInfo}.</returns>
+        Task<HttpResponseInfo> GetResponse(HttpRequestOptions options);
+
         /// <summary>
         /// <summary>
         /// Performs a GET request and returns the resulting stream
         /// Performs a GET request and returns the resulting stream
         /// </summary>
         /// </summary>

+ 3 - 0
MediaBrowser.Controller/Entities/Folder.cs

@@ -798,6 +798,9 @@ namespace MediaBrowser.Controller.Entities
                         });
                         });
 
 
                         await ((Folder)child).ValidateChildren(innerProgress, cancellationToken, recursive, forceRefreshMetadata).ConfigureAwait(false);
                         await ((Folder)child).ValidateChildren(innerProgress, cancellationToken, recursive, forceRefreshMetadata).ConfigureAwait(false);
+
+                        // Some folder providers are unable to refresh until children have been refreshed.
+                        await child.RefreshMetadata(cancellationToken, resetResolveArgs: false).ConfigureAwait(false);
                     }
                     }
                     else
                     else
                     {
                     {

+ 21 - 37
MediaBrowser.Controller/Providers/IProviderManager.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Entities;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.IO;
 using System.IO;
 using System.Threading;
 using System.Threading;
@@ -12,64 +13,47 @@ namespace MediaBrowser.Controller.Providers
     /// </summary>
     /// </summary>
     public interface IProviderManager
     public interface IProviderManager
     {
     {
-        /// <summary>
-        /// Downloads the and save image.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="source">The source.</param>
-        /// <param name="targetName">Name of the target.</param>
-        /// <param name="saveLocally">if set to <c>true</c> [save locally].</param>
-        /// <param name="resourcePool">The resource pool.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task{System.String}.</returns>
-        /// <exception cref="System.ArgumentNullException">item</exception>
-        Task<string> DownloadAndSaveImage(BaseItem item, string source, string targetName, bool saveLocally, SemaphoreSlim resourcePool, CancellationToken cancellationToken);
+        Task<string> DownloadAndSaveImage(BaseItem item, string source, string targetName, bool saveLocally,
+                                          SemaphoreSlim resourcePool, CancellationToken cancellationToken);
 
 
         /// <summary>
         /// <summary>
-        /// Saves the image.
+        /// Executes the metadata providers.
         /// </summary>
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
-        /// <param name="source">The source.</param>
-        /// <param name="targetName">Name of the target.</param>
-        /// <param name="saveLocally">if set to <c>true</c> [save locally].</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task{System.String}.</returns>
-        Task<string> SaveImage(BaseItem item, Stream source, string targetName, bool saveLocally, CancellationToken cancellationToken);
+        /// <param name="force">if set to <c>true</c> [force].</param>
+        /// <param name="allowSlowProviders">if set to <c>true</c> [allow slow providers].</param>
+        /// <returns>Task{System.Boolean}.</returns>
+        Task<ItemUpdateType?> ExecuteMetadataProviders(BaseItem item, CancellationToken cancellationToken, bool force = false, bool allowSlowProviders = true);
 
 
         /// <summary>
         /// <summary>
-        /// Saves to library filesystem.
+        /// Saves the image.
         /// </summary>
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
-        /// <param name="path">The path.</param>
-        /// <param name="dataToSave">The data to save.</param>
+        /// <param name="url">The URL.</param>
+        /// <param name="resourcePool">The resource pool.</param>
+        /// <param name="type">The type.</param>
+        /// <param name="imageIndex">Index of the image.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task.</returns>
         /// <returns>Task.</returns>
-        /// <exception cref="System.ArgumentNullException"></exception>
-        Task SaveToLibraryFilesystem(BaseItem item, string path, Stream dataToSave, CancellationToken cancellationToken);
+        Task SaveImage(BaseItem item, string url, SemaphoreSlim resourcePool, ImageType type, int? imageIndex, CancellationToken cancellationToken);
 
 
         /// <summary>
         /// <summary>
-        /// Executes the metadata providers.
+        /// Saves the image.
         /// </summary>
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
+        /// <param name="source">The source.</param>
+        /// <param name="mimeType">Type of the MIME.</param>
+        /// <param name="type">The type.</param>
+        /// <param name="imageIndex">Index of the image.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
-        /// <param name="force">if set to <c>true</c> [force].</param>
-        /// <param name="allowSlowProviders">if set to <c>true</c> [allow slow providers].</param>
-        /// <returns>Task{System.Boolean}.</returns>
-        Task<ItemUpdateType?> ExecuteMetadataProviders(BaseItem item, CancellationToken cancellationToken, bool force = false, bool allowSlowProviders = true);
+        /// <returns>Task.</returns>
+        Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, CancellationToken cancellationToken);
 
 
         /// <summary>
         /// <summary>
         /// 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);
         void AddParts(IEnumerable<BaseMetadataProvider> providers);
-
-        /// <summary>
-        /// Gets the save path.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="targetFileName">Name of the target file.</param>
-        /// <param name="saveLocally">if set to <c>true</c> [save locally].</param>
-        /// <returns>System.String.</returns>
-        string GetSavePath(BaseItem item, string targetFileName, bool saveLocally);
     }
     }
 }
 }

+ 11 - 9
MediaBrowser.Providers/Movies/FanArtMovieProvider.cs

@@ -303,9 +303,6 @@ namespace MediaBrowser.Providers.Movies
             
             
             cancellationToken.ThrowIfCancellationRequested();
             cancellationToken.ThrowIfCancellationRequested();
 
 
-            var saveLocal = ConfigurationManager.Configuration.SaveLocalMeta &&
-                            item.LocationType == LocationType.FileSystem;
-
             string path;
             string path;
             var hd = ConfigurationManager.Configuration.DownloadHDFanArt ? "hd" : "";
             var hd = ConfigurationManager.Configuration.DownloadHDFanArt ? "hd" : "";
 
 
@@ -322,7 +319,7 @@ namespace MediaBrowser.Providers.Movies
                 path = node != null ? node.Value : null;
                 path = node != null ? node.Value : null;
                 if (!string.IsNullOrEmpty(path))
                 if (!string.IsNullOrEmpty(path))
                 {
                 {
-                    item.SetImage(ImageType.Logo, await _providerManager.DownloadAndSaveImage(item, path, LogoFile, saveLocal, FanArtResourcePool, cancellationToken).ConfigureAwait(false));
+                    await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Logo, null, cancellationToken).ConfigureAwait(false);
                 }
                 }
             }
             }
             cancellationToken.ThrowIfCancellationRequested();
             cancellationToken.ThrowIfCancellationRequested();
@@ -337,7 +334,8 @@ namespace MediaBrowser.Providers.Movies
                 path = node != null ? node.Value : null;
                 path = node != null ? node.Value : null;
                 if (!string.IsNullOrEmpty(path))
                 if (!string.IsNullOrEmpty(path))
                 {
                 {
-                    item.SetImage(ImageType.Art, await _providerManager.DownloadAndSaveImage(item, path, ArtFile, saveLocal, FanArtResourcePool, cancellationToken).ConfigureAwait(false));
+                    await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Art, null, cancellationToken)
+                                        .ConfigureAwait(false);
                 }
                 }
             }
             }
             cancellationToken.ThrowIfCancellationRequested();
             cancellationToken.ThrowIfCancellationRequested();
@@ -349,7 +347,8 @@ namespace MediaBrowser.Providers.Movies
                 path = node != null ? node.Value : null;
                 path = node != null ? node.Value : null;
                 if (!string.IsNullOrEmpty(path))
                 if (!string.IsNullOrEmpty(path))
                 {
                 {
-                    item.SetImage(ImageType.Disc, await _providerManager.DownloadAndSaveImage(item, path, DiscFile, saveLocal, FanArtResourcePool, cancellationToken).ConfigureAwait(false));
+                    await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Disc, null, cancellationToken)
+                                        .ConfigureAwait(false);
                 }
                 }
             }
             }
 
 
@@ -362,7 +361,8 @@ namespace MediaBrowser.Providers.Movies
                 path = node != null ? node.Value : null;
                 path = node != null ? node.Value : null;
                 if (!string.IsNullOrEmpty(path))
                 if (!string.IsNullOrEmpty(path))
                 {
                 {
-                    item.SetImage(ImageType.Banner, await _providerManager.DownloadAndSaveImage(item, path, BannerFile, saveLocal, FanArtResourcePool, cancellationToken).ConfigureAwait(false));
+                    await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Banner, null, cancellationToken)
+                                        .ConfigureAwait(false);
                 }
                 }
             }
             }
 
 
@@ -375,7 +375,8 @@ namespace MediaBrowser.Providers.Movies
                 path = node != null ? node.Value : null;
                 path = node != null ? node.Value : null;
                 if (!string.IsNullOrEmpty(path))
                 if (!string.IsNullOrEmpty(path))
                 {
                 {
-                    item.SetImage(ImageType.Thumb, await _providerManager.DownloadAndSaveImage(item, path, ThumbFile, saveLocal, FanArtResourcePool, cancellationToken).ConfigureAwait(false));
+                    await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Thumb, null, cancellationToken)
+                                        .ConfigureAwait(false);
                 }
                 }
             }
             }
 
 
@@ -393,7 +394,8 @@ namespace MediaBrowser.Providers.Movies
 
 
                         if (!string.IsNullOrEmpty(path))
                         if (!string.IsNullOrEmpty(path))
                         {
                         {
-                            item.BackdropImagePaths.Add(await _providerManager.DownloadAndSaveImage(item, path, ("backdrop" + (numBackdrops > 0 ? numBackdrops.ToString(UsCulture) : "") + ".jpg"), saveLocal, FanArtResourcePool, cancellationToken).ConfigureAwait(false));
+                            await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Backdrop, numBackdrops, cancellationToken)
+                                                .ConfigureAwait(false);
 
 
                             numBackdrops++;
                             numBackdrops++;
 
 

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

@@ -93,7 +93,7 @@ namespace MediaBrowser.Providers.Movies
                 return ItemUpdateType.ImageUpdate;
                 return ItemUpdateType.ImageUpdate;
             }
             }
         }
         }
-        
+
         /// <summary>
         /// <summary>
         /// Gets a value indicating whether [requires internet].
         /// Gets a value indicating whether [requires internet].
         /// </summary>
         /// </summary>
@@ -148,7 +148,7 @@ namespace MediaBrowser.Providers.Movies
             {
             {
                 return false;
                 return false;
             }
             }
-            
+
             return base.NeedsRefreshInternal(item, providerInfo);
             return base.NeedsRefreshInternal(item, providerInfo);
         }
         }
 
 
@@ -168,7 +168,7 @@ namespace MediaBrowser.Providers.Movies
                 data = new BaseProviderInfo();
                 data = new BaseProviderInfo();
                 item.ProviderData[Id] = data;
                 item.ProviderData[Id] = data;
             }
             }
-            
+
             var images = await FetchImages(item, item.GetProviderId(MetadataProviders.Tmdb), cancellationToken).ConfigureAwait(false);
             var images = await FetchImages(item, item.GetProviderId(MetadataProviders.Tmdb), cancellationToken).ConfigureAwait(false);
 
 
             var status = await ProcessImages(item, images, cancellationToken).ConfigureAwait(false);
             var status = await ProcessImages(item, images, cancellationToken).ConfigureAwait(false);
@@ -246,7 +246,9 @@ namespace MediaBrowser.Providers.Movies
 
 
                     }).ConfigureAwait(false);
                     }).ConfigureAwait(false);
 
 
-                    item.PrimaryImagePath = await _providerManager.SaveImage(item, img, "folder" + Path.GetExtension(poster.file_path), ConfigurationManager.Configuration.SaveLocalMeta && item.LocationType == LocationType.FileSystem, cancellationToken).ConfigureAwait(false);
+                    await _providerManager.SaveImage(item, img, MimeTypes.GetMimeType(poster.file_path), ImageType.Primary, null, cancellationToken)
+                                        .ConfigureAwait(false);
+
                 }
                 }
             }
             }
 
 
@@ -274,7 +276,8 @@ namespace MediaBrowser.Providers.Movies
 
 
                         }).ConfigureAwait(false);
                         }).ConfigureAwait(false);
 
 
-                        item.BackdropImagePaths.Add(await _providerManager.SaveImage(item, img, bdName + Path.GetExtension(images.backdrops[i].file_path), ConfigurationManager.Configuration.SaveLocalMeta && item.LocationType == LocationType.FileSystem, cancellationToken).ConfigureAwait(false));
+                        await _providerManager.SaveImage(item, img, MimeTypes.GetMimeType(images.backdrops[i].file_path), ImageType.Backdrop, item.BackdropImagePaths.Count, cancellationToken)
+                          .ConfigureAwait(false);
                     }
                     }
 
 
                     if (item.BackdropImagePaths.Count >= ConfigurationManager.Configuration.MaxBackdrops)
                     if (item.BackdropImagePaths.Count >= ConfigurationManager.Configuration.MaxBackdrops)

+ 20 - 38
MediaBrowser.Providers/Movies/MovieDbProvider.cs

@@ -32,7 +32,7 @@ namespace MediaBrowser.Providers.Movies
         /// <summary>
         /// <summary>
         /// The movie db
         /// The movie db
         /// </summary>
         /// </summary>
-        private readonly SemaphoreSlim _movieDbResourcePool = new SemaphoreSlim(1,1);
+        private readonly SemaphoreSlim _movieDbResourcePool = new SemaphoreSlim(1, 1);
 
 
         internal static MovieDbProvider Current { get; private set; }
         internal static MovieDbProvider Current { get; private set; }
 
 
@@ -158,7 +158,7 @@ namespace MediaBrowser.Providers.Movies
                 _tmdbSettingsSemaphore.Release();
                 _tmdbSettingsSemaphore.Release();
                 return _tmdbSettings;
                 return _tmdbSettings;
             }
             }
-            
+
             try
             try
             {
             {
                 using (var json = await GetMovieDbResponse(new HttpRequestOptions
                 using (var json = await GetMovieDbResponse(new HttpRequestOptions
@@ -199,7 +199,13 @@ namespace MediaBrowser.Providers.Movies
         protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
         protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
         {
         {
             if (HasAltMeta(item))
             if (HasAltMeta(item))
-                return false; 
+                return false;
+
+            // Boxsets require two passes because we need the children to be refreshed
+            if (item is BoxSet && string.IsNullOrEmpty(item.GetProviderId(MetadataProviders.TmdbCollection)))
+            {
+                return true;
+            }
 
 
             return base.NeedsRefreshInternal(item, providerInfo);
             return base.NeedsRefreshInternal(item, providerInfo);
         }
         }
@@ -291,19 +297,6 @@ namespace MediaBrowser.Providers.Movies
         /// <returns>Task{System.String}.</returns>
         /// <returns>Task{System.String}.</returns>
         public async Task<string> FindId(BaseItem item, int? productionYear, CancellationToken cancellationToken)
         public async Task<string> FindId(BaseItem item, int? productionYear, CancellationToken cancellationToken)
         {
         {
-            string id = null;
-
-            if (item.LocationType == LocationType.FileSystem)
-            {
-                string justName = item.Path != null ? item.Path.Substring(item.Path.LastIndexOf(Path.DirectorySeparatorChar)) : string.Empty;
-                id = justName.GetAttributeValue("tmdbid");
-                if (id != null)
-                {
-                    Logger.Debug("Using tmdb id specified in path.");
-                    return id;
-                }
-            }
-
             int? year;
             int? year;
             string name = item.Name;
             string name = item.Name;
             ParseName(name, out name, out year);
             ParseName(name, out name, out year);
@@ -320,25 +313,14 @@ namespace MediaBrowser.Providers.Movies
             var boxset = item as BoxSet;
             var boxset = item as BoxSet;
             if (boxset != null)
             if (boxset != null)
             {
             {
-                var firstChild = boxset.Children.FirstOrDefault();
-
-                if (firstChild != null)
-                {
-                    Logger.Debug("MovieDbProvider - Attempting to find boxset ID from: " + firstChild.Name);
-                    string childName;
-                    int? childYear;
-                    ParseName(firstChild.Name, out childName, out childYear);
-                    id = await GetBoxsetIdFromMovie(childName, childYear, language, cancellationToken).ConfigureAwait(false);
-                    if (id != null)
-                    {
-                        Logger.Info("MovieDbProvider - Found Boxset ID: " + id);
-                    }
-                }
-
-                return id;
+               // See if any movies have a collection id already
+                return boxset.Children.OfType<Video>()
+                    .Select(i => i.GetProviderId(MetadataProviders.TmdbCollection))
+                   .FirstOrDefault(i => i != null);
             }
             }
+
             //nope - search for it
             //nope - search for it
-            id = await AttemptFindId(name, year, language, cancellationToken).ConfigureAwait(false);
+            var id = await AttemptFindId(name, year, language, cancellationToken).ConfigureAwait(false);
             if (id == null)
             if (id == null)
             {
             {
                 //try in english if wasn't before
                 //try in english if wasn't before
@@ -509,7 +491,7 @@ namespace MediaBrowser.Providers.Movies
                             DateTime r;
                             DateTime r;
 
 
                             //These dates are always in this exact format
                             //These dates are always in this exact format
-                            if (DateTime.TryParseExact(possible.release_date, "yyyy-MM-dd", EnUs, DateTimeStyles.None,  out r))
+                            if (DateTime.TryParseExact(possible.release_date, "yyyy-MM-dd", EnUs, DateTimeStyles.None, out r))
                             {
                             {
                                 if (Math.Abs(r.Year - year.Value) > 1) // allow a 1 year tolerance on release date
                                 if (Math.Abs(r.Year - year.Value) > 1) // allow a 1 year tolerance on release date
                                 {
                                 {
@@ -708,7 +690,7 @@ namespace MediaBrowser.Providers.Movies
                     var ourRelease = movieData.releases.countries.FirstOrDefault(c => c.iso_3166_1.Equals(ConfigurationManager.Configuration.MetadataCountryCode, StringComparison.OrdinalIgnoreCase)) ?? new Country();
                     var ourRelease = movieData.releases.countries.FirstOrDefault(c => c.iso_3166_1.Equals(ConfigurationManager.Configuration.MetadataCountryCode, StringComparison.OrdinalIgnoreCase)) ?? new Country();
                     var usRelease = movieData.releases.countries.FirstOrDefault(c => c.iso_3166_1.Equals("US", StringComparison.OrdinalIgnoreCase)) ?? new Country();
                     var usRelease = movieData.releases.countries.FirstOrDefault(c => c.iso_3166_1.Equals("US", StringComparison.OrdinalIgnoreCase)) ?? new Country();
                     var minimunRelease = movieData.releases.countries.OrderBy(c => c.release_date).FirstOrDefault() ?? new Country();
                     var minimunRelease = movieData.releases.countries.OrderBy(c => c.release_date).FirstOrDefault() ?? new Country();
-                    var ratingPrefix = ConfigurationManager.Configuration.MetadataCountryCode.Equals("us", StringComparison.OrdinalIgnoreCase) ? "" : ConfigurationManager.Configuration.MetadataCountryCode +"-";
+                    var ratingPrefix = ConfigurationManager.Configuration.MetadataCountryCode.Equals("us", StringComparison.OrdinalIgnoreCase) ? "" : ConfigurationManager.Configuration.MetadataCountryCode + "-";
                     movie.OfficialRating = !string.IsNullOrEmpty(ourRelease.certification)
                     movie.OfficialRating = !string.IsNullOrEmpty(ourRelease.certification)
                                                ? ratingPrefix + ourRelease.certification
                                                ? ratingPrefix + ourRelease.certification
                                                : !string.IsNullOrEmpty(usRelease.certification)
                                                : !string.IsNullOrEmpty(usRelease.certification)
@@ -725,7 +707,7 @@ namespace MediaBrowser.Providers.Movies
                             movie.ProductionYear = ourRelease.release_date.Year;
                             movie.ProductionYear = ourRelease.release_date.Year;
                         }
                         }
                     }
                     }
-                    else if(usRelease.release_date != default (DateTime))
+                    else if (usRelease.release_date != default(DateTime))
                     {
                     {
                         if (usRelease.release_date.Year != 1)
                         if (usRelease.release_date.Year != 1)
                         {
                         {
@@ -733,7 +715,7 @@ namespace MediaBrowser.Providers.Movies
                             movie.ProductionYear = usRelease.release_date.Year;
                             movie.ProductionYear = usRelease.release_date.Year;
                         }
                         }
                     }
                     }
-                    else if (minimunRelease.release_date != default (DateTime))
+                    else if (minimunRelease.release_date != default(DateTime))
                     {
                     {
                         if (minimunRelease.release_date.Year != 1)
                         if (minimunRelease.release_date.Year != 1)
                         {
                         {
@@ -1099,7 +1081,7 @@ namespace MediaBrowser.Providers.Movies
             /// <value>The total_results.</value>
             /// <value>The total_results.</value>
             public int total_results { get; set; }
             public int total_results { get; set; }
         }
         }
-        
+
         protected class BelongsToCollection
         protected class BelongsToCollection
         {
         {
             public int id { get; set; }
             public int id { get; set; }

+ 7 - 16
MediaBrowser.Providers/Movies/TmdbPersonProvider.cs

@@ -279,13 +279,8 @@ namespace MediaBrowser.Providers.Movies
                 {
                 {
                     var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
                     var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
 
 
-                    var img = await DownloadAndSaveImage(person, tmdbSettings.images.base_url + ConfigurationManager.Configuration.TmdbFetchedProfileSize + profile.file_path,
-                                             "folder" + Path.GetExtension(profile.file_path), cancellationToken).ConfigureAwait(false);
-
-                    if (!string.IsNullOrEmpty(img))
-                    {
-                        person.PrimaryImagePath = img;
-                    }
+                    await DownloadAndSaveImage(person, tmdbSettings.images.base_url + ConfigurationManager.Configuration.TmdbFetchedProfileSize + profile.file_path,
+                                             MimeTypes.GetMimeType(profile.file_path), cancellationToken).ConfigureAwait(false);
                 }
                 }
             }
             }
         }
         }
@@ -300,15 +295,12 @@ namespace MediaBrowser.Providers.Movies
         /// </summary>
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="item">The item.</param>
         /// <param name="source">The source.</param>
         /// <param name="source">The source.</param>
-        /// <param name="targetName">Name of the target.</param>
+        /// <param name="mimeType">Type of the MIME.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task{System.String}.</returns>
         /// <returns>Task{System.String}.</returns>
-        private async Task<string> DownloadAndSaveImage(BaseItem item, string source, string targetName, CancellationToken cancellationToken)
+        private async Task DownloadAndSaveImage(BaseItem item, string source, string mimeType, CancellationToken cancellationToken)
         {
         {
-            if (source == null) return null;
-
-            //download and save locally (if not already there)
-            var localPath = Path.Combine(item.MetaLocation, targetName);
+            if (source == null) return;
 
 
             using (var sourceStream = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions
             using (var sourceStream = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions
             {
             {
@@ -317,12 +309,11 @@ namespace MediaBrowser.Providers.Movies
 
 
             }).ConfigureAwait(false))
             }).ConfigureAwait(false))
             {
             {
-                await ProviderManager.SaveToLibraryFilesystem(item, localPath, sourceStream, cancellationToken).ConfigureAwait(false);
+                await ProviderManager.SaveImage(item, sourceStream, mimeType, ImageType.Primary, null, cancellationToken)
+                                   .ConfigureAwait(false);
 
 
                 Logger.Debug("TmdbPersonProvider downloaded and saved image for {0}", item.Name);
                 Logger.Debug("TmdbPersonProvider downloaded and saved image for {0}", item.Name);
             }
             }
-
-            return localPath;
         }
         }
 
 
         #region Result Objects
         #region Result Objects

+ 4 - 2
MediaBrowser.Providers/Music/FanArtAlbumProvider.cs

@@ -199,7 +199,8 @@ namespace MediaBrowser.Providers.Music
 
 
                         if (!string.IsNullOrEmpty(path))
                         if (!string.IsNullOrEmpty(path))
                         {
                         {
-                            item.SetImage(ImageType.Disc, await _providerManager.DownloadAndSaveImage(item, path, DiscFile, ConfigurationManager.Configuration.SaveLocalMeta, FanArtResourcePool, cancellationToken).ConfigureAwait(false));
+                            await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Disc, null, cancellationToken)
+                                .ConfigureAwait(false);
                         }
                         }
                     }
                     }
 
 
@@ -217,7 +218,8 @@ namespace MediaBrowser.Providers.Music
 
 
                         if (!string.IsNullOrEmpty(path))
                         if (!string.IsNullOrEmpty(path))
                         {
                         {
-                            item.SetImage(ImageType.Primary, await _providerManager.DownloadAndSaveImage(item, path, PrimaryFile, ConfigurationManager.Configuration.SaveLocalMeta, FanArtResourcePool, cancellationToken).ConfigureAwait(false));
+                            await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Primary, null, cancellationToken)
+                                .ConfigureAwait(false);
                         }
                         }
                     }
                     }
                 }
                 }

+ 10 - 5
MediaBrowser.Providers/Music/FanArtArtistProvider.cs

@@ -306,7 +306,8 @@ namespace MediaBrowser.Providers.Music
                 path = node != null ? node.Value : null;
                 path = node != null ? node.Value : null;
                 if (!string.IsNullOrEmpty(path))
                 if (!string.IsNullOrEmpty(path))
                 {
                 {
-                    item.SetImage(ImageType.Logo, await _providerManager.DownloadAndSaveImage(item, path, LogoFile, SaveLocalMeta, FanArtResourcePool, cancellationToken).ConfigureAwait(false));
+                    await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Logo, null, cancellationToken)
+                        .ConfigureAwait(false);
                 }
                 }
             }
             }
             cancellationToken.ThrowIfCancellationRequested();
             cancellationToken.ThrowIfCancellationRequested();
@@ -323,7 +324,8 @@ namespace MediaBrowser.Providers.Music
                         path = node.Value;
                         path = node.Value;
                         if (!string.IsNullOrEmpty(path))
                         if (!string.IsNullOrEmpty(path))
                         {
                         {
-                            item.BackdropImagePaths.Add(await _providerManager.DownloadAndSaveImage(item, path, ("Backdrop" + (numBackdrops > 0 ? numBackdrops.ToString(UsCulture) : "") + ".jpg"), SaveLocalMeta, FanArtResourcePool, cancellationToken).ConfigureAwait(false));
+                            await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Backdrop, numBackdrops, cancellationToken)
+                                .ConfigureAwait(false);
                             numBackdrops++;
                             numBackdrops++;
                             if (numBackdrops >= ConfigurationManager.Configuration.MaxBackdrops) break;
                             if (numBackdrops >= ConfigurationManager.Configuration.MaxBackdrops) break;
                         }
                         }
@@ -343,7 +345,8 @@ namespace MediaBrowser.Providers.Music
                 path = node != null ? node.Value : null;
                 path = node != null ? node.Value : null;
                 if (!string.IsNullOrEmpty(path))
                 if (!string.IsNullOrEmpty(path))
                 {
                 {
-                    item.SetImage(ImageType.Art, await _providerManager.DownloadAndSaveImage(item, path, ArtFile, SaveLocalMeta, FanArtResourcePool, cancellationToken).ConfigureAwait(false));
+                    await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Art, null, cancellationToken)
+                        .ConfigureAwait(false);
                 }
                 }
             }
             }
             cancellationToken.ThrowIfCancellationRequested();
             cancellationToken.ThrowIfCancellationRequested();
@@ -355,7 +358,8 @@ namespace MediaBrowser.Providers.Music
                 path = node != null ? node.Value : null;
                 path = node != null ? node.Value : null;
                 if (!string.IsNullOrEmpty(path))
                 if (!string.IsNullOrEmpty(path))
                 {
                 {
-                    item.SetImage(ImageType.Banner, await _providerManager.DownloadAndSaveImage(item, path, BannerFile, SaveLocalMeta, FanArtResourcePool, cancellationToken).ConfigureAwait(false));
+                    await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Banner, null, cancellationToken)
+                        .ConfigureAwait(false);
                 }
                 }
             }
             }
 
 
@@ -368,7 +372,8 @@ namespace MediaBrowser.Providers.Music
                 path = node != null ? node.Value : null;
                 path = node != null ? node.Value : null;
                 if (!string.IsNullOrEmpty(path))
                 if (!string.IsNullOrEmpty(path))
                 {
                 {
-                    item.SetImage(ImageType.Primary, await _providerManager.DownloadAndSaveImage(item, path, PrimaryFile, SaveLocalMeta, FanArtResourcePool, cancellationToken).ConfigureAwait(false));
+                    await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Primary, null, cancellationToken)
+                        .ConfigureAwait(false);
                 }
                 }
             }
             }
         }
         }

+ 9 - 6
MediaBrowser.Providers/Savers/SeriesXmlSaver.cs

@@ -82,13 +82,16 @@ namespace MediaBrowser.Providers.Savers
                 builder.Append("<Airs_Time>" + SecurityElement.Escape(series.AirTime) + "</Airs_Time>");
                 builder.Append("<Airs_Time>" + SecurityElement.Escape(series.AirTime) + "</Airs_Time>");
             }
             }
 
 
-            if (series.AirDays.Count == 7)
+            if (series.AirDays != null)
             {
             {
-                builder.Append("<Airs_DayOfWeek>" + SecurityElement.Escape("Daily") + "</Airs_DayOfWeek>");
-            }
-            else if (series.AirDays.Count > 0)
-            {
-                builder.Append("<Airs_DayOfWeek>" + SecurityElement.Escape(series.AirDays[0].ToString()) + "</Airs_DayOfWeek>");
+                if (series.AirDays.Count == 7)
+                {
+                    builder.Append("<Airs_DayOfWeek>" + SecurityElement.Escape("Daily") + "</Airs_DayOfWeek>");
+                }
+                else if (series.AirDays.Count > 0)
+                {
+                    builder.Append("<Airs_DayOfWeek>" + SecurityElement.Escape(series.AirDays[0].ToString()) + "</Airs_DayOfWeek>");
+                }
             }
             }
 
 
             XmlSaverHelpers.AddCommonNodes(item, builder);
             XmlSaverHelpers.AddCommonNodes(item, builder);

+ 2 - 1
MediaBrowser.Providers/TV/FanArtSeasonProvider.cs

@@ -149,7 +149,8 @@ namespace MediaBrowser.Providers.TV
                 
                 
                 if (!string.IsNullOrEmpty(path))
                 if (!string.IsNullOrEmpty(path))
                 {
                 {
-                    season.SetImage(ImageType.Thumb, await _providerManager.DownloadAndSaveImage(season, path, ThumbFile, ConfigurationManager.Configuration.SaveLocalMeta, FanArtResourcePool, cancellationToken).ConfigureAwait(false));
+                    await _providerManager.SaveImage(season, path, FanArtResourcePool, ImageType.Thumb, null, cancellationToken)
+                                        .ConfigureAwait(false);
                 }
                 }
             }
             }
         }
         }

+ 10 - 5
MediaBrowser.Providers/TV/FanArtTVProvider.cs

@@ -234,7 +234,8 @@ namespace MediaBrowser.Providers.TV
                 var path = node != null ? node.Value : null;
                 var path = node != null ? node.Value : null;
                 if (!string.IsNullOrEmpty(path))
                 if (!string.IsNullOrEmpty(path))
                 {
                 {
-                    item.SetImage(ImageType.Logo, await _providerManager.DownloadAndSaveImage(item, path, LogoFile, ConfigurationManager.Configuration.SaveLocalMeta, FanArtResourcePool, cancellationToken).ConfigureAwait(false));
+                    await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Logo, null, cancellationToken)
+                          .ConfigureAwait(false);
                 }
                 }
             }
             }
 
 
@@ -250,7 +251,8 @@ namespace MediaBrowser.Providers.TV
                 var path = node != null ? node.Value : null;
                 var path = node != null ? node.Value : null;
                 if (!string.IsNullOrEmpty(path))
                 if (!string.IsNullOrEmpty(path))
                 {
                 {
-                    item.SetImage(ImageType.Art, await _providerManager.DownloadAndSaveImage(item, path, ArtFile, ConfigurationManager.Configuration.SaveLocalMeta, FanArtResourcePool, cancellationToken).ConfigureAwait(false));
+                    await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Art, null, cancellationToken)
+                          .ConfigureAwait(false);
                 }
                 }
             }
             }
 
 
@@ -263,7 +265,8 @@ namespace MediaBrowser.Providers.TV
                 var path = node != null ? node.Value : null;
                 var path = node != null ? node.Value : null;
                 if (!string.IsNullOrEmpty(path))
                 if (!string.IsNullOrEmpty(path))
                 {
                 {
-                    item.SetImage(ImageType.Thumb, await _providerManager.DownloadAndSaveImage(item, path, ThumbFile, ConfigurationManager.Configuration.SaveLocalMeta, FanArtResourcePool, cancellationToken).ConfigureAwait(false));
+                    await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Thumb, null, cancellationToken)
+                          .ConfigureAwait(false);
                 }
                 }
             }
             }
 
 
@@ -274,7 +277,8 @@ namespace MediaBrowser.Providers.TV
                 var path = node != null ? node.Value : null;
                 var path = node != null ? node.Value : null;
                 if (!string.IsNullOrEmpty(path))
                 if (!string.IsNullOrEmpty(path))
                 {
                 {
-                    item.SetImage(ImageType.Banner, await _providerManager.DownloadAndSaveImage(item, path, BannerFile, ConfigurationManager.Configuration.SaveLocalMeta, FanArtResourcePool, cancellationToken).ConfigureAwait(false));
+                    await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Banner, null, cancellationToken)
+                          .ConfigureAwait(false);
                 }
                 }
             }
             }
 
 
@@ -292,7 +296,8 @@ namespace MediaBrowser.Providers.TV
 
 
                         if (!string.IsNullOrEmpty(path))
                         if (!string.IsNullOrEmpty(path))
                         {
                         {
-                            item.BackdropImagePaths.Add(await _providerManager.DownloadAndSaveImage(item, path, ("backdrop" + (numBackdrops > 0 ? numBackdrops.ToString(UsCulture) : "") + ".jpg"), ConfigurationManager.Configuration.SaveLocalMeta, FanArtResourcePool, cancellationToken).ConfigureAwait(false));
+                            await _providerManager.SaveImage(item, path, FanArtResourcePool, ImageType.Backdrop, numBackdrops, cancellationToken)
+                                  .ConfigureAwait(false);
 
 
                             numBackdrops++;
                             numBackdrops++;
 
 

+ 4 - 1
MediaBrowser.Providers/TV/RemoteEpisodeProvider.cs

@@ -276,7 +276,10 @@ namespace MediaBrowser.Providers.TV
 
 
                     try
                     try
                     {
                     {
-                        episode.PrimaryImagePath = await _providerManager.DownloadAndSaveImage(episode, TVUtils.BannerUrl + p, Path.GetFileName(p), ConfigurationManager.Configuration.SaveLocalMeta, RemoteSeriesProvider.Current.TvDbResourcePool, cancellationToken);
+                        var url = TVUtils.BannerUrl + p;
+
+                        await _providerManager.SaveImage(episode, url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Primary, null, cancellationToken)
+                          .ConfigureAwait(false);
                     }
                     }
                     catch (HttpException)
                     catch (HttpException)
                     {
                     {

+ 15 - 11
MediaBrowser.Providers/TV/RemoteSeasonProvider.cs

@@ -188,7 +188,12 @@ namespace MediaBrowser.Providers.TV
                     n = n.SelectSingleNode("./BannerPath");
                     n = n.SelectSingleNode("./BannerPath");
 
 
                     if (n != null)
                     if (n != null)
-                        season.PrimaryImagePath = await _providerManager.DownloadAndSaveImage(season, TVUtils.BannerUrl + n.InnerText, "folder" + Path.GetExtension(n.InnerText), ConfigurationManager.Configuration.SaveLocalMeta, RemoteSeriesProvider.Current.TvDbResourcePool, cancellationToken).ConfigureAwait(false);
+                    {
+                        var url = TVUtils.BannerUrl + n.InnerText;
+
+                        await _providerManager.SaveImage(season, url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Primary, null, cancellationToken)
+                          .ConfigureAwait(false);
+                    }
                 }
                 }
             }
             }
 
 
@@ -203,15 +208,10 @@ namespace MediaBrowser.Providers.TV
                     {
                     {
                         try
                         try
                         {
                         {
-                            var bannerImagePath =
-                                await _providerManager.DownloadAndSaveImage(season,
-                                                                                 TVUtils.BannerUrl + n.InnerText,
-                                                                                 "banner" +
-                                                                                 Path.GetExtension(n.InnerText),
-                                                                                 ConfigurationManager.Configuration.SaveLocalMeta, RemoteSeriesProvider.Current.TvDbResourcePool, cancellationToken).
-                                                   ConfigureAwait(false);
-
-                            season.SetImage(ImageType.Banner, bannerImagePath);
+                            var url = TVUtils.BannerUrl + n.InnerText;
+
+                            await _providerManager.SaveImage(season, url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Banner, null, cancellationToken)
+                              .ConfigureAwait(false);
                         }
                         }
                         catch (HttpException ex)
                         catch (HttpException ex)
                         {
                         {
@@ -235,7 +235,11 @@ namespace MediaBrowser.Providers.TV
                     n = n.SelectSingleNode("./BannerPath");
                     n = n.SelectSingleNode("./BannerPath");
                     if (n != null)
                     if (n != null)
                     {
                     {
-                        season.BackdropImagePaths.Add(await _providerManager.DownloadAndSaveImage(season, TVUtils.BannerUrl + n.InnerText, "backdrop" + Path.GetExtension(n.InnerText), ConfigurationManager.Configuration.SaveLocalMeta, RemoteSeriesProvider.Current.TvDbResourcePool, cancellationToken).ConfigureAwait(false));
+                        var url = TVUtils.BannerUrl + n.InnerText;
+
+                        await _providerManager.SaveImage(season, url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Backdrop, 0, cancellationToken)
+                          .ConfigureAwait(false);
+
                     }
                     }
                 }
                 }
             }
             }

+ 0 - 7
MediaBrowser.Providers/TV/RemoteSeriesProvider.cs

@@ -215,13 +215,6 @@ namespace MediaBrowser.Providers.TV
                 await FetchSeriesData(series, seriesId, seriesDataPath, force, cancellationToken).ConfigureAwait(false);
                 await FetchSeriesData(series, seriesId, seriesDataPath, force, cancellationToken).ConfigureAwait(false);
             }
             }
 
 
-            BaseProviderInfo data;
-            if (!item.ProviderData.TryGetValue(Id, out data))
-            {
-                data = new BaseProviderInfo();
-                item.ProviderData[Id] = data;
-            }
-
             SetLastRefreshed(item, DateTime.UtcNow);
             SetLastRefreshed(item, DateTime.UtcNow);
             return true;
             return true;
         }
         }

+ 11 - 6
MediaBrowser.Providers/TV/TvdbSeriesImageProvider.cs

@@ -197,9 +197,10 @@ namespace MediaBrowser.Providers.TV
                     n = n.SelectSingleNode("./BannerPath");
                     n = n.SelectSingleNode("./BannerPath");
                     if (n != null)
                     if (n != null)
                     {
                     {
-                        var path = await _providerManager.DownloadAndSaveImage(series, TVUtils.BannerUrl + n.InnerText, "folder" + Path.GetExtension(n.InnerText), ConfigurationManager.Configuration.SaveLocalMeta, RemoteSeriesProvider.Current.TvDbResourcePool, cancellationToken).ConfigureAwait(false);
+                        var url = TVUtils.BannerUrl + n.InnerText;
 
 
-                        series.SetImage(ImageType.Primary, path);
+                        await _providerManager.SaveImage(series, url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Primary, null, cancellationToken)
+                          .ConfigureAwait(false);
                     }
                     }
                 }
                 }
             }
             }
@@ -212,9 +213,10 @@ namespace MediaBrowser.Providers.TV
                     n = n.SelectSingleNode("./BannerPath");
                     n = n.SelectSingleNode("./BannerPath");
                     if (n != null)
                     if (n != null)
                     {
                     {
-                        var bannerImagePath = await _providerManager.DownloadAndSaveImage(series, TVUtils.BannerUrl + n.InnerText, "banner" + Path.GetExtension(n.InnerText), ConfigurationManager.Configuration.SaveLocalMeta, RemoteSeriesProvider.Current.TvDbResourcePool, cancellationToken);
+                        var url = TVUtils.BannerUrl + n.InnerText;
 
 
-                        series.SetImage(ImageType.Banner, bannerImagePath);
+                        await _providerManager.SaveImage(series, url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Banner, null, cancellationToken)
+                          .ConfigureAwait(false);
                     }
                     }
                 }
                 }
             }
             }
@@ -232,8 +234,11 @@ namespace MediaBrowser.Providers.TV
 
 
                         if (p != null)
                         if (p != null)
                         {
                         {
-                            var bdName = "backdrop" + (bdNo > 0 ? bdNo.ToString(UsCulture) : "");
-                            series.BackdropImagePaths.Add(await _providerManager.DownloadAndSaveImage(series, TVUtils.BannerUrl + p.InnerText, bdName + Path.GetExtension(p.InnerText), ConfigurationManager.Configuration.SaveLocalMeta, RemoteSeriesProvider.Current.TvDbResourcePool, cancellationToken).ConfigureAwait(false));
+                            var url = TVUtils.BannerUrl + p.InnerText;
+
+                            await _providerManager.SaveImage(series, url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Backdrop, bdNo, cancellationToken)
+                              .ConfigureAwait(false);
+                            
                             bdNo++;
                             bdNo++;
                         }
                         }
 
 

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

@@ -148,6 +148,7 @@
     <Compile Include="Persistence\SqliteExtensions.cs" />
     <Compile Include="Persistence\SqliteExtensions.cs" />
     <Compile Include="Persistence\TypeMapper.cs" />
     <Compile Include="Persistence\TypeMapper.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="Providers\ImageSaver.cs" />
     <Compile Include="Providers\ProviderManager.cs" />
     <Compile Include="Providers\ProviderManager.cs" />
     <Compile Include="ScheduledTasks\ArtistValidationTask.cs" />
     <Compile Include="ScheduledTasks\ArtistValidationTask.cs" />
     <Compile Include="ScheduledTasks\PeopleValidationTask.cs" />
     <Compile Include="ScheduledTasks\PeopleValidationTask.cs" />

+ 255 - 0
MediaBrowser.Server.Implementations/Providers/ImageSaver.cs

@@ -0,0 +1,255 @@
+using System.Globalization;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.IO;
+using MediaBrowser.Model.Entities;
+using System;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Server.Implementations.Providers
+{
+    /// <summary>
+    /// Class ImageSaver
+    /// </summary>
+    public class ImageSaver
+    {
+        private static readonly CultureInfo UsCulture = new CultureInfo("en-US");
+
+        /// <summary>
+        /// The _config
+        /// </summary>
+        private readonly IServerConfigurationManager _config;
+
+        /// <summary>
+        /// The remote image cache
+        /// </summary>
+        private readonly FileSystemRepository _remoteImageCache;
+        /// <summary>
+        /// The _directory watchers
+        /// </summary>
+        private readonly IDirectoryWatchers _directoryWatchers;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ImageSaver"/> class.
+        /// </summary>
+        /// <param name="config">The config.</param>
+        /// <param name="directoryWatchers">The directory watchers.</param>
+        public ImageSaver(IServerConfigurationManager config, IDirectoryWatchers directoryWatchers)
+        {
+            _config = config;
+            _directoryWatchers = directoryWatchers;
+            _remoteImageCache = new FileSystemRepository(config.ApplicationPaths.DownloadedImagesDataPath);
+        }
+
+        /// <summary>
+        /// Saves the image.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <param name="source">The source.</param>
+        /// <param name="mimeType">Type of the MIME.</param>
+        /// <param name="type">The type.</param>
+        /// <param name="imageIndex">Index of the image.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        public async Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, CancellationToken cancellationToken)
+        {
+            if (string.IsNullOrEmpty(mimeType))
+            {
+                throw new ArgumentNullException("mimeType");
+            }
+
+            var saveLocally = _config.Configuration.SaveLocalMeta;
+
+            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))
+            {
+                saveLocally = false;
+            }
+
+            if (type != ImageType.Primary)
+            {
+                if (item is Episode)
+                {
+                    saveLocally = false;
+                }
+            }
+
+            if (item.LocationType != LocationType.FileSystem)
+            {
+                saveLocally = false;
+            }
+
+            var path = GetSavePath(item, type, imageIndex, mimeType, saveLocally);
+
+            var currentPath = GetCurrentImagePath(item, type, imageIndex);
+
+            try
+            {
+                _directoryWatchers.TemporarilyIgnore(path);
+
+                using (source)
+                {
+                    using (var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
+                    {
+                        await source.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false);
+                    }
+                }
+
+                SetImagePath(item, type, imageIndex, path);
+
+                if (!string.IsNullOrEmpty(currentPath) && !string.Equals(path, currentPath, StringComparison.OrdinalIgnoreCase))
+                {
+                    File.Delete(currentPath);
+                }
+            }
+            finally
+            {
+                _directoryWatchers.RemoveTempIgnore(path);
+            }
+
+        }
+
+        private string GetCurrentImagePath(BaseItem item, ImageType type, int? imageIndex)
+        {
+            switch (type)
+            {
+                case ImageType.Screenshot:
+
+                    if (!imageIndex.HasValue)
+                    {
+                        throw new ArgumentNullException("imageIndex");
+                    }
+                    return item.ScreenshotImagePaths.Count > imageIndex.Value ? item.ScreenshotImagePaths[imageIndex.Value] : null;
+                case ImageType.Backdrop:
+                    if (!imageIndex.HasValue)
+                    {
+                        throw new ArgumentNullException("imageIndex");
+                    }
+                    return item.BackdropImagePaths.Count > imageIndex.Value ? item.BackdropImagePaths[imageIndex.Value] : null;
+                default:
+                    return item.GetImage(type);
+            }
+        }
+
+        private void SetImagePath(BaseItem item, ImageType type, int? imageIndex, string path)
+        {
+            switch (type)
+            {
+                case ImageType.Screenshot:
+
+                    if (!imageIndex.HasValue)
+                    {
+                        throw new ArgumentNullException("imageIndex");
+                    }
+
+                    if (item.ScreenshotImagePaths.Count > imageIndex.Value)
+                    {
+                        item.ScreenshotImagePaths[imageIndex.Value] = path;
+                    }
+                    else
+                    {
+                        item.ScreenshotImagePaths.Add(path);
+                    }
+                    break;
+                case ImageType.Backdrop:
+                    if (!imageIndex.HasValue)
+                    {
+                        throw new ArgumentNullException("imageIndex");
+                    }
+                    if (item.BackdropImagePaths.Count > imageIndex.Value)
+                    {
+                        item.BackdropImagePaths[imageIndex.Value] = path;
+                    }
+                    else
+                    {
+                        item.BackdropImagePaths.Add(path);
+                    }
+                    break;
+                default:
+                    item.SetImage(type, path);
+                    break;
+            }
+        }
+
+        /// <summary>
+        /// Gets the save path.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <param name="type">The type.</param>
+        /// <param name="imageIndex">Index of the image.</param>
+        /// <param name="mimeType">Type of the MIME.</param>
+        /// <param name="saveLocally">if set to <c>true</c> [save locally].</param>
+        /// <returns>System.String.</returns>
+        /// <exception cref="System.ArgumentNullException">
+        /// imageIndex
+        /// or
+        /// imageIndex
+        /// </exception>
+        private string GetSavePath(BaseItem item, ImageType type, int? imageIndex, string mimeType, bool saveLocally)
+        {
+            string filename;
+
+            switch (type)
+            {
+                case ImageType.Art:
+                    filename = "clearart";
+                    break;
+                case ImageType.Primary:
+                    filename = item is Episode ? Path.GetFileNameWithoutExtension(item.Path) : "folder";
+                    break;
+                case ImageType.Backdrop:
+                    if (!imageIndex.HasValue)
+                    {
+                        throw new ArgumentNullException("imageIndex");
+                    }
+                    filename = imageIndex.Value == 0 ? "backdrop" : "backdrop" + imageIndex.Value.ToString(UsCulture);
+                    break;
+                case ImageType.Screenshot:
+                    if (!imageIndex.HasValue)
+                    {
+                        throw new ArgumentNullException("imageIndex");
+                    }
+                    filename = imageIndex.Value == 0 ? "screenshot" : "screenshot" + imageIndex.Value.ToString(UsCulture);
+                    break;
+                default:
+                    filename = type.ToString().ToLower();
+                    break;
+            }
+
+            var extension = mimeType.Split('/').Last();
+
+            if (string.Equals(extension, "jpeg", StringComparison.OrdinalIgnoreCase))
+            {
+                extension = "jpg";
+            }
+
+            filename += "." + extension.ToLower();
+
+            var path = (saveLocally && !string.IsNullOrEmpty(item.MetaLocation)) ?
+                Path.Combine(item.MetaLocation, filename) :
+                _remoteImageCache.GetResourcePath(item.GetType().FullName + item.Id, filename);
+
+            var parentPath = Path.GetDirectoryName(path);
+
+            if (!Directory.Exists(parentPath))
+            {
+                Directory.CreateDirectory(parentPath);
+            }
+
+            return path;
+        }
+    }
+}

+ 46 - 90
MediaBrowser.Server.Implementations/Providers/ProviderManager.cs

@@ -2,11 +2,14 @@
 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.TV;
 using MediaBrowser.Controller.IO;
 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.Concurrent;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
 using System.Collections.Generic;
@@ -22,11 +25,6 @@ namespace MediaBrowser.Server.Implementations.Providers
     /// </summary>
     /// </summary>
     public class ProviderManager : IProviderManager
     public class ProviderManager : IProviderManager
     {
     {
-        /// <summary>
-        /// The remote image cache
-        /// </summary>
-        private readonly FileSystemRepository _remoteImageCache;
-
         /// <summary>
         /// <summary>
         /// The currently running metadata providers
         /// The currently running metadata providers
         /// </summary>
         /// </summary>
@@ -74,7 +72,6 @@ namespace MediaBrowser.Server.Implementations.Providers
             _httpClient = httpClient;
             _httpClient = httpClient;
             ConfigurationManager = configurationManager;
             ConfigurationManager = configurationManager;
             _directoryWatchers = directoryWatchers;
             _directoryWatchers = directoryWatchers;
-            _remoteImageCache = new FileSystemRepository(configurationManager.ApplicationPaths.DownloadedImagesDataPath);
 
 
             configurationManager.ConfigurationUpdated += configurationManager_ConfigurationUpdated;
             configurationManager.ConfigurationUpdated += configurationManager_ConfigurationUpdated;
         }
         }
@@ -206,7 +203,7 @@ namespace MediaBrowser.Server.Implementations.Providers
             try
             try
             {
             {
                 var changed = await provider.FetchAsync(item, force, CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, innerCancellationTokenSource.Token).Token).ConfigureAwait(false);
                 var changed = await provider.FetchAsync(item, force, CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, innerCancellationTokenSource.Token).Token).ConfigureAwait(false);
-            
+
                 if (changed)
                 if (changed)
                 {
                 {
                     return provider.ItemUpdateType;
                     return provider.ItemUpdateType;
@@ -315,90 +312,9 @@ namespace MediaBrowser.Server.Implementations.Providers
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task{System.String}.</returns>
         /// <returns>Task{System.String}.</returns>
         /// <exception cref="System.ArgumentNullException">item</exception>
         /// <exception cref="System.ArgumentNullException">item</exception>
-        public async Task<string> DownloadAndSaveImage(BaseItem item, string source, string targetName, bool saveLocally, SemaphoreSlim resourcePool, CancellationToken cancellationToken)
+        public Task<string> DownloadAndSaveImage(BaseItem item, string source, string targetName, bool saveLocally, SemaphoreSlim resourcePool, CancellationToken cancellationToken)
         {
         {
-            if (item == null)
-            {
-                throw new ArgumentNullException("item");
-            }
-            if (string.IsNullOrEmpty(source))
-            {
-                throw new ArgumentNullException("source");
-            }
-            if (string.IsNullOrEmpty(targetName))
-            {
-                throw new ArgumentNullException("targetName");
-            }
-            if (resourcePool == null)
-            {
-                throw new ArgumentNullException("resourcePool");
-            }
-
-            var img = await _httpClient.Get(source, resourcePool, cancellationToken).ConfigureAwait(false);
-
-            //download and save locally
-            return await SaveImage(item, img, targetName, saveLocally, cancellationToken).ConfigureAwait(false);
-        }
-
-        public async Task<string> SaveImage(BaseItem item, Stream source, string targetName, bool saveLocally, CancellationToken cancellationToken)
-        {
-            //download and save locally
-            var localPath = GetSavePath(item, targetName, saveLocally);
-
-            if (saveLocally) // queue to media directories
-            {
-                await SaveToLibraryFilesystem(item, localPath, source, cancellationToken).ConfigureAwait(false);
-            }
-            else
-            {
-                // we can write directly here because it won't affect the watchers
-
-                try
-                {
-                    using (var fs = new FileStream(localPath, FileMode.Create, FileAccess.Write, FileShare.Read, StreamDefaults.DefaultFileStreamBufferSize, FileOptions.Asynchronous))
-                    {
-                        await source.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false);
-                    }
-                }
-                catch (OperationCanceledException)
-                {
-                    throw;
-                }
-                catch (Exception e)
-                {
-                    _logger.ErrorException("Error downloading and saving image " + localPath, e);
-                    throw;
-                }
-                finally
-                {
-                    source.Dispose();
-                }
-
-            }
-            return localPath;
-        }
-
-        /// <summary>
-        /// Gets the save path.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="targetFileName">Name of the target file.</param>
-        /// <param name="saveLocally">if set to <c>true</c> [save locally].</param>
-        /// <returns>System.String.</returns>
-        public string GetSavePath(BaseItem item, string targetFileName, bool saveLocally)
-        {
-            var path = (saveLocally && item.MetaLocation != null) ?
-                Path.Combine(item.MetaLocation, targetFileName) :
-                _remoteImageCache.GetResourcePath(item.GetType().FullName + item.Id.ToString(), targetFileName);
-
-            var parentPath = Path.GetDirectoryName(path);
-
-            if (!Directory.Exists(parentPath))
-            {
-                Directory.CreateDirectory(parentPath);
-            }
-
-            return path;
+            throw new HttpException(string.Empty) { IsTimedOut = true };
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -462,5 +378,45 @@ namespace MediaBrowser.Server.Implementations.Providers
                 _directoryWatchers.RemoveTempIgnore(path);
                 _directoryWatchers.RemoveTempIgnore(path);
             }
             }
         }
         }
+
+
+        /// <summary>
+        /// Saves the image.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <param name="url">The URL.</param>
+        /// <param name="resourcePool">The resource pool.</param>
+        /// <param name="type">The type.</param>
+        /// <param name="imageIndex">Index of the image.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        public async Task SaveImage(BaseItem item, string url, SemaphoreSlim resourcePool, ImageType type, int? imageIndex, CancellationToken cancellationToken)
+        {
+            var response = await _httpClient.GetResponse(new HttpRequestOptions
+            {
+                CancellationToken = cancellationToken,
+                ResourcePool = resourcePool,
+                Url = url
+
+            }).ConfigureAwait(false);
+
+            await SaveImage(item, response.Content, response.ContentType, type, imageIndex, cancellationToken)
+                    .ConfigureAwait(false);
+        }
+
+        /// <summary>
+        /// Saves the image.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <param name="source">The source.</param>
+        /// <param name="mimeType">Type of the MIME.</param>
+        /// <param name="type">The type.</param>
+        /// <param name="imageIndex">Index of the image.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        public Task SaveImage(BaseItem item, Stream source, string mimeType, ImageType type, int? imageIndex, CancellationToken cancellationToken)
+        {
+            return new ImageSaver(ConfigurationManager, _directoryWatchers).SaveImage(item, source, mimeType, type, imageIndex, cancellationToken);
+        }
     }
     }
 }
 }