Prechádzať zdrojové kódy

create images list object

Luke Pulverenti 11 rokov pred
rodič
commit
76658f0797

+ 17 - 14
MediaBrowser.Api/DefaultTheme/DefaultThemeService.cs

@@ -115,7 +115,7 @@ namespace MediaBrowser.Api.DefaultTheme
             var itemsWithImages = allFavoriteItems.Where(i => !string.IsNullOrEmpty(i.PrimaryImagePath))
                 .ToList();
 
-            var itemsWithBackdrops = allFavoriteItems.Where(i => i.BackdropImagePaths.Count > 0)
+            var itemsWithBackdrops = allFavoriteItems.Where(i => i.GetImages(ImageType.Backdrop).Any())
                 .ToList();
 
             var view = new FavoritesView();
@@ -227,7 +227,7 @@ namespace MediaBrowser.Api.DefaultTheme
 
             var gamesWithImages = items.OfType<Game>().Where(i => !string.IsNullOrEmpty(i.PrimaryImagePath)).ToList();
 
-            var itemsWithBackdrops = FilterItemsForBackdropDisplay(items.Where(i => i.BackdropImagePaths.Count > 0)).ToList();
+            var itemsWithBackdrops = FilterItemsForBackdropDisplay(items.Where(i => i.GetImages(ImageType.Backdrop).Any())).ToList();
 
             var gamesWithBackdrops = itemsWithBackdrops.OfType<Game>().ToList();
 
@@ -282,7 +282,7 @@ namespace MediaBrowser.Api.DefaultTheme
                 .OfType<Series>()
                 .ToList();
 
-            var seriesWithBackdrops = series.Where(i => i.BackdropImagePaths.Count > 0).ToList();
+            var seriesWithBackdrops = series.Where(i => i.GetImages(ImageType.Backdrop).Any()).ToList();
 
             var view = new TvView();
 
@@ -298,7 +298,7 @@ namespace MediaBrowser.Api.DefaultTheme
                 .ToList();
 
             view.ShowsItems = series
-               .Where(i => i.BackdropImagePaths.Count > 0)
+               .Where(i => i.GetImages(ImageType.Backdrop).Any())
                .Randomize("all")
                .Select(i => GetItemStub(i, ImageType.Backdrop))
                .Where(i => i != null)
@@ -425,7 +425,7 @@ namespace MediaBrowser.Api.DefaultTheme
             view.FamilyMoviePercentage /= movies.Count;
 
             var moviesWithBackdrops = movies
-               .Where(i => i.BackdropImagePaths.Count > 0)
+               .Where(i => i.GetImages(ImageType.Backdrop).Any())
                .ToList();
 
             var fields = new List<ItemFields>();
@@ -456,7 +456,7 @@ namespace MediaBrowser.Api.DefaultTheme
 
             view.BoxSetItems = items
              .OfType<BoxSet>()
-             .Where(i => i.BackdropImagePaths.Count > 0)
+             .Where(i => i.GetImages(ImageType.Backdrop).Any())
              .Randomize()
              .Select(i => GetItemStub(i, ImageType.Backdrop))
              .Where(i => i != null)
@@ -491,7 +491,7 @@ namespace MediaBrowser.Api.DefaultTheme
              .ToList();
 
             view.HDItems = hdMovies
-             .Where(i => i.BackdropImagePaths.Count > 0)
+             .Where(i => i.GetImages(ImageType.Backdrop).Any())
              .Randomize("hd")
              .Select(i => GetItemStub(i, ImageType.Backdrop))
              .Where(i => i != null)
@@ -499,7 +499,7 @@ namespace MediaBrowser.Api.DefaultTheme
              .ToList();
 
             view.FamilyMovies = familyMovies
-             .Where(i => i.BackdropImagePaths.Count > 0)
+             .Where(i => i.GetImages(ImageType.Backdrop).Any())
              .Randomize("family")
              .Select(i => GetItemStub(i, ImageType.Backdrop))
              .Where(i => i != null)
@@ -575,7 +575,7 @@ namespace MediaBrowser.Api.DefaultTheme
         private IEnumerable<BaseItem> FilterItemsForBackdropDisplay(IEnumerable<BaseItem> items)
         {
             var tuples = items
-                .Select(i => new Tuple<BaseItem, double>(i, GetResolution(i, i.BackdropImagePaths[0])))
+                .Select(i => new Tuple<BaseItem, double>(i, GetResolution(i, ImageType.Backdrop, 0)))
                 .Where(i => i.Item2 > 0)
                 .ToList();
 
@@ -591,13 +591,13 @@ namespace MediaBrowser.Api.DefaultTheme
             return tuples.Select(i => i.Item1);
         }
 
-        private double GetResolution(BaseItem item, string path)
+        private double GetResolution(BaseItem item, ImageType type, int index)
         {
             try
             {
-                var date = item.GetImageDateModified(path);
+                var info = item.GetImageInfo(type, index);
 
-                var size = _imageProcessor.GetImageSize(path, date);
+                var size = _imageProcessor.GetImageSize(info.Path, info.DateModified);
 
                 return size.Width;
             }
@@ -618,9 +618,12 @@ namespace MediaBrowser.Api.DefaultTheme
 
             try
             {
-                var imagePath = item.GetImagePath(imageType, 0);
+                var tag = _imageProcessor.GetImageCacheTag(item, imageType);
 
-                stub.ImageTag = _imageProcessor.GetImageCacheTag(item, imageType, imagePath);
+                if (tag.HasValue)
+                {
+                    stub.ImageTag = tag.Value;
+                }
             }
             catch (Exception ex)
             {

+ 38 - 49
MediaBrowser.Api/Images/ImageService.cs

@@ -1,5 +1,6 @@
 using MediaBrowser.Common.Configuration;
 using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.IO;
 using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
@@ -334,12 +335,12 @@ namespace MediaBrowser.Api.Images
         private readonly IItemRepository _itemRepo;
         private readonly IDtoService _dtoService;
         private readonly IImageProcessor _imageProcessor;
-
+        private readonly IFileSystem _fileSystem;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="ImageService" /> class.
         /// </summary>
-        public ImageService(IUserManager userManager, ILibraryManager libraryManager, IApplicationPaths appPaths, IProviderManager providerManager, IItemRepository itemRepo, IDtoService dtoService, IImageProcessor imageProcessor)
+        public ImageService(IUserManager userManager, ILibraryManager libraryManager, IApplicationPaths appPaths, IProviderManager providerManager, IItemRepository itemRepo, IDtoService dtoService, IImageProcessor imageProcessor, IFileSystem fileSystem)
         {
             _userManager = userManager;
             _libraryManager = libraryManager;
@@ -348,6 +349,7 @@ namespace MediaBrowser.Api.Images
             _itemRepo = itemRepo;
             _dtoService = dtoService;
             _imageProcessor = imageProcessor;
+            _fileSystem = fileSystem;
         }
 
         /// <summary>
@@ -395,9 +397,9 @@ namespace MediaBrowser.Api.Images
         {
             var list = new List<ImageInfo>();
 
-            foreach (var image in item.Images)
+            foreach (var image in item.ImageInfos.Where(i => !item.AllowsMultipleImages(i.Type)))
             {
-                var info = GetImageInfo(image.Value, item, null, image.Key);
+                var info = GetImageInfo(item, image, null);
 
                 if (info != null)
                 {
@@ -405,28 +407,16 @@ namespace MediaBrowser.Api.Images
                 }
             }
 
-            var index = 0;
-
-            foreach (var image in item.BackdropImagePaths)
+            foreach (var imageType in item.ImageInfos.Select(i => i.Type).Distinct().Where(item.AllowsMultipleImages))
             {
-                var info = GetImageInfo(image, item, index, ImageType.Backdrop);
-
-                if (info != null)
-                {
-                    list.Add(info);
-                }
+                var index = 0;
 
-                index++;
-            }
+                // Prevent implicitly captured closure
+                var currentImageType = imageType;
 
-            index = 0;
-
-            var hasScreenshots = item as IHasScreenshots;
-            if (hasScreenshots != null)
-            {
-                foreach (var image in hasScreenshots.ScreenshotImagePaths)
+                foreach (var image in item.ImageInfos.Where(i => i.Type == currentImageType))
                 {
-                    var info = GetImageInfo(image, item, index, ImageType.Screenshot);
+                    var info = GetImageInfo(item, image, index);
 
                     if (info != null)
                     {
@@ -441,7 +431,7 @@ namespace MediaBrowser.Api.Images
 
             if (video != null)
             {
-                index = 0;
+                var index = 0;
 
                 foreach (var chapter in _itemRepo.GetChapters(video.Id))
                 {
@@ -449,7 +439,13 @@ namespace MediaBrowser.Api.Images
                     {
                         var image = chapter.ImagePath;
 
-                        var info = GetImageInfo(image, item, index, ImageType.Chapter);
+                        var info = GetImageInfo(item, new ItemImageInfo
+                        {
+                            Path = image,
+                            Type = ImageType.Chapter,
+                            DateModified = _fileSystem.GetLastWriteTimeUtc(image)
+
+                        }, index);
 
                         if (info != null)
                         {
@@ -464,20 +460,20 @@ namespace MediaBrowser.Api.Images
             return list;
         }
 
-        private ImageInfo GetImageInfo(string path, IHasImages item, int? imageIndex, ImageType type)
+        private ImageInfo GetImageInfo(IHasImages item, ItemImageInfo info, int? imageIndex)
         {
             try
             {
-                var fileInfo = new FileInfo(path);
+                var fileInfo = new FileInfo(info.Path);
 
-                var size = _imageProcessor.GetImageSize(path);
+                var size = _imageProcessor.GetImageSize(info.Path);
 
                 return new ImageInfo
                 {
-                    Path = path,
+                    Path = info.Path,
                     ImageIndex = imageIndex,
-                    ImageType = type,
-                    ImageTag = _imageProcessor.GetImageCacheTag(item, type, path),
+                    ImageType = info.Type,
+                    ImageTag = _imageProcessor.GetImageCacheTag(item, info),
                     Size = fileInfo.Length,
                     Width = Convert.ToInt32(size.Width),
                     Height = Convert.ToInt32(size.Height)
@@ -485,7 +481,7 @@ namespace MediaBrowser.Api.Images
             }
             catch (Exception ex)
             {
-                Logger.ErrorException("Error getting image information for {0}", ex, path);
+                Logger.ErrorException("Error getting image information for {0}", ex, info.Path);
 
                 return null;
             }
@@ -584,7 +580,7 @@ namespace MediaBrowser.Api.Images
         {
             var item = _userManager.Users.First(i => i.Id == request.Id);
 
-            var task = item.DeleteImage(request.Type, request.Index);
+            var task = item.DeleteImage(request.Type, request.Index ?? 0);
 
             Task.WaitAll(task);
         }
@@ -597,7 +593,7 @@ namespace MediaBrowser.Api.Images
         {
             var item = _libraryManager.GetItemById(request.Id);
 
-            var task = item.DeleteImage(request.Type, request.Index);
+            var task = item.DeleteImage(request.Type, request.Index ?? 0);
 
             Task.WaitAll(task);
         }
@@ -613,7 +609,7 @@ namespace MediaBrowser.Api.Images
 
             var item = GetItemByName(request.Name, type, _libraryManager);
 
-            var task = item.DeleteImage(request.Type, request.Index);
+            var task = item.DeleteImage(request.Type, request.Index ?? 0);
 
             Task.WaitAll(task);
         }
@@ -671,15 +667,15 @@ namespace MediaBrowser.Api.Images
         /// </exception>
         public object GetImage(ImageRequest request, IHasImages item)
         {
-            var imagePath = GetImagePath(request, item);
+            var imageInfo = GetImageInfo(request, item);
 
-            if (string.IsNullOrEmpty(imagePath))
+            if (imageInfo == null)
             {
                 throw new ResourceNotFoundException(string.Format("{0} does not have an image of type {1}", item.Name, request.Type));
             }
 
             // See if we can avoid a file system lookup by looking for the file in ResolveArgs
-            var originalFileImageDateModified = item.GetImageDateModified(imagePath);
+            var originalFileImageDateModified = imageInfo.DateModified;
 
             var supportedImageEnhancers = request.EnableImageEnhancers ? _imageProcessor.ImageEnhancers.Where(i =>
             {
@@ -696,16 +692,9 @@ namespace MediaBrowser.Api.Images
 
             }).ToList() : new List<IImageEnhancer>();
 
-            // If the file does not exist GetLastWriteTimeUtc will return jan 1, 1601 as opposed to throwing an exception
-            // http://msdn.microsoft.com/en-us/library/system.io.file.getlastwritetimeutc.aspx
-            if (originalFileImageDateModified.Year == 1601 && !File.Exists(imagePath))
-            {
-                throw new ResourceNotFoundException(string.Format("File not found: {0}", imagePath));
-            }
-
-            var contentType = GetMimeType(request.Format, imagePath);
+            var contentType = GetMimeType(request.Format, imageInfo.Path);
 
-            var cacheGuid = _imageProcessor.GetImageCacheTag(item, request.Type, imagePath, originalFileImageDateModified, supportedImageEnhancers);
+            var cacheGuid = _imageProcessor.GetImageCacheTag(item, request.Type, imageInfo.Path, originalFileImageDateModified, supportedImageEnhancers);
 
             TimeSpan? cacheDuration = null;
 
@@ -724,7 +713,7 @@ namespace MediaBrowser.Api.Images
                 Request = currentRequest,
                 OriginalImageDateModified = originalFileImageDateModified,
                 Enhancers = supportedImageEnhancers,
-                OriginalImagePath = imagePath,
+                OriginalImagePath = imageInfo.Path,
                 ImageProcessor = _imageProcessor
 
             }, contentType);
@@ -758,11 +747,11 @@ namespace MediaBrowser.Api.Images
         /// <param name="request">The request.</param>
         /// <param name="item">The item.</param>
         /// <returns>System.String.</returns>
-        private string GetImagePath(ImageRequest request, IHasImages item)
+        private ItemImageInfo GetImageInfo(ImageRequest request, IHasImages item)
         {
             var index = request.Index ?? 0;
 
-            return item.GetImagePath(request.Type, index);
+            return item.GetImageInfo(request.Type, index);
         }
 
         /// <summary>

+ 18 - 6
MediaBrowser.Api/SearchService.cs

@@ -165,9 +165,11 @@ namespace MediaBrowser.Api
                 ProductionYear = item.ProductionYear
             };
 
-            if (item.HasImage(ImageType.Primary))
+            var primaryImageTag = _imageProcessor.GetImageCacheTag(item, ImageType.Primary);
+
+            if (primaryImageTag.HasValue)
             {
-                result.PrimaryImageTag = _imageProcessor.GetImageCacheTag(item, ImageType.Primary, item.GetImagePath(ImageType.Primary));
+                result.PrimaryImageTag = primaryImageTag.Value;
             }
 
             SetThumbImageInfo(result, item);
@@ -241,8 +243,13 @@ namespace MediaBrowser.Api
 
             if (itemWithImage != null)
             {
-                hint.ThumbImageTag = _imageProcessor.GetImageCacheTag(itemWithImage, ImageType.Thumb, itemWithImage.GetImagePath(ImageType.Thumb));
-                hint.ThumbImageItemId = itemWithImage.Id.ToString("N");
+                var tag = _imageProcessor.GetImageCacheTag(itemWithImage, ImageType.Thumb);
+
+                if (tag.HasValue)
+                {
+                    hint.ThumbImageTag = tag.Value;
+                    hint.ThumbImageItemId = itemWithImage.Id.ToString("N");
+                }
             }
         }
 
@@ -257,8 +264,13 @@ namespace MediaBrowser.Api
 
             if (itemWithImage != null)
             {
-                hint.BackdropImageTag = _imageProcessor.GetImageCacheTag(itemWithImage, ImageType.Backdrop, itemWithImage.GetImagePath(ImageType.Backdrop, 0));
-                hint.BackdropImageItemId = itemWithImage.Id.ToString("N");
+                var tag = _imageProcessor.GetImageCacheTag(itemWithImage, ImageType.Backdrop);
+
+                if (tag.HasValue)
+                {
+                    hint.BackdropImageTag = tag.Value;
+                    hint.BackdropImageItemId = itemWithImage.Id.ToString("N");
+                }
             }
         }
 

+ 0 - 15
MediaBrowser.Api/UserLibrary/ItemsService.cs

@@ -1181,21 +1181,6 @@ namespace MediaBrowser.Api.UserLibrary
         /// <returns><c>true</c> if the specified item has image; otherwise, <c>false</c>.</returns>
         internal static bool HasImage(BaseItem item, ImageType imageType)
         {
-            if (imageType == ImageType.Backdrop)
-            {
-                return item.BackdropImagePaths.Count > 0;
-            }
-
-            if (imageType == ImageType.Screenshot)
-            {
-                var hasScreenshots = item as IHasScreenshots;
-                if (hasScreenshots == null)
-                {
-                    return false;
-                }
-                return hasScreenshots.ScreenshotImagePaths.Count > 0;
-            }
-
             return item.HasImage(imageType);
         }
 

+ 22 - 3
MediaBrowser.Controller/Drawing/IImageProcessor.cs

@@ -53,10 +53,9 @@ namespace MediaBrowser.Controller.Drawing
         /// Gets the image cache tag.
         /// </summary>
         /// <param name="item">The item.</param>
-        /// <param name="imageType">Type of the image.</param>
-        /// <param name="imagePath">The image path.</param>
+        /// <param name="image">The image.</param>
         /// <returns>Guid.</returns>
-        Guid GetImageCacheTag(IHasImages item, ImageType imageType, string imagePath);
+        Guid GetImageCacheTag(IHasImages item, ItemImageInfo image);
 
         /// <summary>
         /// Gets the image cache tag.
@@ -87,4 +86,24 @@ namespace MediaBrowser.Controller.Drawing
         /// <returns>Task{System.String}.</returns>
         Task<string> GetEnhancedImage(IHasImages item, ImageType imageType, int imageIndex);
     }
+
+    public static class ImageProcessorExtensions
+    {
+        public static Guid? GetImageCacheTag(this IImageProcessor processor, IHasImages item, ImageType imageType)
+        {
+            return processor.GetImageCacheTag(item, imageType, 0);
+        }
+        
+        public static Guid? GetImageCacheTag(this IImageProcessor processor, IHasImages item, ImageType imageType, int imageIndex)
+        {
+            var imageInfo = item.GetImageInfo(imageType, imageIndex);
+
+            if (imageInfo == null)
+            {
+                return null;
+            }
+
+            return processor.GetImageCacheTag(item, imageInfo);
+        }
+    }
 }

+ 123 - 192
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -28,10 +28,9 @@ namespace MediaBrowser.Controller.Entities
             Genres = new List<string>();
             Studios = new List<string>();
             People = new List<PersonInfo>();
-            BackdropImagePaths = new List<string>();
-            Images = new Dictionary<ImageType, string>();
             ProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
             LockedFields = new List<MetadataFields>();
+            ImageInfos = new List<ItemImageInfo>();
         }
 
         /// <summary>
@@ -48,6 +47,12 @@ namespace MediaBrowser.Controller.Entities
         public const string ThemeVideosFolderName = "backdrops";
         public const string XbmcTrailerFileSuffix = "-trailer";
 
+        public List<ItemImageInfo> ImageInfos { get; set; }
+
+        /// <summary>
+        /// Gets a value indicating whether this instance is in mixed folder.
+        /// </summary>
+        /// <value><c>true</c> if this instance is in mixed folder; otherwise, <c>false</c>.</value>
         public bool IsInMixedFolder { get; set; }
 
         private string _name;
@@ -160,15 +165,8 @@ namespace MediaBrowser.Controller.Entities
         public string PrimaryImagePath
         {
             get { return this.GetImagePath(ImageType.Primary); }
-            set { this.SetImagePath(ImageType.Primary, value); }
         }
 
-        /// <summary>
-        /// Gets or sets the images.
-        /// </summary>
-        /// <value>The images.</value>
-        public Dictionary<ImageType, string> Images { get; set; }
-
         /// <summary>
         /// Gets or sets the date created.
         /// </summary>
@@ -349,12 +347,6 @@ namespace MediaBrowser.Controller.Entities
         /// <value>The display type of the media.</value>
         public string DisplayMediaType { get; set; }
 
-        /// <summary>
-        /// Gets or sets the backdrop image paths.
-        /// </summary>
-        /// <value>The backdrop image paths.</value>
-        public List<string> BackdropImagePaths { get; set; }
-
         /// <summary>
         /// Gets or sets the official rating.
         /// </summary>
@@ -1162,43 +1154,31 @@ namespace MediaBrowser.Controller.Entities
         /// <exception cref="System.ArgumentException">Backdrops should be accessed using Item.Backdrops</exception>
         public bool HasImage(ImageType type, int imageIndex)
         {
-            if (type == ImageType.Backdrop)
-            {
-                return BackdropImagePaths.Count > imageIndex;
-            }
-            if (type == ImageType.Screenshot)
-            {
-                var hasScreenshots = this as IHasScreenshots;
-                return hasScreenshots != null && hasScreenshots.ScreenshotImagePaths.Count > imageIndex;
-            }
-
-            return !string.IsNullOrEmpty(this.GetImagePath(type));
+            return GetImageInfo(type, imageIndex) != null;
         }
 
-        public void SetImagePath(ImageType type, int index, string path)
+        public void SetImagePath(ImageType type, int index, FileInfo file)
         {
-            if (type == ImageType.Backdrop)
+            if (type == ImageType.Chapter)
             {
-                throw new ArgumentException("Backdrops should be accessed using Item.Backdrops");
-            }
-            if (type == ImageType.Screenshot)
-            {
-                throw new ArgumentException("Screenshots should be accessed using Item.Screenshots");
+                throw new ArgumentException("Cannot set chapter images using SetImagePath");
             }
 
-            var typeKey = type;
+            var image = GetImageInfo(type, index);
 
-            // If it's null remove the key from the dictionary
-            if (string.IsNullOrEmpty(path))
+            if (image == null)
             {
-                if (Images.ContainsKey(typeKey))
+                ImageInfos.Add(new ItemImageInfo
                 {
-                    Images.Remove(typeKey);
-                }
+                    Path = file.FullName,
+                    Type = type,
+                    DateModified = FileSystem.GetLastWriteTimeUtc(file)
+                });
             }
             else
             {
-                Images[typeKey] = path;
+                image.Path = file.FullName;
+                image.DateModified = FileSystem.GetLastWriteTimeUtc(file);
             }
         }
 
@@ -1208,66 +1188,23 @@ namespace MediaBrowser.Controller.Entities
         /// <param name="type">The type.</param>
         /// <param name="index">The index.</param>
         /// <returns>Task.</returns>
-        public Task DeleteImage(ImageType type, int? index)
+        public Task DeleteImage(ImageType type, int index)
         {
-            if (type == ImageType.Backdrop)
-            {
-                if (!index.HasValue)
-                {
-                    throw new ArgumentException("Please specify a backdrop image index to delete.");
-                }
-
-                var file = BackdropImagePaths[index.Value];
+            var info = GetImageInfo(type, index);
 
-                BackdropImagePaths.Remove(file);
-
-                // Delete the source file
-                DeleteImagePath(file);
-            }
-            else if (type == ImageType.Screenshot)
+            if (info == null)
             {
-                if (!index.HasValue)
-                {
-                    throw new ArgumentException("Please specify a screenshot image index to delete.");
-                }
-
-                var hasScreenshots = (IHasScreenshots)this;
-                var file = hasScreenshots.ScreenshotImagePaths[index.Value];
-
-                hasScreenshots.ScreenshotImagePaths.Remove(file);
-
-                // Delete the source file
-                DeleteImagePath(file);
-            }
-            else
-            {
-                // Delete the source file
-                DeleteImagePath(this.GetImagePath(type));
-
-                // Remove it from the item
-                this.SetImagePath(type, null);
+                // Nothing to do
+                return Task.FromResult(true);
             }
 
-            // Refresh metadata
-            // Need to disable slow providers or the image might get re-downloaded
-            return RefreshMetadata(new MetadataRefreshOptions
-            {
-                ForceSave = true,
-                ImageRefreshMode = ImageRefreshMode.ValidationOnly,
-                MetadataRefreshMode = MetadataRefreshMode.None
-
-            }, CancellationToken.None);
-        }
+            // Remove it from the item
+            ImageInfos.Remove(info);
 
-        /// <summary>
-        /// Deletes the image path.
-        /// </summary>
-        /// <param name="path">The path.</param>
-        private void DeleteImagePath(string path)
-        {
-            var currentFile = new FileInfo(path);
+            // Delete the source file
+            var currentFile = new FileInfo(info.Path);
 
-            // This will fail if the file is hidden
+            // Deletion will fail if the file is hidden so remove the attribute first
             if (currentFile.Exists)
             {
                 if ((currentFile.Attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
@@ -1277,6 +1214,8 @@ namespace MediaBrowser.Controller.Entities
 
                 currentFile.Delete();
             }
+
+            return LibraryManager.UpdateItem(this, ItemUpdateType.ImageUpdate, CancellationToken.None);
         }
 
         /// <summary>
@@ -1284,132 +1223,110 @@ namespace MediaBrowser.Controller.Entities
         /// </summary>
         public bool ValidateImages()
         {
-            var changed = false;
-
-            // Only validate paths from the same directory - need to copy to a list because we are going to potentially modify the collection below
-            var deletedKeys = Images
-                .Where(image => !File.Exists(image.Value))
-                .Select(i => i.Key)
+            var deletedImages = ImageInfos
+                .Where(image => !File.Exists(image.Path))
                 .ToList();
 
-            // Now remove them from the dictionary
-            foreach (var key in deletedKeys)
+            if (deletedImages.Count > 0)
             {
-                Images.Remove(key);
-                changed = true;
+                ImageInfos = ImageInfos.Except(deletedImages).ToList();
             }
 
-            if (ValidateBackdrops())
-            {
-                changed = true;
-            }
-            if (ValidateScreenshots())
-            {
-                changed = true;
-            }
-
-            return changed;
+            return deletedImages.Count > 0;
         }
 
         /// <summary>
-        /// Validates that backdrops within the item are still on the file system
+        /// Gets the image path.
         /// </summary>
-        private bool ValidateBackdrops()
+        /// <param name="imageType">Type of the image.</param>
+        /// <param name="imageIndex">Index of the image.</param>
+        /// <returns>System.String.</returns>
+        /// <exception cref="System.InvalidOperationException">
+        /// </exception>
+        /// <exception cref="System.ArgumentNullException">item</exception>
+        public string GetImagePath(ImageType imageType, int imageIndex)
         {
-            var changed = false;
-
-            // Only validate paths from the same directory - need to copy to a list because we are going to potentially modify the collection below
-            var deletedImages = BackdropImagePaths
-                .Where(path => !File.Exists(path))
-                .ToList();
-
-            // Now remove them from the dictionary
-            foreach (var path in deletedImages)
-            {
-                BackdropImagePaths.Remove(path);
+            var info = GetImageInfo(imageType, imageIndex);
 
-                changed = true;
-            }
-
-            return changed;
+            return info == null ? null : info.Path;
         }
 
         /// <summary>
-        /// Validates the screenshots.
+        /// Gets the image information.
         /// </summary>
-        private bool ValidateScreenshots()
+        /// <param name="imageType">Type of the image.</param>
+        /// <param name="imageIndex">Index of the image.</param>
+        /// <returns>ItemImageInfo.</returns>
+        public ItemImageInfo GetImageInfo(ImageType imageType, int imageIndex)
         {
-            var changed = false;
+            if (imageType == ImageType.Chapter)
+            {
+                var chapter = ItemRepository.GetChapter(Id, imageIndex);
 
-            var hasScreenshots = this as IHasScreenshots;
+                if (chapter == null)
+                {
+                    return null;
+                }
 
-            if (hasScreenshots == null)
-            {
-                return changed;
-            }
+                var path = chapter.ImagePath;
 
-            // Only validate paths from the same directory - need to copy to a list because we are going to potentially modify the collection below
-            var deletedImages = hasScreenshots.ScreenshotImagePaths
-                .Where(path => !File.Exists(path))
-                .ToList();
+                if (string.IsNullOrWhiteSpace(path))
+                {
+                    return null;
+                }
 
-            // Now remove them from the dictionary
-            foreach (var path in deletedImages)
-            {
-                hasScreenshots.ScreenshotImagePaths.Remove(path);
-                changed = true;
+                return new ItemImageInfo
+                {
+                    Path = path,
+                    DateModified = FileSystem.GetLastWriteTimeUtc(path),
+                    Type = imageType
+                };
             }
 
-            return changed;
+            return GetImages(imageType)
+                .ElementAtOrDefault(imageIndex);
         }
 
-        /// <summary>
-        /// Gets the image path.
-        /// </summary>
-        /// <param name="imageType">Type of the image.</param>
-        /// <param name="imageIndex">Index of the image.</param>
-        /// <returns>System.String.</returns>
-        /// <exception cref="System.InvalidOperationException">
-        /// </exception>
-        /// <exception cref="System.ArgumentNullException">item</exception>
-        public string GetImagePath(ImageType imageType, int imageIndex)
+        public IEnumerable<ItemImageInfo> GetImages(ImageType imageType)
         {
-            if (imageType == ImageType.Backdrop)
-            {
-                return BackdropImagePaths.Count > imageIndex ? BackdropImagePaths[imageIndex] : null;
-            }
-
-            if (imageType == ImageType.Screenshot)
-            {
-                var hasScreenshots = (IHasScreenshots)this;
-                return hasScreenshots.ScreenshotImagePaths.Count > imageIndex ? hasScreenshots.ScreenshotImagePaths[imageIndex] : null;
-            }
-
             if (imageType == ImageType.Chapter)
             {
-                return ItemRepository.GetChapter(Id, imageIndex).ImagePath;
+                throw new ArgumentException("No image info for chapter images");
             }
 
-            string val;
-            Images.TryGetValue(imageType, out val);
-            return val;
+            return ImageInfos.Where(i => i.Type == imageType);
         }
 
         /// <summary>
-        /// Gets the image date modified.
+        /// Adds the images.
         /// </summary>
-        /// <param name="imagePath">The image path.</param>
-        /// <returns>DateTime.</returns>
-        /// <exception cref="System.ArgumentNullException">item</exception>
-        public DateTime GetImageDateModified(string imagePath)
+        /// <param name="imageType">Type of the image.</param>
+        /// <param name="images">The images.</param>
+        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
+        /// <exception cref="System.ArgumentException">Cannot call AddImages with chapter images</exception>
+        public bool AddImages(ImageType imageType, IEnumerable<FileInfo> images)
         {
-            if (string.IsNullOrEmpty(imagePath))
+            if (imageType == ImageType.Chapter)
             {
-                throw new ArgumentNullException("imagePath");
+                throw new ArgumentException("Cannot call AddImages with chapter images");
             }
 
-            // See if we can avoid a file system lookup by looking for the file in ResolveArgs
-            return FileSystem.GetLastWriteTimeUtc(imagePath);
+            var existingImagePaths = GetImages(imageType)
+                .Select(i => i.Path)
+                .ToList();
+
+            var newImages = images
+                .Where(i => !existingImagePaths.Contains(i.FullName, StringComparer.OrdinalIgnoreCase))
+                .ToList();
+
+            ImageInfos.AddRange(newImages.Select(i => new ItemImageInfo
+            {
+                Path = i.FullName,
+                Type = imageType,
+                DateModified = FileSystem.GetLastWriteTimeUtc(i)
+            }));
+
+            return newImages.Count > 0;
         }
 
         /// <summary>
@@ -1421,25 +1338,39 @@ namespace MediaBrowser.Controller.Entities
             return new[] { Path };
         }
 
+        public bool AllowsMultipleImages(ImageType type)
+        {
+            return type == ImageType.Backdrop || type == ImageType.Screenshot || type == ImageType.Chapter;
+        }
+
         public Task SwapImages(ImageType type, int index1, int index2)
         {
-            if (type != ImageType.Screenshot && type != ImageType.Backdrop)
+            if (!AllowsMultipleImages(type))
             {
                 throw new ArgumentException("The change index operation is only applicable to backdrops and screenshots");
             }
 
-            var file1 = GetImagePath(type, index1);
-            var file2 = GetImagePath(type, index2);
+            var info1 = GetImageInfo(type, index1);
+            var info2 = GetImageInfo(type, index2);
 
-            FileSystem.SwapFiles(file1, file2);
-
-            // Directory watchers should repeat this, but do a quick refresh first
-            return RefreshMetadata(new MetadataRefreshOptions
+            if (info1 == null || info2 == null)
             {
-                ForceSave = true,
-                MetadataRefreshMode = MetadataRefreshMode.None
+                // Nothing to do
+                return Task.FromResult(true);
+            }
+
+            var path1 = info1.Path;
+            var path2 = info2.Path;
+
+            FileSystem.SwapFiles(path1, path2);
+
+            info1.Path = path2;
+            info2.Path = path1;
+
+            info1.DateModified = FileSystem.GetLastWriteTimeUtc(info1.Path);
+            info2.DateModified = FileSystem.GetLastWriteTimeUtc(info2.Path);
 
-            }, CancellationToken.None);
+            return LibraryManager.UpdateItem(this, ItemUpdateType.ImageUpdate, CancellationToken.None);
         }
 
         public virtual bool IsPlayed(User user)

+ 0 - 7
MediaBrowser.Controller/Entities/Game.cs

@@ -30,17 +30,10 @@ namespace MediaBrowser.Controller.Entities
             ThemeSongIds = new List<Guid>();
             ThemeVideoIds = new List<Guid>();
             Tags = new List<string>();
-            ScreenshotImagePaths = new List<string>();
         }
 
         public List<Guid> LocalTrailerIds { get; set; }
 
-        /// <summary>
-        /// Gets or sets the screenshot image paths.
-        /// </summary>
-        /// <value>The screenshot image paths.</value>
-        public List<string> ScreenshotImagePaths { get; set; }
-
         /// <summary>
         /// Gets or sets the tags.
         /// </summary>

+ 34 - 16
MediaBrowser.Controller/Entities/IHasImages.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Model.Entities;
+using System.IO;
+using MediaBrowser.Model.Entities;
 using System;
 using System.Collections.Generic;
 using System.Threading.Tasks;
@@ -31,6 +32,13 @@ namespace MediaBrowser.Controller.Entities
         /// <value>The type of the location.</value>
         LocationType LocationType { get; }
 
+        /// <summary>
+        /// Gets the images.
+        /// </summary>
+        /// <param name="imageType">Type of the image.</param>
+        /// <returns>IEnumerable{ItemImageInfo}.</returns>
+        IEnumerable<ItemImageInfo> GetImages(ImageType imageType);
+
         /// <summary>
         /// Gets the image path.
         /// </summary>
@@ -40,19 +48,20 @@ namespace MediaBrowser.Controller.Entities
         string GetImagePath(ImageType imageType, int imageIndex);
 
         /// <summary>
-        /// Gets the image date modified.
+        /// Gets the image information.
         /// </summary>
-        /// <param name="imagePath">The image path.</param>
-        /// <returns>DateTime.</returns>
-        DateTime GetImageDateModified(string imagePath);
+        /// <param name="imageType">Type of the image.</param>
+        /// <param name="imageIndex">Index of the image.</param>
+        /// <returns>ItemImageInfo.</returns>
+        ItemImageInfo GetImageInfo(ImageType imageType, int imageIndex);
 
         /// <summary>
         /// Sets the image.
         /// </summary>
         /// <param name="type">The type.</param>
         /// <param name="index">The index.</param>
-        /// <param name="path">The path.</param>
-        void SetImagePath(ImageType type, int index, string path);
+        /// <param name="file">The file.</param>
+        void SetImagePath(ImageType type, int index, FileInfo file);
 
         /// <summary>
         /// Determines whether the specified type has image.
@@ -62,6 +71,13 @@ namespace MediaBrowser.Controller.Entities
         /// <returns><c>true</c> if the specified type has image; otherwise, <c>false</c>.</returns>
         bool HasImage(ImageType type, int imageIndex);
 
+        /// <summary>
+        /// Allowses the multiple images.
+        /// </summary>
+        /// <param name="type">The type.</param>
+        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
+        bool AllowsMultipleImages(ImageType type);
+
         /// <summary>
         /// Swaps the images.
         /// </summary>
@@ -94,12 +110,6 @@ namespace MediaBrowser.Controller.Entities
         /// </summary>
         bool ValidateImages();
 
-        /// <summary>
-        /// Gets or sets the backdrop image paths.
-        /// </summary>
-        /// <value>The backdrop image paths.</value>
-        List<string> BackdropImagePaths { get; set; }
-
         /// <summary>
         /// Gets a value indicating whether this instance is owned item.
         /// </summary>
@@ -111,6 +121,14 @@ namespace MediaBrowser.Controller.Entities
         /// </summary>
         /// <value>The containing folder path.</value>
         string ContainingFolderPath { get; }
+
+        /// <summary>
+        /// Adds the images.
+        /// </summary>
+        /// <param name="imageType">Type of the image.</param>
+        /// <param name="images">The images.</param>
+        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
+        bool AddImages(ImageType imageType, IEnumerable<FileInfo> images);
     }
 
     public static class HasImagesExtensions
@@ -136,10 +154,10 @@ namespace MediaBrowser.Controller.Entities
         /// </summary>
         /// <param name="item">The item.</param>
         /// <param name="imageType">Type of the image.</param>
-        /// <param name="path">The path.</param>
-        public static void SetImagePath(this IHasImages item, ImageType imageType, string path)
+        /// <param name="file">The file.</param>
+        public static void SetImagePath(this IHasImages item, ImageType imageType, FileInfo file)
         {
-            item.SetImagePath(imageType, 0, path);
+            item.SetImagePath(imageType, 0, file);
         }
     }
 }

+ 1 - 7
MediaBrowser.Controller/Entities/IHasScreenshots.cs

@@ -1,5 +1,4 @@
-using System.Collections.Generic;
-
+
 namespace MediaBrowser.Controller.Entities
 {
     /// <summary>
@@ -7,10 +6,5 @@ namespace MediaBrowser.Controller.Entities
     /// </summary>
     public interface IHasScreenshots
     {
-        /// <summary>
-        /// Gets or sets the screenshot image paths.
-        /// </summary>
-        /// <value>The screenshot image paths.</value>
-        List<string> ScreenshotImagePaths { get; set; }
     }
 }

+ 14 - 0
MediaBrowser.Controller/Entities/ItemImageInfo.cs

@@ -0,0 +1,14 @@
+using MediaBrowser.Model.Entities;
+using System;
+
+namespace MediaBrowser.Controller.Entities
+{
+    public class ItemImageInfo
+    {
+        public string Path { get; set; }
+
+        public ImageType Type { get; set; }
+
+        public DateTime DateModified { get; set; }
+    }
+}

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

@@ -104,6 +104,7 @@
     <Compile Include="Entities\ILibraryItem.cs" />
     <Compile Include="Entities\ImageSourceInfo.cs" />
     <Compile Include="Entities\IMetadataContainer.cs" />
+    <Compile Include="Entities\ItemImageInfo.cs" />
     <Compile Include="Entities\LinkedChild.cs" />
     <Compile Include="Entities\MusicVideo.cs" />
     <Compile Include="Entities\IHasAwards.cs" />

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

@@ -60,7 +60,7 @@ namespace MediaBrowser.Controller.Providers
 
         public void SetFormatFromMimeType(string mimeType)
         {
-            
+
         }
     }
 }

+ 10 - 60
MediaBrowser.Providers/Manager/ImageSaver.cs

@@ -107,14 +107,9 @@ namespace MediaBrowser.Providers.Manager
                 }
             }
 
-            if (type == ImageType.Backdrop && imageIndex == null)
+            if (!imageIndex.HasValue && item.AllowsMultipleImages(type))
             {
-                imageIndex = item.BackdropImagePaths.Count;
-            }
-            else if (type == ImageType.Screenshot && imageIndex == null)
-            {
-                var hasScreenshots = (IHasScreenshots)item;
-                imageIndex = hasScreenshots.ScreenshotImagePaths.Count;
+                imageIndex = item.GetImages(type).Count();
             }
 
             var index = imageIndex ?? 0;
@@ -275,43 +270,7 @@ namespace MediaBrowser.Providers.Manager
         /// imageIndex</exception>
         private void SetImagePath(BaseItem item, ImageType type, int? imageIndex, string path)
         {
-            switch (type)
-            {
-                case ImageType.Screenshot:
-
-                    if (!imageIndex.HasValue)
-                    {
-                        throw new ArgumentNullException("imageIndex");
-                    }
-
-                    var hasScreenshots = (IHasScreenshots)item;
-                    if (hasScreenshots.ScreenshotImagePaths.Count > imageIndex.Value)
-                    {
-                        hasScreenshots.ScreenshotImagePaths[imageIndex.Value] = path;
-                    }
-                    else if (!hasScreenshots.ScreenshotImagePaths.Contains(path, StringComparer.OrdinalIgnoreCase))
-                    {
-                        hasScreenshots.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 if (!item.BackdropImagePaths.Contains(path, StringComparer.OrdinalIgnoreCase))
-                    {
-                        item.BackdropImagePaths.Add(path);
-                    }
-                    break;
-                default:
-                    item.SetImagePath(type, path);
-                    break;
-            }
+            item.SetImagePath(type, imageIndex ?? 0, new FileInfo(path));
         }
 
         /// <summary>
@@ -347,19 +306,10 @@ namespace MediaBrowser.Providers.Manager
                     filename = item is Episode ? Path.GetFileNameWithoutExtension(item.Path) : "folder";
                     break;
                 case ImageType.Backdrop:
-                    if (!imageIndex.HasValue)
-                    {
-                        throw new ArgumentNullException("imageIndex");
-                    }
-                    filename = GetBackdropSaveFilename(item.BackdropImagePaths, "backdrop", "backdrop", imageIndex.Value);
+                    filename = GetBackdropSaveFilename(item.GetImages(type), "backdrop", "backdrop", imageIndex);
                     break;
                 case ImageType.Screenshot:
-                    if (!imageIndex.HasValue)
-                    {
-                        throw new ArgumentNullException("imageIndex");
-                    }
-                    var hasScreenshots = (IHasScreenshots)item;
-                    filename = GetBackdropSaveFilename(hasScreenshots.ScreenshotImagePaths, "screenshot", "screenshot", imageIndex.Value);
+                    filename = GetBackdropSaveFilename(item.GetImages(type), "screenshot", "screenshot", imageIndex);
                     break;
                 default:
                     filename = type.ToString().ToLower();
@@ -404,16 +354,16 @@ namespace MediaBrowser.Providers.Manager
             return path;
         }
 
-        private string GetBackdropSaveFilename(IEnumerable<string> images, string zeroIndexFilename, string numberedIndexPrefix, int index)
+        private string GetBackdropSaveFilename(IEnumerable<ItemImageInfo> images, string zeroIndexFilename, string numberedIndexPrefix, int? index)
         {
-            if (index == 0)
+            if (index.HasValue && index.Value == 0)
             {
                 return zeroIndexFilename;
             }
 
-            var filenames = images.Select(Path.GetFileNameWithoutExtension).ToList();
+            var filenames = images.Select(i => Path.GetFileNameWithoutExtension(i.Path)).ToList();
 
-            var current = index;
+            var current = 1;
             while (filenames.Contains(numberedIndexPrefix + current.ToString(UsCulture), StringComparer.OrdinalIgnoreCase))
             {
                 current++;
@@ -484,7 +434,7 @@ namespace MediaBrowser.Providers.Manager
                     return new[] { GetSavePathForItemInMixedFolder(item, type, "fanart" + outputIndex.ToString(UsCulture), extension) };
                 }
 
-                var extraFanartFilename = GetBackdropSaveFilename(item.BackdropImagePaths, "fanart", "fanart", outputIndex);
+                var extraFanartFilename = GetBackdropSaveFilename(item.GetImages(ImageType.Backdrop), "fanart", "fanart", outputIndex);
 
                 return new[]
                     {

+ 25 - 75
MediaBrowser.Providers/Manager/ItemImageProvider.cs

@@ -178,23 +178,16 @@ namespace MediaBrowser.Providers.Manager
                 return false;
             }
 
-            if (images.Contains(ImageType.Backdrop) && item.BackdropImagePaths.Count < backdropLimit)
+            if (images.Contains(ImageType.Backdrop) && item.GetImages(ImageType.Backdrop).Count() < backdropLimit)
             {
                 return false;
             }
 
-            if (images.Contains(ImageType.Screenshot))
+            if (images.Contains(ImageType.Screenshot) && item.GetImages(ImageType.Screenshot).Count() < backdropLimit)
             {
-                var hasScreenshots = item as IHasScreenshots;
-                if (hasScreenshots != null)
-                {
-                    if (hasScreenshots.ScreenshotImagePaths.Count < screenshotLimit)
-                    {
-                        return false;
-                    }
-                }
-            } 
-            
+                return false;
+            }
+
             return true;
         }
 
@@ -220,7 +213,7 @@ namespace MediaBrowser.Providers.Manager
                 }
 
                 _logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name);
-                
+
                 var images = await provider.GetAllImages(item, cancellationToken).ConfigureAwait(false);
                 var list = images.ToList();
 
@@ -232,12 +225,12 @@ namespace MediaBrowser.Providers.Manager
                     }
                 }
 
-                await DownloadBackdrops(item, backdropLimit, provider, result, list, cancellationToken).ConfigureAwait(false);
+                await DownloadBackdrops(item, ImageType.Backdrop, backdropLimit, provider, result, list, cancellationToken).ConfigureAwait(false);
 
                 var hasScreenshots = item as IHasScreenshots;
                 if (hasScreenshots != null)
                 {
-                    await DownloadScreenshots(hasScreenshots, screenshotLimit, provider, result, list, cancellationToken).ConfigureAwait(false);
+                    await DownloadBackdrops(item, ImageType.Screenshot, screenshotLimit, provider, result, list, cancellationToken).ConfigureAwait(false);
                 }
             }
             catch (OperationCanceledException)
@@ -280,50 +273,42 @@ namespace MediaBrowser.Providers.Manager
 
                 if (image != null)
                 {
-                    var oldPath = item.GetImagePath(type);
+                    var currentImage = item.GetImageInfo(type, 0);
 
-                    item.SetImagePath(type, image.Path);
-
-                    if (!string.Equals(oldPath, image.Path, StringComparison.OrdinalIgnoreCase))
+                    if (currentImage == null || !string.Equals(currentImage.Path, image.Path, StringComparison.OrdinalIgnoreCase))
                     {
+                        item.SetImagePath(type, new FileInfo(image.Path));
                         changed = true;
                     }
                 }
             }
 
-            // The change reporting will only be accurate at the count level
-            // Improve this if/when needed
             var backdrops = images.Where(i => i.Type == ImageType.Backdrop).ToList();
             if (backdrops.Count > 0)
             {
-                var oldCount = item.BackdropImagePaths.Count;
-
-                item.BackdropImagePaths = item.BackdropImagePaths
-                    .Concat(backdrops.Select(i => i.Path))
-                    .Distinct(StringComparer.OrdinalIgnoreCase)
+                var foundImages = images.Where(i => i.Type == ImageType.Backdrop)
+                    .Select(i => new FileInfo(i.Path))
                     .ToList();
 
-                if (oldCount != item.BackdropImagePaths.Count)
+                if (foundImages.Count > 0)
                 {
-                    changed = true;
+                    if (item.AddImages(ImageType.Backdrop, foundImages))
+                    {
+                        changed = true;
+                    }
                 }
             }
 
             var hasScreenshots = item as IHasScreenshots;
             if (hasScreenshots != null)
             {
-                var screenshots = images.Where(i => i.Type == ImageType.Screenshot).ToList();
+                var foundImages = images.Where(i => i.Type == ImageType.Screenshot)
+                    .Select(i => new FileInfo(i.Path))
+                    .ToList();
 
-                if (screenshots.Count > 0)
+                if (foundImages.Count > 0)
                 {
-                    var oldCount = hasScreenshots.ScreenshotImagePaths.Count;
-
-                    hasScreenshots.ScreenshotImagePaths = hasScreenshots.ScreenshotImagePaths
-                        .Concat(screenshots.Select(i => i.Path))
-                        .Distinct(StringComparer.OrdinalIgnoreCase)
-                        .ToList();
-
-                    if (oldCount != hasScreenshots.ScreenshotImagePaths.Count)
+                    if (item.AddImages(ImageType.Screenshot, foundImages))
                     {
                         changed = true;
                     }
@@ -360,46 +345,11 @@ namespace MediaBrowser.Providers.Manager
             }
         }
 
-        private async Task DownloadBackdrops(IHasImages item, int limit, IRemoteImageProvider provider, RefreshResult result, IEnumerable<RemoteImageInfo> images, CancellationToken cancellationToken)
+        private async Task DownloadBackdrops(IHasImages item, ImageType imageType, int limit, IRemoteImageProvider provider, RefreshResult result, IEnumerable<RemoteImageInfo> images, CancellationToken cancellationToken)
         {
-            const ImageType imageType = ImageType.Backdrop;
-
-            foreach (var image in images.Where(i => i.Type == imageType))
-            {
-                if (item.BackdropImagePaths.Count >= limit)
-                {
-                    break;
-                }
-
-                var url = image.Url;
-
-                try
-                {
-                    var response = await provider.GetImageResponse(url, cancellationToken).ConfigureAwait(false);
-
-                    await _providerManager.SaveImage((BaseItem)item, response.Content, response.ContentType, imageType, null, cancellationToken).ConfigureAwait(false);
-                    result.UpdateType = result.UpdateType | ItemUpdateType.ImageUpdate;
-                    break;
-                }
-                catch (HttpException ex)
-                {
-                    // Sometimes providers send back bad url's. Just move onto the next image
-                    if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound)
-                    {
-                        continue;
-                    }
-                    break;
-                }
-            }
-        }
-
-        private async Task DownloadScreenshots(IHasScreenshots item, int limit, IRemoteImageProvider provider, RefreshResult result, IEnumerable<RemoteImageInfo> images, CancellationToken cancellationToken)
-        {
-            const ImageType imageType = ImageType.Screenshot;
-
             foreach (var image in images.Where(i => i.Type == imageType))
             {
-                if (item.ScreenshotImagePaths.Count >= limit)
+                if (item.GetImages(imageType).Count() >= limit)
                 {
                     break;
                 }

+ 27 - 29
MediaBrowser.Providers/Manager/ProviderManager.cs

@@ -389,35 +389,33 @@ namespace MediaBrowser.Providers.Manager
 
         public IEnumerable<MetadataPluginSummary> GetAllMetadataPlugins()
         {
-            var list = new List<MetadataPluginSummary>();
-
-            list.Add(GetPluginSummary<Game>());
-            list.Add(GetPluginSummary<GameSystem>());
-            list.Add(GetPluginSummary<Movie>());
-            list.Add(GetPluginSummary<Trailer>());
-            list.Add(GetPluginSummary<BoxSet>());
-            list.Add(GetPluginSummary<Book>());
-            list.Add(GetPluginSummary<Series>());
-            list.Add(GetPluginSummary<Season>());
-            list.Add(GetPluginSummary<Episode>());
-            list.Add(GetPluginSummary<Person>());
-            list.Add(GetPluginSummary<MusicAlbum>());
-            list.Add(GetPluginSummary<MusicArtist>());
-            list.Add(GetPluginSummary<Audio>());
-
-            list.Add(GetPluginSummary<Genre>());
-            list.Add(GetPluginSummary<Studio>());
-            list.Add(GetPluginSummary<GameGenre>());
-            list.Add(GetPluginSummary<MusicGenre>());
-
-            list.Add(GetPluginSummary<AdultVideo>());
-            list.Add(GetPluginSummary<MusicVideo>());
-            list.Add(GetPluginSummary<Video>());
-
-            list.Add(GetPluginSummary<LiveTvChannel>());
-            list.Add(GetPluginSummary<LiveTvProgram>());
-            list.Add(GetPluginSummary<LiveTvVideoRecording>());
-            list.Add(GetPluginSummary<LiveTvAudioRecording>());
+            var list = new List<MetadataPluginSummary>
+            {
+                GetPluginSummary<Game>(),
+                GetPluginSummary<GameSystem>(),
+                GetPluginSummary<Movie>(),
+                GetPluginSummary<Trailer>(),
+                GetPluginSummary<BoxSet>(),
+                GetPluginSummary<Book>(),
+                GetPluginSummary<Series>(),
+                GetPluginSummary<Season>(),
+                GetPluginSummary<Episode>(),
+                GetPluginSummary<Person>(),
+                GetPluginSummary<MusicAlbum>(),
+                GetPluginSummary<MusicArtist>(),
+                GetPluginSummary<Audio>(),
+                GetPluginSummary<Genre>(),
+                GetPluginSummary<Studio>(),
+                GetPluginSummary<GameGenre>(),
+                GetPluginSummary<MusicGenre>(),
+                GetPluginSummary<AdultVideo>(),
+                GetPluginSummary<MusicVideo>(),
+                GetPluginSummary<Video>(),
+                GetPluginSummary<LiveTvChannel>(),
+                GetPluginSummary<LiveTvProgram>(),
+                GetPluginSummary<LiveTvVideoRecording>(),
+                GetPluginSummary<LiveTvAudioRecording>()
+            };
 
             return list;
         }

+ 9 - 9
MediaBrowser.Providers/TV/EpisodeXmlParser.cs

@@ -66,17 +66,17 @@ namespace MediaBrowser.Providers.TV
 
                         if (!string.IsNullOrWhiteSpace(filename))
                         {
-                            // Strip off everything but the filename. Some metadata tools like MetaBrowser v1.0 will have an 'episodes' prefix
-                            // even though it's actually using the metadata folder.
-                            filename = Path.GetFileName(filename);
+                            //// Strip off everything but the filename. Some metadata tools like MetaBrowser v1.0 will have an 'episodes' prefix
+                            //// even though it's actually using the metadata folder.
+                            //filename = Path.GetFileName(filename);
 
-                            var seasonFolder = Path.GetDirectoryName(item.Path);
-                            filename = Path.Combine(seasonFolder, "metadata", filename);
+                            //var seasonFolder = Path.GetDirectoryName(item.Path);
+                            //filename = Path.Combine(seasonFolder, "metadata", filename);
 
-                            if (File.Exists(filename))
-                            {
-                                item.SetImagePath(ImageType.Primary, 0, filename);
-                            }
+                            //if (File.Exists(filename))
+                            //{
+                            //    item.SetImagePath(ImageType.Primary, 0, filename);
+                            //}
                         }
                         break;
                     }

+ 9 - 11
MediaBrowser.Server.Implementations/Drawing/ImageProcessor.cs

@@ -619,27 +619,24 @@ namespace MediaBrowser.Server.Implementations.Drawing
         /// Gets the image cache tag.
         /// </summary>
         /// <param name="item">The item.</param>
-        /// <param name="imageType">Type of the image.</param>
-        /// <param name="imagePath">The image path.</param>
+        /// <param name="image">The image.</param>
         /// <returns>Guid.</returns>
         /// <exception cref="System.ArgumentNullException">item</exception>
-        public Guid GetImageCacheTag(IHasImages item, ImageType imageType, string imagePath)
+        public Guid GetImageCacheTag(IHasImages item, ItemImageInfo image)
         {
             if (item == null)
             {
                 throw new ArgumentNullException("item");
             }
 
-            if (string.IsNullOrEmpty(imagePath))
+            if (image == null)
             {
-                throw new ArgumentNullException("imagePath");
+                throw new ArgumentNullException("image");
             }
 
-            var dateModified = item.GetImageDateModified(imagePath);
-
-            var supportedEnhancers = GetSupportedEnhancers(item, imageType);
+            var supportedEnhancers = GetSupportedEnhancers(item, image.Type);
 
-            return GetImageCacheTag(item, imageType, imagePath, dateModified, supportedEnhancers.ToList());
+            return GetImageCacheTag(item, image.Type, image.Path, image.DateModified, supportedEnhancers.ToList());
         }
 
         /// <summary>
@@ -693,9 +690,10 @@ namespace MediaBrowser.Server.Implementations.Drawing
         {
             var enhancers = GetSupportedEnhancers(item, imageType).ToList();
 
-            var imagePath = item.GetImagePath(imageType, imageIndex);
+            var imageInfo = item.GetImageInfo(imageType, imageIndex);
+            var imagePath = imageInfo.Path;
 
-            var dateModified = item.GetImageDateModified(imagePath);
+            var dateModified = imageInfo.DateModified;
 
             var result = await GetEnhancedImage(imagePath, dateModified, item, imageType, imageIndex, enhancers);
 

+ 59 - 69
MediaBrowser.Server.Implementations/Dto/DtoService.cs

@@ -1,4 +1,5 @@
 using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.IO;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Dto;
@@ -34,8 +35,9 @@ namespace MediaBrowser.Server.Implementations.Dto
 
         private readonly IImageProcessor _imageProcessor;
         private readonly IServerConfigurationManager _config;
+        private readonly IFileSystem _fileSystem;
 
-        public DtoService(ILogger logger, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataRepository, IItemRepository itemRepo, IImageProcessor imageProcessor, IServerConfigurationManager config)
+        public DtoService(ILogger logger, ILibraryManager libraryManager, IUserManager userManager, IUserDataManager userDataRepository, IItemRepository itemRepo, IImageProcessor imageProcessor, IServerConfigurationManager config, IFileSystem fileSystem)
         {
             _logger = logger;
             _libraryManager = libraryManager;
@@ -44,6 +46,7 @@ namespace MediaBrowser.Server.Implementations.Dto
             _itemRepo = itemRepo;
             _imageProcessor = imageProcessor;
             _config = config;
+            _fileSystem = fileSystem;
         }
 
         /// <summary>
@@ -207,11 +210,11 @@ namespace MediaBrowser.Server.Implementations.Dto
                 Configuration = user.Configuration
             };
 
-            var image = user.PrimaryImagePath;
+            var image = user.GetImageInfo(ImageType.Primary, 0);
 
-            if (!string.IsNullOrEmpty(image))
+            if (image != null)
             {
-                dto.PrimaryImageTag = GetImageCacheTag(user, ImageType.Primary, image);
+                dto.PrimaryImageTag = GetImageCacheTag(user, image);
 
                 try
                 {
@@ -288,12 +291,7 @@ namespace MediaBrowser.Server.Implementations.Dto
                 RunTimeTicks = item.RunTimeTicks
             };
 
-            var imagePath = item.PrimaryImagePath;
-
-            if (!string.IsNullOrEmpty(imagePath))
-            {
-                info.PrimaryImageTag = GetImageCacheTag(item, ImageType.Primary, imagePath);
-            }
+            info.PrimaryImageTag = GetImageCacheTag(item, ImageType.Primary);
 
             return info;
         }
@@ -380,11 +378,7 @@ namespace MediaBrowser.Server.Implementations.Dto
         /// <returns>List{System.String}.</returns>
         private List<Guid> GetBackdropImageTags(BaseItem item)
         {
-            return item.BackdropImagePaths
-                .Select(p => GetImageCacheTag(item, ImageType.Backdrop, p))
-                .Where(i => i.HasValue)
-                .Select(i => i.Value)
-                .ToList();
+            return GetCacheTags(item, ImageType.Backdrop).ToList();
         }
 
         /// <summary>
@@ -399,23 +393,40 @@ namespace MediaBrowser.Server.Implementations.Dto
             {
                 return new List<Guid>();
             }
+            return GetCacheTags(item, ImageType.Screenshot).ToList();
+        }
 
-            return hasScreenshots.ScreenshotImagePaths
-                .Select(p => GetImageCacheTag(item, ImageType.Screenshot, p))
+        private IEnumerable<Guid> GetCacheTags(BaseItem item, ImageType type)
+        {
+            return item.GetImages(type)
+                .Select(p => GetImageCacheTag(item, p))
                 .Where(i => i.HasValue)
                 .Select(i => i.Value)
                 .ToList();
         }
 
-        private Guid? GetImageCacheTag(BaseItem item, ImageType type, string path)
+        private Guid? GetImageCacheTag(BaseItem item, ImageType type)
+        {
+            try
+            {
+                return _imageProcessor.GetImageCacheTag(item, type);
+            }
+            catch (IOException ex)
+            {
+                _logger.ErrorException("Error getting {0} image info", ex, type);
+                return null;
+            }
+        }
+
+        private Guid? GetImageCacheTag(BaseItem item, ItemImageInfo image)
         {
             try
             {
-                return _imageProcessor.GetImageCacheTag(item, type, path);
+                return _imageProcessor.GetImageCacheTag(item, image);
             }
             catch (IOException ex)
             {
-                _logger.ErrorException("Error getting {0} image info for {1}", ex, type, path);
+                _logger.ErrorException("Error getting {0} image info for {1}", ex, image.Type, image.Path);
                 return null;
             }
         }
@@ -468,12 +479,7 @@ namespace MediaBrowser.Server.Implementations.Dto
 
                 if (dictionary.TryGetValue(person.Name, out entity))
                 {
-                    var primaryImagePath = entity.PrimaryImagePath;
-
-                    if (!string.IsNullOrEmpty(primaryImagePath))
-                    {
-                        baseItemPerson.PrimaryImageTag = GetImageCacheTag(entity, ImageType.Primary, primaryImagePath);
-                    }
+                    baseItemPerson.PrimaryImageTag = GetImageCacheTag(entity, ImageType.Primary);
                 }
 
                 dto.People[i] = baseItemPerson;
@@ -520,12 +526,7 @@ namespace MediaBrowser.Server.Implementations.Dto
 
                 if (dictionary.TryGetValue(studio, out entity))
                 {
-                    var primaryImagePath = entity.PrimaryImagePath;
-
-                    if (!string.IsNullOrEmpty(primaryImagePath))
-                    {
-                        studioDto.PrimaryImageTag = GetImageCacheTag(entity, ImageType.Primary, primaryImagePath);
-                    }
+                    studioDto.PrimaryImageTag = GetImageCacheTag(entity, ImageType.Primary);
                 }
 
                 dto.Studios[i] = studioDto;
@@ -544,7 +545,7 @@ namespace MediaBrowser.Server.Implementations.Dto
 
             while (parent != null)
             {
-                if (parent.BackdropImagePaths != null && parent.BackdropImagePaths.Count > 0)
+                if (parent.GetImages(ImageType.Backdrop).Any())
                 {
                     return parent;
                 }
@@ -595,7 +596,12 @@ namespace MediaBrowser.Server.Implementations.Dto
 
             if (!string.IsNullOrEmpty(chapterInfo.ImagePath))
             {
-                dto.ImageTag = GetImageCacheTag(item, ImageType.Chapter, chapterInfo.ImagePath);
+                dto.ImageTag = GetImageCacheTag(item, new ItemImageInfo
+                {
+                    Path = chapterInfo.ImagePath,
+                    Type = ImageType.Chapter,
+                    DateModified = _fileSystem.GetLastWriteTimeUtc(chapterInfo.ImagePath)
+                });
             }
 
             return dto;
@@ -698,7 +704,7 @@ namespace MediaBrowser.Server.Implementations.Dto
 
             if (fields.Contains(ItemFields.Keywords))
             {
-                var hasTags = item as  IHasKeywords;
+                var hasTags = item as IHasKeywords;
                 if (hasTags != null)
                 {
                     dto.Keywords = hasTags.Keywords;
@@ -750,15 +756,15 @@ namespace MediaBrowser.Server.Implementations.Dto
 
             dto.ImageTags = new Dictionary<ImageType, Guid>();
 
-            foreach (var image in item.Images)
+            // Prevent implicitly captured closure
+            var currentItem = item;
+            foreach (var image in currentItem.ImageInfos.Where(i => !currentItem.AllowsMultipleImages(i.Type)))
             {
-                var type = image.Key;
-
-                var tag = GetImageCacheTag(item, type, image.Value);
+                var tag = GetImageCacheTag(item, image);
 
                 if (tag.HasValue)
                 {
-                    dto.ImageTags[type] = tag.Value;
+                    dto.ImageTags[image.Type] = tag.Value;
                 }
             }
 
@@ -804,7 +810,7 @@ namespace MediaBrowser.Server.Implementations.Dto
             {
                 dto.CollectionType = collectionFolder.CollectionType;
             }
-            
+
             if (fields.Contains(ItemFields.RemoteTrailers))
             {
                 dto.RemoteTrailers = hasTrailers != null ?
@@ -862,7 +868,7 @@ namespace MediaBrowser.Server.Implementations.Dto
                 {
                     dto.ParentLogoItemId = GetDtoId(parentWithLogo);
 
-                    dto.ParentLogoImageTag = GetImageCacheTag(parentWithLogo, ImageType.Logo, parentWithLogo.GetImagePath(ImageType.Logo));
+                    dto.ParentLogoImageTag = GetImageCacheTag(parentWithLogo, ImageType.Logo);
                 }
             }
 
@@ -875,7 +881,7 @@ namespace MediaBrowser.Server.Implementations.Dto
                 {
                     dto.ParentArtItemId = GetDtoId(parentWithImage);
 
-                    dto.ParentArtImageTag = GetImageCacheTag(parentWithImage, ImageType.Art, parentWithImage.GetImagePath(ImageType.Art));
+                    dto.ParentArtImageTag = GetImageCacheTag(parentWithImage, ImageType.Art);
                 }
             }
 
@@ -888,7 +894,7 @@ namespace MediaBrowser.Server.Implementations.Dto
                 {
                     dto.ParentThumbItemId = GetDtoId(parentWithImage);
 
-                    dto.ParentThumbImageTag = GetImageCacheTag(parentWithImage, ImageType.Thumb, parentWithImage.GetImagePath(ImageType.Thumb));
+                    dto.ParentThumbImageTag = GetImageCacheTag(parentWithImage, ImageType.Thumb);
                 }
             }
 
@@ -959,12 +965,7 @@ namespace MediaBrowser.Server.Implementations.Dto
                 {
                     dto.AlbumId = GetDtoId(albumParent);
 
-                    var imagePath = albumParent.PrimaryImagePath;
-
-                    if (!string.IsNullOrEmpty(imagePath))
-                    {
-                        dto.AlbumPrimaryImageTag = GetImageCacheTag(albumParent, ImageType.Primary, imagePath);
-                    }
+                    dto.AlbumPrimaryImageTag = GetImageCacheTag(albumParent, ImageType.Primary);
                 }
             }
 
@@ -1085,17 +1086,9 @@ namespace MediaBrowser.Server.Implementations.Dto
                 dto.AirTime = series.AirTime;
                 dto.SeriesStudio = series.Studios.FirstOrDefault();
 
-                if (series.HasImage(ImageType.Thumb))
-                {
-                    dto.SeriesThumbImageTag = GetImageCacheTag(series, ImageType.Thumb, series.GetImagePath(ImageType.Thumb));
-                }
-
-                var imagePath = series.PrimaryImagePath;
+                dto.SeriesThumbImageTag = GetImageCacheTag(series, ImageType.Thumb);
 
-                if (!string.IsNullOrEmpty(imagePath))
-                {
-                    dto.SeriesPrimaryImageTag = GetImageCacheTag(series, ImageType.Primary, imagePath);
-                }
+                dto.SeriesPrimaryImageTag = GetImageCacheTag(series, ImageType.Primary);
             }
 
             // Add SeasonInfo
@@ -1110,12 +1103,7 @@ namespace MediaBrowser.Server.Implementations.Dto
                 dto.AirTime = series.AirTime;
                 dto.SeriesStudio = series.Studios.FirstOrDefault();
 
-                var imagePath = series.PrimaryImagePath;
-
-                if (!string.IsNullOrEmpty(imagePath))
-                {
-                    dto.SeriesPrimaryImageTag = GetImageCacheTag(series, ImageType.Primary, imagePath);
-                }
+                dto.SeriesPrimaryImageTag = GetImageCacheTag(series, ImageType.Primary);
             }
 
             var game = item as Game;
@@ -1303,15 +1291,17 @@ namespace MediaBrowser.Server.Implementations.Dto
         /// <returns>Task.</returns>
         public void AttachPrimaryImageAspectRatio(IItemDto dto, IHasImages item)
         {
-            var path = item.PrimaryImagePath;
+            var imageInfo = item.GetImageInfo(ImageType.Primary, 0);
 
-            if (string.IsNullOrEmpty(path))
+            if (imageInfo == null)
             {
                 return;
             }
 
+            var path = imageInfo.Path;
+
             // See if we can avoid a file system lookup by looking for the file in ResolveArgs
-            var dateModified = item.GetImageDateModified(path);
+            var dateModified = imageInfo.DateModified;
 
             ImageSize size;
 

+ 1 - 8
MediaBrowser.Server.Implementations/LiveTv/LiveTvDtoService.cs

@@ -401,16 +401,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
         private Guid? GetImageTag(IHasImages info)
         {
-            var path = info.PrimaryImagePath;
-
-            if (string.IsNullOrEmpty(path))
-            {
-                return null;
-            }
-
             try
             {
-                return _imageProcessor.GetImageCacheTag(info, ImageType.Primary, path);
+                return _imageProcessor.GetImageCacheTag(info, ImageType.Primary);
             }
             catch (Exception ex)
             {

+ 1 - 1
MediaBrowser.ServerApplication/ApplicationHost.cs

@@ -309,7 +309,7 @@ namespace MediaBrowser.ServerApplication
             ImageProcessor = new ImageProcessor(Logger, ServerConfigurationManager.ApplicationPaths, FileSystemManager, JsonSerializer);
             RegisterSingleInstance(ImageProcessor);
 
-            DtoService = new DtoService(Logger, LibraryManager, UserManager, UserDataManager, ItemRepository, ImageProcessor, ServerConfigurationManager);
+            DtoService = new DtoService(Logger, LibraryManager, UserManager, UserDataManager, ItemRepository, ImageProcessor, ServerConfigurationManager, FileSystemManager);
             RegisterSingleInstance(DtoService);
 
             var newsService = new Server.Implementations.News.NewsService(ApplicationPaths, JsonSerializer);

+ 2 - 2
MediaBrowser.ServerApplication/LibraryExplorer.xaml.cs

@@ -258,8 +258,8 @@ namespace MediaBrowser.ServerApplication
                                            previews.Add(new PreviewItem(item.GetImagePath(ImageType.Thumb), "Thumb"));
                                        }
                                        previews.AddRange(
-                                           item.BackdropImagePaths.Select(
-                                               image => new PreviewItem(image, "Backdrop")));
+                                           item.GetImages(ImageType.Backdrop).Select(
+                                               image => new PreviewItem(image.Path, "Backdrop")));
                                    });
                 lstPreviews.ItemsSource = previews;
                 lstPreviews.Items.Refresh();