Browse Source

converted movie providers to new system

Luke Pulverenti 11 years ago
parent
commit
821a3d29a2
94 changed files with 1671 additions and 5363 deletions
  1. 1 9
      MediaBrowser.Api/Library/LibraryService.cs
  2. 2 15
      MediaBrowser.Api/LibraryService.cs
  3. 4 0
      MediaBrowser.Controller/Entities/AdultVideo.cs
  4. 64 1
      MediaBrowser.Controller/Entities/AggregateFolder.cs
  5. 159 399
      MediaBrowser.Controller/Entities/BaseItem.cs
  6. 0 19
      MediaBrowser.Controller/Entities/Book.cs
  7. 64 2
      MediaBrowser.Controller/Entities/CollectionFolder.cs
  8. 15 48
      MediaBrowser.Controller/Entities/Folder.cs
  9. 0 22
      MediaBrowser.Controller/Entities/Game.cs
  10. 6 0
      MediaBrowser.Controller/Entities/IHasImages.cs
  11. 20 61
      MediaBrowser.Controller/Entities/Movies/Movie.cs
  12. 0 22
      MediaBrowser.Controller/Entities/TV/Episode.cs
  13. 0 28
      MediaBrowser.Controller/Entities/TV/Season.cs
  14. 0 14
      MediaBrowser.Controller/Entities/TV/Series.cs
  15. 0 27
      MediaBrowser.Controller/Entities/Trailer.cs
  16. 31 75
      MediaBrowser.Controller/Entities/Video.cs
  17. 0 91
      MediaBrowser.Controller/Library/ItemResolveArgs.cs
  18. 1 1
      MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs
  19. 2 6
      MediaBrowser.Controller/Providers/BaseItemXmlParser.cs
  20. 0 189
      MediaBrowser.Controller/Providers/BaseMetadataProvider.cs
  21. 6 0
      MediaBrowser.Controller/Providers/IHasMetadata.cs
  22. 9 8
      MediaBrowser.Controller/Providers/ILocalMetadataProvider.cs
  23. 1 11
      MediaBrowser.Controller/Providers/IProviderManager.cs
  24. 0 11
      MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs
  25. 0 6
      MediaBrowser.Model/Dto/BaseItemDto.cs
  26. 0 5
      MediaBrowser.Model/Querying/ItemFields.cs
  27. 5 33
      MediaBrowser.Providers/AdultVideos/AdultVideoXmlProvider.cs
  28. 1 1
      MediaBrowser.Providers/All/LocalImageProvider.cs
  29. 56 7
      MediaBrowser.Providers/BaseXmlProvider.cs
  30. 5 34
      MediaBrowser.Providers/BoxSets/BoxSetXmlProvider.cs
  31. 0 56
      MediaBrowser.Providers/CollectionFolderImageProvider.cs
  32. 0 93
      MediaBrowser.Providers/FolderProviderFromXml.cs
  33. 52 0
      MediaBrowser.Providers/Folders/FolderMetadataService.cs
  34. 33 0
      MediaBrowser.Providers/Folders/FolderXmlProvider.cs
  35. 1 1
      MediaBrowser.Providers/Folders/UserRootFolderNameProvider.cs
  36. 5 34
      MediaBrowser.Providers/Games/GameSystemXmlProvider.cs
  37. 8 37
      MediaBrowser.Providers/Games/GameXmlProvider.cs
  38. 0 637
      MediaBrowser.Providers/ImageFromMediaLocationProvider.cs
  39. 0 100
      MediaBrowser.Providers/ImagesByNameProvider.cs
  40. 5 34
      MediaBrowser.Providers/LiveTv/ChannelXmlProvider.cs
  41. 13 8
      MediaBrowser.Providers/Manager/ImageSaver.cs
  42. 2 2
      MediaBrowser.Providers/Manager/ItemImageProvider.cs
  43. 11 5
      MediaBrowser.Providers/Manager/MetadataService.cs
  44. 15 181
      MediaBrowser.Providers/Manager/ProviderManager.cs
  45. 15 15
      MediaBrowser.Providers/MediaBrowser.Providers.csproj
  46. 40 167
      MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs
  47. 0 145
      MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs
  48. 24 21
      MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs
  49. 43 5
      MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs
  50. 63 63
      MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs
  51. 0 686
      MediaBrowser.Providers/MediaInfo/FFProbeVideoInfoProvider.cs
  52. 27 30
      MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
  53. 0 356
      MediaBrowser.Providers/Movies/FanArtMovieProvider.cs
  54. 2 2
      MediaBrowser.Providers/Movies/FanArtMovieUpdatesPrescanTask.cs
  55. 94 7
      MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs
  56. 245 0
      MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs
  57. 12 4
      MediaBrowser.Providers/Movies/MovieDbImageProvider.cs
  58. 0 247
      MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs
  59. 53 457
      MediaBrowser.Providers/Movies/MovieDbProvider.cs
  60. 43 0
      MediaBrowser.Providers/Movies/MovieMetadataService.cs
  61. 0 112
      MediaBrowser.Providers/Movies/MovieProviderFromXml.cs
  62. 10 38
      MediaBrowser.Providers/Movies/MovieXmlProvider.cs
  63. 0 285
      MediaBrowser.Providers/Movies/OpenMovieDatabaseProvider.cs
  64. 17 0
      MediaBrowser.Providers/Movies/TmdbSettings.cs
  65. 43 0
      MediaBrowser.Providers/Movies/TrailerMetadataService.cs
  66. 30 0
      MediaBrowser.Providers/Movies/TrailerXmlProvider.cs
  67. 5 34
      MediaBrowser.Providers/Music/AlbumXmlProvider.cs
  68. 5 34
      MediaBrowser.Providers/Music/ArtistXmlProvider.cs
  69. 53 0
      MediaBrowser.Providers/Music/AudioMetadataService.cs
  70. 5 34
      MediaBrowser.Providers/Music/MusicVideoXmlProvider.cs
  71. 5 34
      MediaBrowser.Providers/People/PersonXmlProvider.cs
  72. 84 1
      MediaBrowser.Providers/ProviderUtils.cs
  73. 1 7
      MediaBrowser.Providers/RefreshIntrosTask.cs
  74. 1 1
      MediaBrowser.Providers/Savers/GameXmlSaver.cs
  75. 2 9
      MediaBrowser.Providers/Savers/MovieXmlSaver.cs
  76. 6 3
      MediaBrowser.Providers/TV/EpisodeMetadataService.cs
  77. 6 34
      MediaBrowser.Providers/TV/EpisodeXmlProvider.cs
  78. 5 34
      MediaBrowser.Providers/TV/SeasonXmlProvider.cs
  79. 11 4
      MediaBrowser.Providers/TV/SeriesPostScanTask.cs
  80. 5 34
      MediaBrowser.Providers/TV/SeriesXmlProvider.cs
  81. 8 1
      MediaBrowser.Providers/TV/TvdbEpisodeProvider.cs
  82. 52 0
      MediaBrowser.Providers/Videos/VideoMetadataService.cs
  83. 0 57
      MediaBrowser.Providers/VirtualItemImageValidator.cs
  84. 43 0
      MediaBrowser.Providers/Years/YearMetadataService.cs
  85. 2 15
      MediaBrowser.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
  86. 1 12
      MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs
  87. 4 14
      MediaBrowser.Server.Implementations/Library/LibraryManager.cs
  88. 0 2
      MediaBrowser.Server.Implementations/Library/ResolverHelper.cs
  89. 1 1
      MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
  90. 0 12
      MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs
  91. 0 2
      MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeriesResolver.cs
  92. 3 9
      MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs
  93. 2 2
      MediaBrowser.ServerApplication/ApplicationHost.cs
  94. 3 0
      MediaBrowser.sln

+ 1 - 9
MediaBrowser.Api/Library/LibraryService.cs

@@ -75,15 +75,7 @@ namespace MediaBrowser.Api.Library
 
                     if (locationType != LocationType.Remote && locationType != LocationType.Virtual)
                     {
-                        try
-                        {
-                            return c.PhysicalLocations;
-                        }
-                        catch (Exception ex)
-                        {
-                            Logger.ErrorException("Error getting ResolveArgs for {0}", ex, c.Path);
-                        }
-
+                        return c.PhysicalLocations;
                     }
 
                     return new List<string>();

+ 2 - 15
MediaBrowser.Api/LibraryService.cs

@@ -331,21 +331,8 @@ namespace MediaBrowser.Api
         {
             if (item.Parent is AggregateFolder)
             {
-                return user.RootFolder.GetChildren(user, true).FirstOrDefault(i =>
-                {
-
-                    try
-                    {
-                        return i.LocationType == LocationType.FileSystem &&
-                               i.PhysicalLocations.Contains(item.Path);
-                    }
-                    catch (Exception ex)
-                    {
-                        Logger.ErrorException("Error getting ResolveArgs for {0}", ex, i.Path);
-                        return false;
-                    }
-
-                });
+                return user.RootFolder.GetChildren(user, true).FirstOrDefault(i => i.LocationType == LocationType.FileSystem &&
+                                                                                   i.PhysicalLocations.Contains(item.Path));
             }
 
             return item;

+ 4 - 0
MediaBrowser.Controller/Entities/AdultVideo.cs

@@ -3,6 +3,10 @@ namespace MediaBrowser.Controller.Entities
 {
     public class AdultVideo : Video, IHasPreferredMetadataLanguage
     {
+        /// <summary>
+        /// Gets or sets the preferred metadata language.
+        /// </summary>
+        /// <value>The preferred metadata language.</value>
         public string PreferredMetadataLanguage { get; set; }
 
         /// <summary>

+ 64 - 1
MediaBrowser.Controller/Entities/AggregateFolder.cs

@@ -1,7 +1,11 @@
-using System;
+using MediaBrowser.Controller.IO;
+using MediaBrowser.Controller.Library;
+using System;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
+using System.IO;
 using System.Linq;
+using System.Runtime.Serialization;
 
 namespace MediaBrowser.Controller.Entities
 {
@@ -11,6 +15,11 @@ namespace MediaBrowser.Controller.Entities
     /// </summary>
     public class AggregateFolder : Folder
     {
+        public AggregateFolder()
+        {
+            PhysicalLocationsList = new List<string>();
+        }
+
         /// <summary>
         /// We don't support manual shortcuts
         /// </summary>
@@ -36,6 +45,60 @@ namespace MediaBrowser.Controller.Entities
             get { return _virtualChildren; }
         }
 
+        [IgnoreDataMember]
+        public override IEnumerable<string> PhysicalLocations
+        {
+            get
+            {
+                return PhysicalLocationsList;
+            }
+        }
+
+        public List<string> PhysicalLocationsList { get; set; }
+
+        protected override IEnumerable<FileSystemInfo> GetFileSystemChildren()
+        {
+            return CreateResolveArgs().FileSystemChildren;
+        }
+
+        private ItemResolveArgs CreateResolveArgs()
+        {
+            var path = ContainingFolderPath;
+
+            var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, LibraryManager)
+            {
+                FileInfo = new DirectoryInfo(path),
+                Path = path,
+                Parent = Parent
+            };
+
+            // Gather child folder and files
+            if (args.IsDirectory)
+            {
+                var isPhysicalRoot = args.IsPhysicalRoot;
+
+                // When resolving the root, we need it's grandchildren (children of user views)
+                var flattenFolderDepth = isPhysicalRoot ? 2 : 0;
+
+                var fileSystemDictionary = FileData.GetFilteredFileSystemEntries(args.Path, FileSystem, Logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf);
+
+                // Need to remove subpaths that may have been resolved from shortcuts
+                // Example: if \\server\movies exists, then strip out \\server\movies\action
+                if (isPhysicalRoot)
+                {
+                    var paths = LibraryManager.NormalizeRootPathList(fileSystemDictionary.Keys);
+
+                    fileSystemDictionary = paths.Select(i => (FileSystemInfo)new DirectoryInfo(i)).ToDictionary(i => i.FullName);
+                }
+
+                args.FileSystemDictionary = fileSystemDictionary;
+            }
+
+            PhysicalLocationsList = args.PhysicalLocations.ToList();
+
+            return args;
+        }
+        
         /// <summary>
         /// Adds the virtual child.
         /// </summary>

+ 159 - 399
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -1,12 +1,10 @@
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.IO;
 using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Localization;
 using MediaBrowser.Controller.Persistence;
 using MediaBrowser.Controller.Providers;
-using MediaBrowser.Controller.Resolvers;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
@@ -102,6 +100,35 @@ namespace MediaBrowser.Controller.Entities
         [IgnoreDataMember]
         protected internal bool IsOffline { get; set; }
 
+        /// <summary>
+        /// Returns the folder containing the item.
+        /// If the item is a folder, it returns the folder itself
+        /// </summary>
+        [IgnoreDataMember]
+        public virtual string ContainingFolderPath
+        {
+            get
+            {
+                if (IsFolder)
+                {
+                    return Path;
+                }
+
+                return System.IO.Path.GetDirectoryName(Path);
+            }
+        }
+
+        [IgnoreDataMember]
+        public bool IsOwnedItem
+        {
+            get
+            {
+                // Local trailer, special feature, theme video, etc.
+                // An item that belongs to another item but is not part of the Parent-Child tree
+                return !IsFolder && Parent == null;
+            }
+        }
+
         /// <summary>
         /// Gets or sets the type of the location.
         /// </summary>
@@ -189,19 +216,6 @@ namespace MediaBrowser.Controller.Entities
         /// <value>The locked fields.</value>
         public List<MetadataFields> LockedFields { get; set; }
 
-        /// <summary>
-        /// Should be overridden to return the proper folder where metadata lives
-        /// </summary>
-        /// <value>The meta location.</value>
-        [IgnoreDataMember]
-        public virtual string MetaLocation
-        {
-            get
-            {
-                return Path ?? "";
-            }
-        }
-
         /// <summary>
         /// Gets the type of the media.
         /// </summary>
@@ -215,160 +229,19 @@ namespace MediaBrowser.Controller.Entities
             }
         }
 
-        /// <summary>
-        /// The _resolve args
-        /// </summary>
-        private ItemResolveArgs _resolveArgs;
-        /// <summary>
-        /// We attach these to the item so that we only ever have to hit the file system once
-        /// (this includes the children of the containing folder)
-        /// </summary>
-        /// <value>The resolve args.</value>
         [IgnoreDataMember]
-        public ItemResolveArgs ResolveArgs
+        public virtual IEnumerable<string> PhysicalLocations
         {
             get
             {
-                if (_resolveArgs == null)
-                {
-                    try
-                    {
-                        _resolveArgs = CreateResolveArgs();
-                    }
-                    catch (IOException ex)
-                    {
-                        Logger.ErrorException("Error creating resolve args for {0}", ex, Path);
-
-                        IsOffline = true;
+                var locationType = LocationType;
 
-                        throw;
-                    }
-                }
-
-                return _resolveArgs;
-            }
-            set
-            {
-                _resolveArgs = value;
-            }
-        }
-
-        [IgnoreDataMember]
-        public IEnumerable<string> PhysicalLocations
-        {
-            get
-            {
-                return ResolveArgs.PhysicalLocations;
-            }
-        }
-
-        /// <summary>
-        /// Resets the resolve args.
-        /// </summary>
-        /// <param name="pathInfo">The path info.</param>
-        public void ResetResolveArgs(FileSystemInfo pathInfo)
-        {
-            ResetResolveArgs(CreateResolveArgs(pathInfo));
-        }
-
-        /// <summary>
-        /// Resets the resolve args.
-        /// </summary>
-        public void ResetResolveArgs()
-        {
-            _resolveArgs = null;
-        }
-
-        /// <summary>
-        /// Resets the resolve args.
-        /// </summary>
-        /// <param name="args">The args.</param>
-        public void ResetResolveArgs(ItemResolveArgs args)
-        {
-            _resolveArgs = args;
-        }
-
-        /// <summary>
-        /// Creates ResolveArgs on demand
-        /// </summary>
-        /// <param name="pathInfo">The path info.</param>
-        /// <returns>ItemResolveArgs.</returns>
-        /// <exception cref="System.IO.IOException">Unable to retrieve file system info for  + path</exception>
-        protected internal virtual ItemResolveArgs CreateResolveArgs(FileSystemInfo pathInfo = null)
-        {
-            var path = Path;
-
-            var locationType = LocationType;
-
-            if (locationType == LocationType.Remote ||
-                locationType == LocationType.Virtual)
-            {
-                return new ItemResolveArgs(ConfigurationManager.ApplicationPaths, LibraryManager);
-            }
-
-            var isDirectory = false;
-
-            if (UseParentPathToCreateResolveArgs)
-            {
-                path = System.IO.Path.GetDirectoryName(path);
-                isDirectory = true;
-            }
-
-            pathInfo = pathInfo ?? (isDirectory ? new DirectoryInfo(path) : FileSystem.GetFileSystemInfo(path));
-
-            if (pathInfo == null || !pathInfo.Exists)
-            {
-                throw new IOException("Unable to retrieve file system info for " + path);
-            }
-
-            var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, LibraryManager)
-            {
-                FileInfo = pathInfo,
-                Path = path,
-                Parent = Parent
-            };
-
-            // Gather child folder and files
-            if (args.IsDirectory)
-            {
-                var isPhysicalRoot = args.IsPhysicalRoot;
-
-                // When resolving the root, we need it's grandchildren (children of user views)
-                var flattenFolderDepth = isPhysicalRoot ? 2 : 0;
-
-                var fileSystemDictionary = FileData.GetFilteredFileSystemEntries(args.Path, FileSystem, Logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf);
-
-                // Need to remove subpaths that may have been resolved from shortcuts
-                // Example: if \\server\movies exists, then strip out \\server\movies\action
-                if (isPhysicalRoot)
+                if (locationType != LocationType.Remote && locationType != LocationType.Virtual)
                 {
-                    var paths = LibraryManager.NormalizeRootPathList(fileSystemDictionary.Keys);
-
-                    fileSystemDictionary = paths.Select(i => (FileSystemInfo)new DirectoryInfo(i)).ToDictionary(i => i.FullName);
+                    return new string[] { };
                 }
 
-                args.FileSystemDictionary = fileSystemDictionary;
-            }
-
-            //update our dates
-            EntityResolutionHelper.EnsureDates(FileSystem, this, args, false);
-
-            IsOffline = false;
-
-            return args;
-        }
-
-        /// <summary>
-        /// Some subclasses will stop resolving at a directory and point their Path to a file within. This will help ensure the on-demand resolve args are identical to the
-        /// original ones.
-        /// </summary>
-        /// <value><c>true</c> if [use parent path to create resolve args]; otherwise, <c>false</c>.</value>
-        [IgnoreDataMember]
-        protected virtual bool UseParentPathToCreateResolveArgs
-        {
-            get
-            {
-                return false;
+                return new[] { Path };
             }
         }
 
@@ -583,122 +456,93 @@ namespace MediaBrowser.Controller.Entities
         /// Loads local trailers from the file system
         /// </summary>
         /// <returns>List{Video}.</returns>
-        private IEnumerable<Trailer> LoadLocalTrailers()
-        {
-            ItemResolveArgs resolveArgs;
-
-            try
-            {
-                resolveArgs = ResolveArgs;
-
-                if (!resolveArgs.IsDirectory)
-                {
-                    return new List<Trailer>();
-                }
-            }
-            catch (IOException ex)
-            {
-                Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path);
-                return new List<Trailer>();
-            }
-
-            var files = new List<FileSystemInfo>();
-
-            var folder = resolveArgs.GetFileSystemEntryByName(TrailerFolderName);
-
-            // Path doesn't exist. No biggie
-            if (folder != null)
-            {
-                try
-                {
-                    files.AddRange(new DirectoryInfo(folder.FullName).EnumerateFiles());
-                }
-                catch (IOException ex)
-                {
-                    Logger.ErrorException("Error loading trailers for {0}", ex, Name);
-                }
-            }
-
-            // Support xbmc trailers (-trailer suffix on video file names)
-            files.AddRange(resolveArgs.FileSystemChildren.Where(i =>
-            {
-                try
-                {
-                    if ((i.Attributes & FileAttributes.Directory) != FileAttributes.Directory)
-                    {
-                        if (System.IO.Path.GetFileNameWithoutExtension(i.Name).EndsWith(XbmcTrailerFileSuffix, StringComparison.OrdinalIgnoreCase) && !string.Equals(Path, i.FullName, StringComparison.OrdinalIgnoreCase))
-                        {
-                            return true;
-                        }
-                    }
-                }
-                catch (IOException ex)
-                {
-                    Logger.ErrorException("Error accessing path {0}", ex, i.FullName);
-                }
-
-                return false;
-            }));
-
-            return LibraryManager.ResolvePaths<Trailer>(files, null).Select(video =>
-            {
-                // Try to retrieve it from the db. If we don't find it, use the resolved version
-                var dbItem = LibraryManager.GetItemById(video.Id) as Trailer;
-
-                if (dbItem != null)
-                {
-                    dbItem.ResetResolveArgs(video.ResolveArgs);
-                    video = dbItem;
-                }
-
-                return video;
-
-            }).ToList();
+        private IEnumerable<Trailer> LoadLocalTrailers(List<FileSystemInfo> fileSystemChildren)
+        {
+            return new List<Trailer>();
+            //ItemResolveArgs resolveArgs;
+
+            //try
+            //{
+            //    resolveArgs = ResolveArgs;
+
+            //    if (!resolveArgs.IsDirectory)
+            //    {
+            //        return new List<Trailer>();
+            //    }
+            //}
+            //catch (IOException ex)
+            //{
+            //    Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path);
+            //    return new List<Trailer>();
+            //}
+
+            //var files = new List<FileSystemInfo>();
+
+            //var folder = resolveArgs.GetFileSystemEntryByName(TrailerFolderName);
+
+            //// Path doesn't exist. No biggie
+            //if (folder != null)
+            //{
+            //    try
+            //    {
+            //        files.AddRange(new DirectoryInfo(folder.FullName).EnumerateFiles());
+            //    }
+            //    catch (IOException ex)
+            //    {
+            //        Logger.ErrorException("Error loading trailers for {0}", ex, Name);
+            //    }
+            //}
+
+            //// Support xbmc trailers (-trailer suffix on video file names)
+            //files.AddRange(resolveArgs.FileSystemChildren.Where(i =>
+            //{
+            //    try
+            //    {
+            //        if ((i.Attributes & FileAttributes.Directory) != FileAttributes.Directory)
+            //        {
+            //            if (System.IO.Path.GetFileNameWithoutExtension(i.Name).EndsWith(XbmcTrailerFileSuffix, StringComparison.OrdinalIgnoreCase) && !string.Equals(Path, i.FullName, StringComparison.OrdinalIgnoreCase))
+            //            {
+            //                return true;
+            //            }
+            //        }
+            //    }
+            //    catch (IOException ex)
+            //    {
+            //        Logger.ErrorException("Error accessing path {0}", ex, i.FullName);
+            //    }
+
+            //    return false;
+            //}));
+
+            //return LibraryManager.ResolvePaths<Trailer>(files, null).Select(video =>
+            //{
+            //    // Try to retrieve it from the db. If we don't find it, use the resolved version
+            //    var dbItem = LibraryManager.GetItemById(video.Id) as Trailer;
+
+            //    if (dbItem != null)
+            //    {
+            //        video = dbItem;
+            //    }
+
+            //    return video;
+
+            //}).ToList();
         }
 
         /// <summary>
         /// Loads the theme songs.
         /// </summary>
         /// <returns>List{Audio.Audio}.</returns>
-        private IEnumerable<Audio.Audio> LoadThemeSongs()
+        private IEnumerable<Audio.Audio> LoadThemeSongs(List<FileSystemInfo> fileSystemChildren)
         {
-            ItemResolveArgs resolveArgs;
-
-            try
-            {
-                resolveArgs = ResolveArgs;
-
-                if (!resolveArgs.IsDirectory)
-                {
-                    return new List<Audio.Audio>();
-                }
-            }
-            catch (IOException ex)
-            {
-                Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path);
-                return new List<Audio.Audio>();
-            }
-
-            var files = new List<FileSystemInfo>();
-
-            var folder = resolveArgs.GetFileSystemEntryByName(ThemeSongsFolderName);
-
-            // Path doesn't exist. No biggie
-            if (folder != null)
-            {
-                try
-                {
-                    files.AddRange(new DirectoryInfo(folder.FullName).EnumerateFiles());
-                }
-                catch (IOException ex)
-                {
-                    Logger.ErrorException("Error loading theme songs for {0}", ex, Name);
-                }
-            }
+            var files = fileSystemChildren.OfType<DirectoryInfo>()
+                .Where(i => string.Equals(i.Name, ThemeSongsFolderName, StringComparison.OrdinalIgnoreCase))
+                .SelectMany(i => i.EnumerateFiles("*", SearchOption.TopDirectoryOnly))
+                .ToList();
 
             // Support plex/xbmc convention
-            files.AddRange(resolveArgs.FileSystemChildren
-                .Where(i => string.Equals(System.IO.Path.GetFileNameWithoutExtension(i.Name), ThemeSongFilename, StringComparison.OrdinalIgnoreCase) && EntityResolutionHelper.IsAudioFile(i.Name))
+            files.AddRange(fileSystemChildren.OfType<FileInfo>()
+                .Where(i => string.Equals(System.IO.Path.GetFileNameWithoutExtension(i.Name), ThemeSongFilename, StringComparison.OrdinalIgnoreCase))
                 );
 
             return LibraryManager.ResolvePaths<Audio.Audio>(files, null).Select(audio =>
@@ -708,7 +552,6 @@ namespace MediaBrowser.Controller.Entities
 
                 if (dbItem != null)
                 {
-                    dbItem.ResetResolveArgs(audio.ResolveArgs);
                     audio = dbItem;
                 }
 
@@ -720,44 +563,11 @@ namespace MediaBrowser.Controller.Entities
         /// Loads the video backdrops.
         /// </summary>
         /// <returns>List{Video}.</returns>
-        private IEnumerable<Video> LoadThemeVideos()
+        private IEnumerable<Video> LoadThemeVideos(IEnumerable<FileSystemInfo> fileSystemChildren)
         {
-            ItemResolveArgs resolveArgs;
-
-            try
-            {
-                resolveArgs = ResolveArgs;
-
-                if (!resolveArgs.IsDirectory)
-                {
-                    return new List<Video>();
-                }
-            }
-            catch (IOException ex)
-            {
-                Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path);
-                return new List<Video>();
-            }
-
-            var folder = resolveArgs.GetFileSystemEntryByName(ThemeVideosFolderName);
-
-            // Path doesn't exist. No biggie
-            if (folder == null)
-            {
-                return new List<Video>();
-            }
-
-            IEnumerable<FileSystemInfo> files;
-
-            try
-            {
-                files = new DirectoryInfo(folder.FullName).EnumerateFiles();
-            }
-            catch (IOException ex)
-            {
-                Logger.ErrorException("Error loading video backdrops for {0}", ex, Name);
-                return new List<Video>();
-            }
+            var files = fileSystemChildren.OfType<DirectoryInfo>()
+                .Where(i => string.Equals(i.Name, ThemeVideosFolderName, StringComparison.OrdinalIgnoreCase))
+                .SelectMany(i => i.EnumerateFiles("*", SearchOption.TopDirectoryOnly));
 
             return LibraryManager.ResolvePaths<Video>(files, null).Select(item =>
             {
@@ -766,7 +576,6 @@ namespace MediaBrowser.Controller.Entities
 
                 if (dbItem != null)
                 {
-                    dbItem.ResetResolveArgs(item.ResolveArgs);
                     item = dbItem;
                 }
 
@@ -774,9 +583,9 @@ namespace MediaBrowser.Controller.Entities
             }).ToList();
         }
 
-        public Task<bool> RefreshMetadata(CancellationToken cancellationToken, bool resetResolveArgs = true)
+        public Task RefreshMetadata(CancellationToken cancellationToken)
         {
-            return RefreshMetadata(new MetadataRefreshOptions { ResetResolveArgs = resetResolveArgs }, cancellationToken);
+            return RefreshMetadata(new MetadataRefreshOptions(), cancellationToken);
         }
 
         /// <summary>
@@ -785,35 +594,24 @@ namespace MediaBrowser.Controller.Entities
         /// <param name="options">The options.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>true if a provider reports we changed</returns>
-        public async Task<bool> RefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken)
+        public async Task RefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken)
         {
-            if (options.ResetResolveArgs)
+            var locationType = LocationType;
+
+            if (IsFolder || Parent != null)
             {
-                // Reload this
-                ResetResolveArgs();
-            }
+                var files = locationType == LocationType.FileSystem || locationType == LocationType.Offline ?
+                    GetFileSystemChildren().ToList() :
+                    new List<FileSystemInfo>();
 
-            await BeforeRefreshMetadata(options, cancellationToken).ConfigureAwait(false);
+                await BeforeRefreshMetadata(options, files, cancellationToken).ConfigureAwait(false);
+            }
 
             await ProviderManager.RefreshMetadata(this, options, cancellationToken).ConfigureAwait(false);
-
-            return false;
         }
 
-        private readonly Task _cachedTask = Task.FromResult(true);
-        protected virtual Task BeforeRefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken)
+        protected virtual async Task BeforeRefreshMetadata(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
         {
-            return _cachedTask;
-        }
-
-        [Obsolete]
-        public virtual async Task<bool> RefreshMetadataDirect(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false)
-        {
-            // Refresh for the item
-            var itemRefreshTask = ProviderManager.ExecuteMetadataProviders(this, cancellationToken, forceRefresh);
-
-            cancellationToken.ThrowIfCancellationRequested();
-
             var themeSongsChanged = false;
 
             var themeVideosChanged = false;
@@ -825,102 +623,83 @@ namespace MediaBrowser.Controller.Entities
                 var hasThemeMedia = this as IHasThemeMedia;
                 if (hasThemeMedia != null)
                 {
-                    themeSongsChanged = await RefreshThemeSongs(hasThemeMedia, cancellationToken, forceSave, forceRefresh).ConfigureAwait(false);
+                    if (!IsInMixedFolder)
+                    {
+                        themeSongsChanged = await RefreshThemeSongs(hasThemeMedia, options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
 
-                    themeVideosChanged = await RefreshThemeVideos(hasThemeMedia, cancellationToken, forceSave, forceRefresh).ConfigureAwait(false);
+                        themeVideosChanged = await RefreshThemeVideos(hasThemeMedia, options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
+                    }
                 }
 
                 var hasTrailers = this as IHasTrailers;
                 if (hasTrailers != null)
                 {
-                    localTrailersChanged = await RefreshLocalTrailers(hasTrailers, cancellationToken, forceSave, forceRefresh).ConfigureAwait(false);
+                    localTrailersChanged = await RefreshLocalTrailers(hasTrailers, options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
                 }
             }
-
-            cancellationToken.ThrowIfCancellationRequested();
-
-            // Get the result from the item task
-            var updateReason = await itemRefreshTask.ConfigureAwait(false);
-
-            var changed = updateReason.HasValue;
-
-            if (changed || forceSave || themeSongsChanged || themeVideosChanged || localTrailersChanged)
+            
+            if (themeSongsChanged || themeVideosChanged || localTrailersChanged)
             {
-                cancellationToken.ThrowIfCancellationRequested();
-
-                await LibraryManager.UpdateItem(this, updateReason ?? ItemUpdateType.Unspecified, cancellationToken).ConfigureAwait(false);
+                options.ForceSave = true;
             }
+        }
 
-            return changed;
+        protected virtual IEnumerable<FileSystemInfo> GetFileSystemChildren()
+        {
+            var path = ContainingFolderPath;
+
+            return new DirectoryInfo(path).EnumerateFileSystemInfos("*", SearchOption.TopDirectoryOnly);
         }
 
-        private async Task<bool> RefreshLocalTrailers(IHasTrailers item, CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false)
+        private async Task<bool> RefreshLocalTrailers(IHasTrailers item, MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
         {
-            var newItems = LoadLocalTrailers().ToList();
+            var newItems = LoadLocalTrailers(fileSystemChildren).ToList();
             var newItemIds = newItems.Select(i => i.Id).ToList();
 
             var itemsChanged = !item.LocalTrailerIds.SequenceEqual(newItemIds);
 
-            var tasks = newItems.Select(i => i.RefreshMetadata(new MetadataRefreshOptions
-            {
-                ForceSave = forceSave,
-                ReplaceAllMetadata = forceRefresh,
-                ResetResolveArgs = false
+            var tasks = newItems.Select(i => i.RefreshMetadata(options, cancellationToken));
 
-            }, cancellationToken));
-
-            var results = await Task.WhenAll(tasks).ConfigureAwait(false);
+            await Task.WhenAll(tasks).ConfigureAwait(false);
 
             item.LocalTrailerIds = newItemIds;
 
-            return itemsChanged || results.Contains(true);
+            return itemsChanged;
         }
 
-        private async Task<bool> RefreshThemeVideos(IHasThemeMedia item, CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false)
+        private async Task<bool> RefreshThemeVideos(IHasThemeMedia item, MetadataRefreshOptions options, IEnumerable<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
         {
-            var newThemeVideos = LoadThemeVideos().ToList();
+            var newThemeVideos = LoadThemeVideos(fileSystemChildren).ToList();
             var newThemeVideoIds = newThemeVideos.Select(i => i.Id).ToList();
 
             var themeVideosChanged = !item.ThemeVideoIds.SequenceEqual(newThemeVideoIds);
 
-            var tasks = newThemeVideos.Select(i => i.RefreshMetadata(new MetadataRefreshOptions
-            {
-                ForceSave = forceSave,
-                ReplaceAllMetadata = forceRefresh,
-                ResetResolveArgs = false
-
-            }, cancellationToken));
+            var tasks = newThemeVideos.Select(i => i.RefreshMetadata(options, cancellationToken));
 
-            var results = await Task.WhenAll(tasks).ConfigureAwait(false);
+            await Task.WhenAll(tasks).ConfigureAwait(false);
 
             item.ThemeVideoIds = newThemeVideoIds;
 
-            return themeVideosChanged || results.Contains(true);
+            return themeVideosChanged;
         }
 
         /// <summary>
         /// Refreshes the theme songs.
         /// </summary>
-        private async Task<bool> RefreshThemeSongs(IHasThemeMedia item, CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false)
+        private async Task<bool> RefreshThemeSongs(IHasThemeMedia item, MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
         {
-            var newThemeSongs = LoadThemeSongs().ToList();
+            var newThemeSongs = LoadThemeSongs(fileSystemChildren).ToList();
             var newThemeSongIds = newThemeSongs.Select(i => i.Id).ToList();
 
             var themeSongsChanged = !item.ThemeSongIds.SequenceEqual(newThemeSongIds);
 
-            var tasks = newThemeSongs.Select(i => i.RefreshMetadata(new MetadataRefreshOptions
-            {
-                ForceSave = forceSave,
-                ReplaceAllMetadata = forceRefresh,
-                ResetResolveArgs = false
-
-            }, cancellationToken));
+            var tasks = newThemeSongs.Select(i => i.RefreshMetadata(options, cancellationToken));
 
-            var results = await Task.WhenAll(tasks).ConfigureAwait(false);
+            await Task.WhenAll(tasks).ConfigureAwait(false);
 
             item.ThemeSongIds = newThemeSongIds;
 
-            return themeSongsChanged || results.Contains(true);
+            return themeSongsChanged;
         }
 
         /// <summary>
@@ -1655,27 +1434,8 @@ namespace MediaBrowser.Controller.Entities
                 throw new ArgumentNullException("imagePath");
             }
 
-            var locationType = LocationType;
-
-            if (locationType == LocationType.Remote ||
-                locationType == LocationType.Virtual)
-            {
-                return FileSystem.GetLastWriteTimeUtc(imagePath);
-            }
-
-            var metaFileEntry = ResolveArgs.GetMetaFileByPath(imagePath);
-
-            // If we didn't the metafile entry, check the Season
-            if (metaFileEntry == null)
-            {
-                if (Parent != null)
-                {
-                    metaFileEntry = Parent.ResolveArgs.GetMetaFileByPath(imagePath);
-                }
-            }
-
             // See if we can avoid a file system lookup by looking for the file in ResolveArgs
-            return metaFileEntry == null ? FileSystem.GetLastWriteTimeUtc(imagePath) : FileSystem.GetLastWriteTimeUtc(metaFileEntry);
+            return FileSystem.GetLastWriteTimeUtc(imagePath);
         }
 
         /// <summary>

+ 0 - 19
MediaBrowser.Controller/Entities/Book.cs

@@ -29,25 +29,6 @@ namespace MediaBrowser.Controller.Entities
         /// <value>The preferred metadata country code.</value>
         public string PreferredMetadataCountryCode { get; set; }
 
-        /// <summary>
-        /// 
-        /// </summary>
-        public override string MetaLocation
-        {
-            get
-            {
-                return System.IO.Path.GetDirectoryName(Path);
-            }
-        }
-
-        protected override bool UseParentPathToCreateResolveArgs
-        {
-            get
-            {
-                return !IsInMixedFolder;
-            }
-        }
-
         public Book()
         {
             Tags = new List<string>();

+ 64 - 2
MediaBrowser.Controller/Entities/CollectionFolder.cs

@@ -1,4 +1,6 @@
-using System;
+using MediaBrowser.Controller.IO;
+using MediaBrowser.Controller.Library;
+using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
@@ -14,6 +16,11 @@ namespace MediaBrowser.Controller.Entities
     /// </summary>
     public class CollectionFolder : Folder, ICollectionFolder
     {
+        public CollectionFolder()
+        {
+            PhysicalLocationsList = new List<string>();
+        }
+
         /// <summary>
         /// Gets a value indicating whether this instance is virtual folder.
         /// </summary>
@@ -42,6 +49,60 @@ namespace MediaBrowser.Controller.Entities
             }
         }
 
+        [IgnoreDataMember]
+        public override IEnumerable<string> PhysicalLocations
+        {
+            get
+            {
+                return PhysicalLocationsList;
+            }
+        }
+
+        public List<string> PhysicalLocationsList { get; set; }
+
+        protected override IEnumerable<FileSystemInfo> GetFileSystemChildren()
+        {
+            return CreateResolveArgs().FileSystemChildren;
+        }
+
+        private ItemResolveArgs CreateResolveArgs()
+        {
+            var path = ContainingFolderPath;
+
+            var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, LibraryManager)
+            {
+                FileInfo = new DirectoryInfo(path),
+                Path = path,
+                Parent = Parent
+            };
+
+            // Gather child folder and files
+            if (args.IsDirectory)
+            {
+                var isPhysicalRoot = args.IsPhysicalRoot;
+
+                // When resolving the root, we need it's grandchildren (children of user views)
+                var flattenFolderDepth = isPhysicalRoot ? 2 : 0;
+
+                var fileSystemDictionary = FileData.GetFilteredFileSystemEntries(args.Path, FileSystem, Logger, args, flattenFolderDepth: flattenFolderDepth, resolveShortcuts: isPhysicalRoot || args.IsVf);
+
+                // Need to remove subpaths that may have been resolved from shortcuts
+                // Example: if \\server\movies exists, then strip out \\server\movies\action
+                if (isPhysicalRoot)
+                {
+                    var paths = LibraryManager.NormalizeRootPathList(fileSystemDictionary.Keys);
+
+                    fileSystemDictionary = paths.Select(i => (FileSystemInfo)new DirectoryInfo(i)).ToDictionary(i => i.FullName);
+                }
+
+                args.FileSystemDictionary = fileSystemDictionary;
+            }
+
+            PhysicalLocationsList = args.PhysicalLocations.ToList();
+
+            return args;
+        }
+
         // Cache this since it will be used a lot
         /// <summary>
         /// The null task result
@@ -59,13 +120,14 @@ namespace MediaBrowser.Controller.Entities
         /// <returns>Task.</returns>
         protected override Task ValidateChildrenInternal(IProgress<double> progress, CancellationToken cancellationToken, bool? recursive = null, bool forceRefreshMetadata = false)
         {
+            CreateResolveArgs();
             ResetDynamicChildren();
 
             return NullTaskResult;
         }
 
         private List<LinkedChild> _linkedChildren;
-        
+
         /// <summary>
         /// Our children are actually just references to the ones in the physical root...
         /// </summary>

+ 15 - 48
MediaBrowser.Controller/Entities/Folder.cs

@@ -1,6 +1,7 @@
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Common.Progress;
 using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.IO;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Localization;
 using MediaBrowser.Controller.Providers;
@@ -27,7 +28,7 @@ namespace MediaBrowser.Controller.Entities
 
         public List<Guid> ThemeSongIds { get; set; }
         public List<Guid> ThemeVideoIds { get; set; }
-        
+
         public Folder()
         {
             LinkedChildren = new List<LinkedChild>();
@@ -379,7 +380,7 @@ namespace MediaBrowser.Controller.Entities
                 }
                 catch (IOException ex)
                 {
-                    nonCachedChildren = new BaseItem[] {};
+                    nonCachedChildren = new BaseItem[] { };
 
                     Logger.ErrorException("Error getting file system entries for {0}", ex, Path);
                 }
@@ -402,8 +403,6 @@ namespace MediaBrowser.Controller.Entities
 
                     if (currentChildren.TryGetValue(child.Id, out currentChild))
                     {
-                        currentChild.ResetResolveArgs(child.ResolveArgs);
-
                         //existing item - check if it has changed
                         if (currentChild.HasChanged(child))
                         {
@@ -411,7 +410,7 @@ namespace MediaBrowser.Controller.Entities
                             if (currentChildLocationType != LocationType.Remote &&
                                 currentChildLocationType != LocationType.Virtual)
                             {
-                                EntityResolutionHelper.EnsureDates(FileSystem, currentChild, child.ResolveArgs, false);
+                                currentChild.DateModified = child.DateModified;
                             }
 
                             currentChild.IsInMixedFolder = child.IsInMixedFolder;
@@ -539,8 +538,7 @@ namespace MediaBrowser.Controller.Entities
                 await child.RefreshMetadata(new MetadataRefreshOptions
                 {
                     ForceSave = currentTuple.Item2,
-                    ReplaceAllMetadata = forceRefreshMetadata,
-                    ResetResolveArgs = false
+                    ReplaceAllMetadata = forceRefreshMetadata
 
                 }, cancellationToken).ConfigureAwait(false);
             }
@@ -581,16 +579,6 @@ namespace MediaBrowser.Controller.Entities
                 });
 
                 await ((Folder)child).ValidateChildren(innerProgress, cancellationToken, recursive, forceRefreshMetadata).ConfigureAwait(false);
-
-                try
-                {
-                    // Some folder providers are unable to refresh until children have been refreshed.
-                    await child.RefreshMetadata(cancellationToken, resetResolveArgs: false).ConfigureAwait(false);
-                }
-                catch (IOException ex)
-                {
-                    Logger.ErrorException("Error refreshing {0}", ex, child.Path ?? child.Name);
-                }
             }
             else
             {
@@ -661,14 +649,7 @@ namespace MediaBrowser.Controller.Entities
         /// <returns>IEnumerable{BaseItem}.</returns>
         protected virtual IEnumerable<BaseItem> GetNonCachedChildren()
         {
-            var resolveArgs = ResolveArgs;
-
-            if (resolveArgs == null || resolveArgs.FileSystemDictionary == null)
-            {
-                Logger.Error("ResolveArgs null for {0}", Path);
-            }
-
-            return LibraryManager.ResolvePaths<BaseItem>(resolveArgs.FileSystemChildren, this);
+            return LibraryManager.ResolvePaths<BaseItem>(GetFileSystemChildren(), this);
         }
 
         /// <summary>
@@ -914,43 +895,29 @@ namespace MediaBrowser.Controller.Entities
             return item;
         }
 
-        protected override Task BeforeRefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken)
+        protected override Task BeforeRefreshMetadata(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
         {
             if (SupportsShortcutChildren && LocationType == LocationType.FileSystem)
             {
-                RefreshLinkedChildren();
+                if (RefreshLinkedChildren(fileSystemChildren))
+                {
+                    options.ForceSave = true;
+                }
             }
 
-            return base.BeforeRefreshMetadata(options, cancellationToken);
+            return base.BeforeRefreshMetadata(options, fileSystemChildren, cancellationToken);
         }
 
         /// <summary>
         /// Refreshes the linked children.
         /// </summary>
         /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
-        private bool RefreshLinkedChildren()
+        private bool RefreshLinkedChildren(IEnumerable<FileSystemInfo> fileSystemChildren)
         {
-            ItemResolveArgs resolveArgs;
-
-            try
-            {
-                resolveArgs = ResolveArgs;
-
-                if (!resolveArgs.IsDirectory)
-                {
-                    return false;
-                }
-            }
-            catch (IOException ex)
-            {
-                Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path);
-                return false;
-            }
-
             var currentManualLinks = LinkedChildren.Where(i => i.Type == LinkedChildType.Manual).ToList();
             var currentShortcutLinks = LinkedChildren.Where(i => i.Type == LinkedChildType.Shortcut).ToList();
 
-            var newShortcutLinks = resolveArgs.FileSystemChildren
+            var newShortcutLinks = fileSystemChildren
                 .Where(i => (i.Attributes & FileAttributes.Directory) != FileAttributes.Directory && FileSystem.IsShortcut(i.FullName))
                 .Select(i =>
                 {
@@ -1058,7 +1025,7 @@ namespace MediaBrowser.Controller.Entities
                     return this;
                 }
 
-                if (locationType != LocationType.Virtual && ResolveArgs.PhysicalLocations.Contains(path, StringComparer.OrdinalIgnoreCase))
+                if (locationType != LocationType.Virtual && PhysicalLocations.Contains(path, StringComparer.OrdinalIgnoreCase))
                 {
                     return this;
                 }

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

@@ -79,17 +79,6 @@ namespace MediaBrowser.Controller.Entities
         /// <value>The game system.</value>
         public string GameSystem { get; set; }
 
-        /// <summary>
-        /// 
-        /// </summary>
-        public override string MetaLocation
-        {
-            get
-            {
-                return System.IO.Path.GetDirectoryName(Path);
-            }
-        }
-
         /// <summary>
         /// Gets or sets a value indicating whether this instance is multi part.
         /// </summary>
@@ -101,17 +90,6 @@ namespace MediaBrowser.Controller.Entities
         /// </summary>
         public List<string> MultiPartGameFiles { get; set; }
 
-        /// <summary>
-        /// 
-        /// </summary>
-        protected override bool UseParentPathToCreateResolveArgs
-        {
-            get
-            {
-                return !IsInMixedFolder;
-            }
-        }
-
         public override string GetUserDataKey()
         {
             var id = this.GetProviderId(MetadataProviders.Gamesdb);

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

@@ -99,6 +99,12 @@ namespace MediaBrowser.Controller.Entities
         /// </summary>
         /// <value>The backdrop image paths.</value>
         List<string> BackdropImagePaths { get; set; }
+
+        /// <summary>
+        /// Gets a value indicating whether this instance is owned item.
+        /// </summary>
+        /// <value><c>true</c> if this instance is owned item; otherwise, <c>false</c>.</value>
+        bool IsOwnedItem { get; }
     }
 
     public static class HasImagesExtensions

+ 20 - 61
MediaBrowser.Controller/Entities/Movies/Movie.cs

@@ -21,7 +21,7 @@ namespace MediaBrowser.Controller.Entities.Movies
 
         public List<Guid> ThemeSongIds { get; set; }
         public List<Guid> ThemeVideoIds { get; set; }
-        
+
         /// <summary>
         /// Gets or sets the preferred metadata country code.
         /// </summary>
@@ -29,7 +29,7 @@ namespace MediaBrowser.Controller.Entities.Movies
         public string PreferredMetadataCountryCode { get; set; }
 
         public string PreferredMetadataLanguage { get; set; }
-        
+
         public Movie()
         {
             SpecialFeatureIds = new List<Guid>();
@@ -49,7 +49,7 @@ namespace MediaBrowser.Controller.Entities.Movies
 
         public List<Guid> LocalTrailerIds { get; set; }
         public List<string> Keywords { get; set; }
-    
+
         public List<MediaUrl> RemoteTrailers { get; set; }
 
         /// <summary>
@@ -103,88 +103,48 @@ namespace MediaBrowser.Controller.Entities.Movies
             return this.GetProviderId(MetadataProviders.Tmdb) ?? this.GetProviderId(MetadataProviders.Imdb) ?? base.GetUserDataKey();
         }
 
-        /// <summary>
-        /// Overrides the base implementation to refresh metadata for special features
-        /// </summary>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <param name="forceSave">if set to <c>true</c> [is new item].</param>
-        /// <param name="forceRefresh">if set to <c>true</c> [force].</param>
-        /// <returns>Task{System.Boolean}.</returns>
-        public override async Task<bool> RefreshMetadataDirect(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false)
+        protected override async Task BeforeRefreshMetadata(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
         {
-            // Kick off a task to refresh the main item
-            var result = await base.RefreshMetadataDirect(cancellationToken, forceSave, forceRefresh).ConfigureAwait(false);
-
-            var specialFeaturesChanged = false;
+            await base.BeforeRefreshMetadata(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
 
             // Must have a parent to have special features
             // In other words, it must be part of the Parent/Child tree
             if (LocationType == LocationType.FileSystem && Parent != null && !IsInMixedFolder)
             {
-                specialFeaturesChanged = await RefreshSpecialFeatures(cancellationToken, forceSave, forceRefresh).ConfigureAwait(false);
-            }
+                var specialFeaturesChanged = await RefreshSpecialFeatures(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
 
-            return specialFeaturesChanged || result;
+                if (specialFeaturesChanged)
+                {
+                    options.ForceSave = true;
+                }
+            }
         }
 
-        private async Task<bool> RefreshSpecialFeatures(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false, bool allowSlowProviders = true)
+        private async Task<bool> RefreshSpecialFeatures(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
         {
-            var newItems = LoadSpecialFeatures().ToList();
+            var newItems = LoadSpecialFeatures(fileSystemChildren).ToList();
             var newItemIds = newItems.Select(i => i.Id).ToList();
 
             var itemsChanged = !SpecialFeatureIds.SequenceEqual(newItemIds);
 
-            var tasks = newItems.Select(i => i.RefreshMetadata(new MetadataRefreshOptions
-            {
-                ForceSave = forceSave,
-                ReplaceAllMetadata = forceRefresh,
-                ResetResolveArgs = false
-
-            }, cancellationToken));
+            var tasks = newItems.Select(i => i.RefreshMetadata(options, cancellationToken));
 
-            var results = await Task.WhenAll(tasks).ConfigureAwait(false);
+            await Task.WhenAll(tasks).ConfigureAwait(false);
 
             SpecialFeatureIds = newItemIds;
 
-            return itemsChanged || results.Contains(true);
+            return itemsChanged;
         }
 
         /// <summary>
         /// Loads the special features.
         /// </summary>
         /// <returns>IEnumerable{Video}.</returns>
-        private IEnumerable<Video> LoadSpecialFeatures()
+        private IEnumerable<Video> LoadSpecialFeatures(IEnumerable<FileSystemInfo> fileSystemChildren)
         {
-            FileSystemInfo folder;
-
-            try
-            {
-                folder = ResolveArgs.GetFileSystemEntryByName("extras") ??
-                    ResolveArgs.GetFileSystemEntryByName("specials");
-            }
-            catch (IOException ex)
-            {
-                Logger.ErrorException("Error getting ResolveArgs for {0}", ex, Path);
-                return new List<Video>();
-            }
-
-            // Path doesn't exist. No biggie
-            if (folder == null)
-            {
-                return new List<Video>();
-            }
-
-            IEnumerable<FileSystemInfo> files;
-
-            try
-            {
-                files = new DirectoryInfo(folder.FullName).EnumerateFiles();
-            }
-            catch (IOException ex)
-            {
-                Logger.ErrorException("Error loading special features for {0}", ex, Name);
-                return new List<Video>();
-            }
+            var files = fileSystemChildren.OfType<DirectoryInfo>()
+                .Where(i => string.Equals(i.Name, "extras", StringComparison.OrdinalIgnoreCase) || string.Equals(i.Name, "specials", StringComparison.OrdinalIgnoreCase))
+                .SelectMany(i => i.EnumerateFiles("*", SearchOption.TopDirectoryOnly));
 
             return LibraryManager.ResolvePaths<Video>(files, null).Select(video =>
             {
@@ -193,7 +153,6 @@ namespace MediaBrowser.Controller.Entities.Movies
 
                 if (dbItem != null)
                 {
-                    dbItem.ResetResolveArgs(video.ResolveArgs);
                     video = dbItem;
                 }
 

+ 0 - 22
MediaBrowser.Controller/Entities/TV/Episode.cs

@@ -11,28 +11,6 @@ namespace MediaBrowser.Controller.Entities.TV
     /// </summary>
     public class Episode : Video
     {
-        /// <summary>
-        /// Episodes have a special Metadata folder
-        /// </summary>
-        /// <value>The meta location.</value>
-        [IgnoreDataMember]
-        public override string MetaLocation
-        {
-            get
-            {
-                return System.IO.Path.Combine(Parent.Path, "metadata");
-            }
-        }
-
-        [IgnoreDataMember]
-        protected override bool UseParentPathToCreateResolveArgs
-        {
-            get
-            {
-                return false;
-            }
-        }
-
         /// <summary>
         /// Gets the season in which it aired.
         /// </summary>

+ 0 - 28
MediaBrowser.Controller/Entities/TV/Season.cs

@@ -131,34 +131,6 @@ namespace MediaBrowser.Controller.Entities.TV
             get { return Series != null ? Series.CustomRatingForComparison : base.CustomRatingForComparison; }
         }
 
-        /// <summary>
-        /// Add files from the metadata folder to ResolveArgs
-        /// </summary>
-        /// <param name="args">The args.</param>
-        public static void AddMetadataFiles(ItemResolveArgs args)
-        {
-            var folder = args.GetFileSystemEntryByName("metadata");
-
-            if (folder != null)
-            {
-                args.AddMetadataFiles(new DirectoryInfo(folder.FullName).EnumerateFiles());
-            }
-        }
-
-        /// <summary>
-        /// Creates ResolveArgs on demand
-        /// </summary>
-        /// <param name="pathInfo">The path info.</param>
-        /// <returns>ItemResolveArgs.</returns>
-        protected internal override ItemResolveArgs CreateResolveArgs(FileSystemInfo pathInfo = null)
-        {
-            var args = base.CreateResolveArgs(pathInfo);
-
-            AddMetadataFiles(args);
-
-            return args;
-        }
-
         /// <summary>
         /// Creates the name of the sort.
         /// </summary>

+ 0 - 14
MediaBrowser.Controller/Entities/TV/Series.cs

@@ -111,20 +111,6 @@ namespace MediaBrowser.Controller.Entities.TV
             };
         }
 
-        /// <summary>
-        /// Creates ResolveArgs on demand
-        /// </summary>
-        /// <param name="pathInfo">The path info.</param>
-        /// <returns>ItemResolveArgs.</returns>
-        protected internal override ItemResolveArgs CreateResolveArgs(FileSystemInfo pathInfo = null)
-        {
-            var args = base.CreateResolveArgs(pathInfo);
-
-            Season.AddMetadataFiles(args);
-
-            return args;
-        }
-
         [IgnoreDataMember]
         public bool ContainsEpisodesWithoutSeasonFolders
         {

+ 0 - 27
MediaBrowser.Controller/Entities/Trailer.cs

@@ -89,33 +89,6 @@ namespace MediaBrowser.Controller.Entities
             }
         }
 
-        /// <summary>
-        /// Should be overridden to return the proper folder where metadata lives
-        /// </summary>
-        /// <value>The meta location.</value>
-        [IgnoreDataMember]
-        public override string MetaLocation
-        {
-            get
-            {
-                if (!IsLocalTrailer)
-                {
-                    return System.IO.Path.GetDirectoryName(Path);
-                }
-
-                return base.MetaLocation;
-            }
-        }
-
-        /// <summary>
-        /// Needed because the resolver stops at the trailer folder and we find the video inside.
-        /// </summary>
-        /// <value><c>true</c> if [use parent path to create resolve args]; otherwise, <c>false</c>.</value>
-        protected override bool UseParentPathToCreateResolveArgs
-        {
-            get { return !IsLocalTrailer; }
-        }
-
         public override string GetUserDataKey()
         {
             var key = this.GetProviderId(MetadataProviders.Tmdb) ?? this.GetProviderId(MetadataProviders.Tvdb) ?? this.GetProviderId(MetadataProviders.Imdb) ?? this.GetProviderId(MetadataProviders.Tvcom);

+ 31 - 75
MediaBrowser.Controller/Entities/Video.cs

@@ -84,33 +84,23 @@ namespace MediaBrowser.Controller.Entities
         /// <value>The aspect ratio.</value>
         public string AspectRatio { get; set; }
 
-        /// <summary>
-        /// Should be overridden to return the proper folder where metadata lives
-        /// </summary>
-        /// <value>The meta location.</value>
         [IgnoreDataMember]
-        public override string MetaLocation
+        public override string ContainingFolderPath
         {
             get
             {
-                return VideoType == VideoType.VideoFile || VideoType == VideoType.Iso || IsMultiPart ? System.IO.Path.GetDirectoryName(Path) : Path;
-            }
-        }
+                if (IsMultiPart)
+                {
+                    return System.IO.Path.GetDirectoryName(Path);
+                }
 
-        /// <summary>
-        /// Needed because the resolver stops at the movie folder and we find the video inside.
-        /// </summary>
-        /// <value><c>true</c> if [use parent path to create resolve args]; otherwise, <c>false</c>.</value>
-        protected override bool UseParentPathToCreateResolveArgs
-        {
-            get
-            {
-                if (IsInMixedFolder)
+                if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd ||
+                    VideoType == VideoType.HdDvd)
                 {
-                    return false;
+                    return Path;
                 }
 
-                return VideoType == VideoType.VideoFile || VideoType == VideoType.Iso || IsMultiPart;
+                return base.ContainingFolderPath;
             }
         }
 
@@ -159,106 +149,73 @@ namespace MediaBrowser.Controller.Entities
             }
         }
 
-        /// <summary>
-        /// Overrides the base implementation to refresh metadata for local trailers
-        /// </summary>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <param name="forceSave">if set to <c>true</c> [is new item].</param>
-        /// <param name="forceRefresh">if set to <c>true</c> [force].</param>
-        /// <returns>true if a provider reports we changed</returns>
-        public override async Task<bool> RefreshMetadataDirect(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false)
+        protected override async Task BeforeRefreshMetadata(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
         {
-            // Kick off a task to refresh the main item
-            var result = await base.RefreshMetadataDirect(cancellationToken, forceSave, forceRefresh).ConfigureAwait(false);
-
-            var additionalPartsChanged = false;
+            await base.BeforeRefreshMetadata(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
 
             // Must have a parent to have additional parts
             // In other words, it must be part of the Parent/Child tree
             // The additional parts won't have additional parts themselves
             if (IsMultiPart && LocationType == LocationType.FileSystem && Parent != null)
             {
-                try
-                {
-                    additionalPartsChanged = await RefreshAdditionalParts(cancellationToken, forceSave, forceRefresh).ConfigureAwait(false);
-                }
-                catch (IOException ex)
+                var additionalPartsChanged = await RefreshAdditionalParts(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
+
+                if (additionalPartsChanged)
                 {
-                    Logger.ErrorException("Error loading additional parts for {0}.", ex, Name);
+                    options.ForceSave = true;
                 }
             }
-
-            return additionalPartsChanged || result;
         }
 
         /// <summary>
         /// Refreshes the additional parts.
         /// </summary>
+        /// <param name="options">The options.</param>
+        /// <param name="fileSystemChildren">The file system children.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
-        /// <param name="forceSave">if set to <c>true</c> [force save].</param>
-        /// <param name="forceRefresh">if set to <c>true</c> [force refresh].</param>
-        /// <param name="allowSlowProviders">if set to <c>true</c> [allow slow providers].</param>
         /// <returns>Task{System.Boolean}.</returns>
-        private async Task<bool> RefreshAdditionalParts(CancellationToken cancellationToken, bool forceSave = false, bool forceRefresh = false, bool allowSlowProviders = true)
+        private async Task<bool> RefreshAdditionalParts(MetadataRefreshOptions options, List<FileSystemInfo> fileSystemChildren, CancellationToken cancellationToken)
         {
-            var newItems = LoadAdditionalParts().ToList();
+            var newItems = LoadAdditionalParts(fileSystemChildren).ToList();
 
             var newItemIds = newItems.Select(i => i.Id).ToList();
 
             var itemsChanged = !AdditionalPartIds.SequenceEqual(newItemIds);
 
-            var tasks = newItems.Select(i => i.RefreshMetadata(new MetadataRefreshOptions
-            {
-                ForceSave = forceSave,
-                ReplaceAllMetadata = forceRefresh
+            var tasks = newItems.Select(i => i.RefreshMetadata(options, cancellationToken));
 
-            }, cancellationToken));
-
-            var results = await Task.WhenAll(tasks).ConfigureAwait(false);
+            await Task.WhenAll(tasks).ConfigureAwait(false);
 
             AdditionalPartIds = newItemIds;
 
-            return itemsChanged || results.Contains(true);
+            return itemsChanged;
         }
 
         /// <summary>
         /// Loads the additional parts.
         /// </summary>
         /// <returns>IEnumerable{Video}.</returns>
-        private IEnumerable<Video> LoadAdditionalParts()
+        private IEnumerable<Video> LoadAdditionalParts(IEnumerable<FileSystemInfo> fileSystemChildren)
         {
             IEnumerable<FileSystemInfo> files;
 
             var path = Path;
 
-            if (string.IsNullOrEmpty(path))
-            {
-                throw new ApplicationException(string.Format("Item {0} has a null path.", Name ?? Id.ToString()));
-            }
-
             if (VideoType == VideoType.BluRay || VideoType == VideoType.Dvd)
             {
-                var parentPath = System.IO.Path.GetDirectoryName(path);
-
-                if (string.IsNullOrEmpty(parentPath))
+                files = fileSystemChildren.Where(i =>
                 {
-                    throw new IOException("Unable to get parent path info from " + path);
-                }
+                    if ((i.Attributes & FileAttributes.Directory) == FileAttributes.Directory)
+                    {
+                        return !string.Equals(i.FullName, path, StringComparison.OrdinalIgnoreCase) && EntityResolutionHelper.IsVideoFile(i.FullName) && EntityResolutionHelper.IsMultiPartFile(i.Name);
+                    }
 
-                files = new DirectoryInfo(parentPath)
-                    .EnumerateDirectories()
-                    .Where(i => !string.Equals(i.FullName, path, StringComparison.OrdinalIgnoreCase) && EntityResolutionHelper.IsMultiPartFile(i.Name));
+                    return false;
+                });
             }
             else
             {
-                var resolveArgs = ResolveArgs;
-
-                if (resolveArgs == null)
-                {
-                    throw new IOException("ResolveArgs are null for " + path);
-                }
-
-                files = resolveArgs.FileSystemChildren.Where(i =>
+                files = fileSystemChildren.Where(i =>
                 {
                     if ((i.Attributes & FileAttributes.Directory) == FileAttributes.Directory)
                     {
@@ -276,7 +233,6 @@ namespace MediaBrowser.Controller.Entities
 
                 if (dbItem != null)
                 {
-                    dbItem.ResetResolveArgs(video.ResolveArgs);
                     video = dbItem;
                 }
 

+ 0 - 91
MediaBrowser.Controller/Library/ItemResolveArgs.cs

@@ -195,77 +195,6 @@ namespace MediaBrowser.Controller.Library
             }
         }
 
-        /// <summary>
-        /// Store these to reduce disk access in Resolvers
-        /// </summary>
-        /// <value>The metadata file dictionary.</value>
-        private Dictionary<string, FileSystemInfo> MetadataFileDictionary { get; set; }
-
-        /// <summary>
-        /// Gets the metadata files.
-        /// </summary>
-        /// <value>The metadata files.</value>
-        public IEnumerable<FileSystemInfo> MetadataFiles
-        {
-            get
-            {
-                if (MetadataFileDictionary != null)
-                {
-                    return MetadataFileDictionary.Values;
-                }
-
-                return new FileSystemInfo[] { };
-            }
-        }
-
-        /// <summary>
-        /// Adds the metadata file.
-        /// </summary>
-        /// <param name="path">The path.</param>
-        /// <exception cref="System.IO.FileNotFoundException"></exception>
-        public void AddMetadataFile(string path)
-        {
-            var file = new FileInfo(path);
-
-            if (!file.Exists)
-            {
-                throw new FileNotFoundException(path);
-            }
-
-            AddMetadataFile(file);
-        }
-
-        /// <summary>
-        /// Adds the metadata file.
-        /// </summary>
-        /// <param name="fileInfo">The file info.</param>
-        public void AddMetadataFile(FileSystemInfo fileInfo)
-        {
-            AddMetadataFiles(new[] { fileInfo });
-        }
-
-        /// <summary>
-        /// Adds the metadata files.
-        /// </summary>
-        /// <param name="files">The files.</param>
-        /// <exception cref="System.ArgumentNullException"></exception>
-        public void AddMetadataFiles(IEnumerable<FileSystemInfo> files)
-        {
-            if (files == null)
-            {
-                throw new ArgumentNullException();
-            }
-
-            if (MetadataFileDictionary == null)
-            {
-                MetadataFileDictionary = new Dictionary<string, FileSystemInfo>(StringComparer.OrdinalIgnoreCase);
-            }
-            foreach (var file in files)
-            {
-                MetadataFileDictionary[file.Name] = file;
-            }
-        }
-
         /// <summary>
         /// Gets the name of the file system entry by.
         /// </summary>
@@ -320,16 +249,6 @@ namespace MediaBrowser.Controller.Library
             {
                 throw new ArgumentNullException();
             }
-            
-            if (MetadataFileDictionary != null)
-            {
-                FileSystemInfo entry;
-
-                if (MetadataFileDictionary.TryGetValue(System.IO.Path.GetFileName(path), out entry))
-                {
-                    return entry;
-                }
-            }
 
             return GetFileSystemEntryByPath(path);
         }
@@ -346,16 +265,6 @@ namespace MediaBrowser.Controller.Library
             {
                 throw new ArgumentNullException();
             }
-            
-            if (MetadataFileDictionary != null)
-            {
-                FileSystemInfo entry;
-
-                if (MetadataFileDictionary.TryGetValue(name, out entry))
-                {
-                    return entry;
-                }
-            }
 
             return GetFileSystemEntryByName(name);
         }

+ 1 - 1
MediaBrowser.Controller/LiveTv/ILiveTvRecording.cs

@@ -19,6 +19,6 @@ namespace MediaBrowser.Controller.LiveTv
 
         bool IsParentalAllowed(User user);
 
-        Task<bool> RefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken);
+        Task RefreshMetadata(MetadataRefreshOptions options, CancellationToken cancellationToken);
     }
 }

+ 2 - 6
MediaBrowser.Controller/Providers/BaseItemXmlParser.cs

@@ -20,7 +20,7 @@ namespace MediaBrowser.Controller.Providers
     /// </summary>
     /// <typeparam name="T"></typeparam>
     public class BaseItemXmlParser<T>
-        where T : BaseItem, new()
+        where T : BaseItem
     {
         /// <summary>
         /// The logger
@@ -422,11 +422,7 @@ namespace MediaBrowser.Controller.Providers
                             int runtime;
                             if (int.TryParse(text.Split(' ')[0], NumberStyles.Integer, _usCulture, out runtime))
                             {
-                                // For audio and video don't replace ffmpeg data
-                                if (!(item is Video || item is Audio))
-                                {
-                                    item.RunTimeTicks = TimeSpan.FromMinutes(runtime).Ticks;
-                                }
+                                item.RunTimeTicks = TimeSpan.FromMinutes(runtime).Ticks;
                             }
                         }
                         break;

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

@@ -2,13 +2,8 @@
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
-using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
 
@@ -146,19 +141,6 @@ namespace MediaBrowser.Controller.Providers
             providerInfo.LastRefreshed = value;
             providerInfo.LastRefreshStatus = status;
             providerInfo.ProviderVersion = providerVersion;
-
-            // Save the file system stamp for future comparisons
-            if (RefreshOnFileSystemStampChange && item.LocationType == LocationType.FileSystem)
-            {
-                try
-                {
-                    providerInfo.FileStamp = GetCurrentFileSystemStamp(item);
-                }
-                catch (IOException ex)
-                {
-                    Logger.ErrorException("Error getting file stamp for {0}", ex, item.Path);
-                }
-            }
         }
 
         /// <summary>
@@ -233,12 +215,6 @@ namespace MediaBrowser.Controller.Providers
                 return true;
             }
 
-            if (RefreshOnFileSystemStampChange && item.LocationType == LocationType.FileSystem &&
-                HasFileSystemStampChanged(item, providerInfo))
-            {
-                return true;
-            }
-
             if (RefreshOnVersionChange && !String.Equals(ProviderVersion, providerInfo.ProviderVersion))
             {
                 return true;
@@ -263,17 +239,6 @@ namespace MediaBrowser.Controller.Providers
             return CompareDate(item) > providerInfo.LastRefreshed;
         }
 
-        /// <summary>
-        /// Determines if the item's file system stamp has changed from the last time the provider refreshed
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="providerInfo">The provider info.</param>
-        /// <returns><c>true</c> if [has file system stamp changed] [the specified item]; otherwise, <c>false</c>.</returns>
-        protected bool HasFileSystemStampChanged(BaseItem item, BaseProviderInfo providerInfo)
-        {
-            return GetCurrentFileSystemStamp(item) != providerInfo.FileStamp;
-        }
-
         /// <summary>
         /// Override this to return the date that should be compared to the last refresh date
         /// to determine if this provider should be re-fetched.
@@ -301,159 +266,5 @@ namespace MediaBrowser.Controller.Providers
         /// </summary>
         /// <value>The priority.</value>
         public abstract MetadataProviderPriority Priority { get; }
-
-        /// <summary>
-        /// Returns true or false indicating if the provider should refresh when the contents of it's directory changes
-        /// </summary>
-        /// <value><c>true</c> if [refresh on file system stamp change]; otherwise, <c>false</c>.</value>
-        protected virtual bool RefreshOnFileSystemStampChange
-        {
-            get
-            {
-                return false;
-            }
-        }
-
-        protected virtual string[] FilestampExtensions
-        {
-            get { return new string[] { }; }
-        }
-
-        /// <summary>
-        /// Determines if the parent's file system stamp should be used for comparison
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
-        protected virtual bool UseParentFileSystemStamp(BaseItem item)
-        {
-            // True when the current item is just a file
-            return !item.ResolveArgs.IsDirectory;
-        }
-
-        protected virtual IEnumerable<BaseItem> GetItemsForFileStampComparison(BaseItem item)
-        {
-            if (UseParentFileSystemStamp(item) && item.Parent != null)
-            {
-                return new[] { item.Parent };
-            }
-
-            return new[] { item };
-        }
-
-        /// <summary>
-        /// Gets the item's current file system stamp
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns>Guid.</returns>
-        private Guid GetCurrentFileSystemStamp(BaseItem item)
-        {
-            return GetFileSystemStamp(GetItemsForFileStampComparison(item));
-        }
-
-        private Dictionary<string, string> _fileStampExtensionsDictionary;
-
-        private Dictionary<string, string> FileStampExtensionsDictionary
-        {
-            get
-            {
-                return _fileStampExtensionsDictionary ??
-                       (_fileStampExtensionsDictionary =
-                           FilestampExtensions.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase));
-            }
-        }
-
-        /// <summary>
-        /// Gets the file system stamp.
-        /// </summary>
-        /// <param name="items">The items.</param>
-        /// <returns>Guid.</returns>
-        protected virtual Guid GetFileSystemStamp(IEnumerable<BaseItem> items)
-        {
-            var sb = new StringBuilder();
-
-            var extensions = FileStampExtensionsDictionary;
-            var numExtensions = FilestampExtensions.Length;
-
-            foreach (var item in items)
-            {
-                // If there's no path or the item is a file, there's nothing to do
-                if (item.LocationType == LocationType.FileSystem)
-                {
-                    var resolveArgs = item.ResolveArgs;
-
-                    if (resolveArgs.IsDirectory)
-                    {
-                        // Record the name of each file 
-                        // Need to sort these because accoring to msdn docs, our i/o methods are not guaranteed in any order
-                        AddFiles(sb, resolveArgs.FileSystemChildren, extensions, numExtensions);
-                        AddFiles(sb, resolveArgs.MetadataFiles, extensions, numExtensions);
-                    }
-                }
-            }
-
-            var stamp = sb.ToString();
-
-            if (string.IsNullOrEmpty(stamp))
-            {
-                return Guid.Empty;
-            }
-
-            return stamp.GetMD5();
-        }
-
-        private static readonly Dictionary<string, string> FoldersToMonitor = new[] { "extrafanart", "extrathumbs" }
-            .ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
-
-        protected Guid GetFileSystemStamp(IEnumerable<FileSystemInfo> files)
-        {
-            var sb = new StringBuilder();
-
-            var extensions = FileStampExtensionsDictionary;
-            var numExtensions = FilestampExtensions.Length;
-
-            // Record the name of each file 
-            // Need to sort these because accoring to msdn docs, our i/o methods are not guaranteed in any order
-            AddFiles(sb, files, extensions, numExtensions);
-
-            return sb.ToString().GetMD5();
-        }
-
-        /// <summary>
-        /// Adds the files.
-        /// </summary>
-        /// <param name="sb">The sb.</param>
-        /// <param name="files">The files.</param>
-        /// <param name="extensions">The extensions.</param>
-        /// <param name="numExtensions">The num extensions.</param>
-        private void AddFiles(StringBuilder sb, IEnumerable<FileSystemInfo> files, Dictionary<string, string> extensions, int numExtensions)
-        {
-            foreach (var file in files
-                .OrderBy(f => f.Name))
-            {
-                try
-                {
-                    if ((file.Attributes & FileAttributes.Directory) == FileAttributes.Directory)
-                    {
-                        if (FoldersToMonitor.ContainsKey(file.Name))
-                        {
-                            sb.Append(file.Name);
-
-                            var children = ((DirectoryInfo)file).EnumerateFiles("*", SearchOption.TopDirectoryOnly).ToList();
-                            AddFiles(sb, children, extensions, numExtensions);
-                        }
-                    }
-
-                    // It's a file
-                    else if (numExtensions == 0 || extensions.ContainsKey(file.Extension))
-                    {
-                        sb.Append(file.Name);
-                    }
-                }
-                catch (IOException ex)
-                {
-                    Logger.ErrorException("Error accessing file attributes for {0}", ex, file.FullName);
-                }
-            }
-        }
     }
 }

+ 6 - 0
MediaBrowser.Controller/Providers/IHasMetadata.cs

@@ -39,5 +39,11 @@ namespace MediaBrowser.Controller.Providers
         /// </summary>
         /// <returns><c>true</c> if [is save local metadata enabled]; otherwise, <c>false</c>.</returns>
         bool IsSaveLocalMetadataEnabled();
+
+        /// <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>
+        bool IsInMixedFolder { get; }
     }
 }

+ 9 - 8
MediaBrowser.Controller/Providers/ILocalMetadataProvider.cs

@@ -5,12 +5,6 @@ namespace MediaBrowser.Controller.Providers
 {
     public interface ILocalMetadataProvider : IMetadataProvider
     {
-        /// <summary>
-        /// Determines whether [has local metadata] [the specified item].
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns><c>true</c> if [has local metadata] [the specified item]; otherwise, <c>false</c>.</returns>
-        bool HasLocalMetadata(IHasMetadata item);
     }
 
     public interface ILocalMetadataProvider<TItemType> : IMetadataProvider<TItemType>, ILocalMetadataProvider
@@ -19,9 +13,16 @@ namespace MediaBrowser.Controller.Providers
         /// <summary>
         /// Gets the metadata.
         /// </summary>
-        /// <param name="path">The path.</param>
+        /// <param name="info">The information.</param>
         /// <param name="cancellationToken">The cancellation token.</param>
         /// <returns>Task{MetadataResult{`0}}.</returns>
-        Task<MetadataResult<TItemType>> GetMetadata(string path, CancellationToken cancellationToken);
+        Task<MetadataResult<TItemType>> GetMetadata(ItemInfo info, CancellationToken cancellationToken);
+    }
+
+    public class ItemInfo
+    {
+        public string Path { get; set; }
+
+        public bool IsInMixedFolder { get; set; }
     }
 }

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

@@ -24,15 +24,6 @@ namespace MediaBrowser.Controller.Providers
         /// <returns>Task.</returns>
         Task RefreshMetadata(IHasMetadata item, MetadataRefreshOptions options, CancellationToken cancellationToken);
 
-        /// <summary>
-        /// Executes the metadata providers.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <param name="force">if set to <c>true</c> [force].</param>
-        /// <returns>Task{System.Boolean}.</returns>
-        Task<ItemUpdateType?> ExecuteMetadataProviders(BaseItem item, CancellationToken cancellationToken, bool force = false);
-
         /// <summary>
         /// Saves the image.
         /// </summary>
@@ -60,12 +51,11 @@ namespace MediaBrowser.Controller.Providers
         /// <summary>
         /// Adds the metadata providers.
         /// </summary>
-        /// <param name="providers">The providers.</param>
         /// <param name="imageProviders">The image providers.</param>
         /// <param name="metadataServices">The metadata services.</param>
         /// <param name="metadataProviders">The metadata providers.</param>
         /// <param name="savers">The savers.</param>
-        void AddParts(IEnumerable<BaseMetadataProvider> providers, IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices, IEnumerable<IMetadataProvider> metadataProviders,
+        void AddParts(IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices, IEnumerable<IMetadataProvider> metadataProviders,
             IEnumerable<IMetadataSaver> savers);
 
         /// <summary>

+ 0 - 11
MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs

@@ -16,17 +16,6 @@ namespace MediaBrowser.Controller.Providers
         /// </summary>
         [Obsolete]
         public bool ForceSave { get; set; }
-
-        /// <summary>
-        /// TODO: deprecate. Keeping this for now, for api compatibility
-        /// </summary>
-        [Obsolete]
-        public bool ResetResolveArgs { get; set; }
-
-        public MetadataRefreshOptions()
-        {
-            ResetResolveArgs = true;
-        }
     }
 
     public class ImageRefreshOptions

+ 0 - 6
MediaBrowser.Model/Dto/BaseItemDto.cs

@@ -152,12 +152,6 @@ namespace MediaBrowser.Model.Dto
         /// <value>The vote count.</value>
         public int? VoteCount { get; set; }
 
-        /// <summary>
-        /// Gets or sets the original run time ticks.
-        /// </summary>
-        /// <value>The original run time ticks.</value>
-        public long? OriginalRunTimeTicks { get; set; }
-
         /// <summary>
         /// Gets or sets the cumulative run time ticks.
         /// </summary>

+ 0 - 5
MediaBrowser.Model/Querying/ItemFields.cs

@@ -71,11 +71,6 @@ namespace MediaBrowser.Model.Querying
         /// </summary>
         Settings,
 
-        /// <summary>
-        /// The original run time ticks
-        /// </summary>
-        OriginalRunTimeTicks,
-
         /// <summary>
         /// The item overview
         /// </summary>

+ 5 - 33
MediaBrowser.Providers/AdultVideos/AdultVideoXmlProvider.cs

@@ -5,11 +5,10 @@ using MediaBrowser.Model.Logging;
 using MediaBrowser.Providers.Movies;
 using System.IO;
 using System.Threading;
-using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.AdultVideos
 {
-    class AdultVideoXmlProvider : BaseXmlProvider, ILocalMetadataProvider<AdultVideo>
+    class AdultVideoXmlProvider : BaseXmlProvider<AdultVideo>
     {
         private readonly ILogger _logger;
 
@@ -19,41 +18,14 @@ namespace MediaBrowser.Providers.AdultVideos
             _logger = logger;
         }
 
-        public async Task<MetadataResult<AdultVideo>> GetMetadata(string path, CancellationToken cancellationToken)
+        protected override void Fetch(AdultVideo item, string path, CancellationToken cancellationToken)
         {
-            path = GetXmlFile(path).FullName;
-
-            var result = new MetadataResult<AdultVideo>();
-
-            await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
-
-            try
-            {
-                result.Item = new AdultVideo();
-
-                new MovieXmlParser(_logger).Fetch(result.Item, path, cancellationToken);
-                result.HasMetadata = true;
-            }
-            catch (FileNotFoundException)
-            {
-                result.HasMetadata = false;
-            }
-            finally
-            {
-                XmlParsingResourcePool.Release();
-            }
-
-            return result;
-        }
-
-        public string Name
-        {
-            get { return "Media Browser Xml"; }
+            new MovieXmlParser(_logger).Fetch(item, path, cancellationToken);
         }
 
-        protected override FileInfo GetXmlFile(string path)
+        protected override FileInfo GetXmlFile(ItemInfo info)
         {
-            return MovieXmlProvider.GetXmlFileInfo(path, FileSystem);
+            return MovieXmlProvider.GetXmlFileInfo(info, FileSystem);
         }
     }
 }

+ 1 - 1
MediaBrowser.Providers/All/LocalImageProvider.cs

@@ -39,7 +39,7 @@ namespace MediaBrowser.Providers.All
             if (locationType == LocationType.FileSystem)
             {
                 // Episode has it's own provider
-                if (item is Episode || item is Audio)
+                if (item.IsOwnedItem || item is Episode || item is Audio)
                 {
                     return false;
                 }

+ 56 - 7
MediaBrowser.Providers/BaseXmlProvider.cs

@@ -3,32 +3,81 @@ using MediaBrowser.Controller.Providers;
 using System;
 using System.IO;
 using System.Threading;
+using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers
 {
-    public abstract class BaseXmlProvider: IHasChangeMonitor
+    public abstract class BaseXmlProvider<T> : ILocalMetadataProvider<T>, IHasChangeMonitor
+        where T : IHasMetadata, new()
     {
-        protected static readonly SemaphoreSlim XmlParsingResourcePool = new SemaphoreSlim(4, 4);
-
         protected IFileSystem FileSystem;
 
+        public async Task<MetadataResult<T>> GetMetadata(ItemInfo info, CancellationToken cancellationToken)
+        {
+            var result = new MetadataResult<T>();
+
+            var file = GetXmlFile(info);
+
+            if (file == null)
+            {
+                return result;
+            }
+
+            var path = file.FullName;
+
+            await XmlProviderUtils.XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+            try
+            {
+                result.Item = new T();
+
+                Fetch(result.Item, path, cancellationToken);
+                result.HasMetadata = true;
+            }
+            catch (FileNotFoundException)
+            {
+                result.HasMetadata = false;
+            }
+            finally
+            {
+                XmlProviderUtils.XmlParsingResourcePool.Release();
+            }
+
+            return result;
+        }
+
+        protected abstract void Fetch(T item, string path, CancellationToken cancellationToken);
+
         protected BaseXmlProvider(IFileSystem fileSystem)
         {
             FileSystem = fileSystem;
         }
 
-        protected abstract FileInfo GetXmlFile(string path);
+        protected abstract FileInfo GetXmlFile(ItemInfo info);
 
         public bool HasChanged(IHasMetadata item, DateTime date)
         {
-            var file = GetXmlFile(item.Path);
+            var file = GetXmlFile(new ItemInfo { IsInMixedFolder = item.IsInMixedFolder, Path = item.Path });
+
+            if (file == null)
+            {
+                return false;
+            }
 
             return FileSystem.GetLastWriteTimeUtc(file) > date;
         }
 
-        public bool HasLocalMetadata(IHasMetadata item)
+        public string Name
         {
-            return GetXmlFile(item.Path).Exists;
+            get
+            {
+                return "Media Browser Xml";
+            }
         }
     }
+
+    static class XmlProviderUtils
+    {
+        internal static readonly SemaphoreSlim XmlParsingResourcePool = new SemaphoreSlim(4, 4);
+    }
 }

+ 5 - 34
MediaBrowser.Providers/BoxSets/BoxSetXmlProvider.cs

@@ -4,14 +4,13 @@ using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Logging;
 using System.IO;
 using System.Threading;
-using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.BoxSets
 {
     /// <summary>
     /// Class BoxSetXmlProvider.
     /// </summary>
-    public class BoxSetXmlProvider : BaseXmlProvider, ILocalMetadataProvider<BoxSet>
+    public class BoxSetXmlProvider : BaseXmlProvider<BoxSet>
     {
         private readonly ILogger _logger;
 
@@ -21,42 +20,14 @@ namespace MediaBrowser.Providers.BoxSets
             _logger = logger;
         }
 
-        public async Task<MetadataResult<BoxSet>> GetMetadata(string path, CancellationToken cancellationToken)
+        protected override void Fetch(BoxSet item, string path, CancellationToken cancellationToken)
         {
-            path = GetXmlFile(path).FullName;
-
-            var result = new MetadataResult<BoxSet>();
-
-            await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
-
-            try
-            {
-                var item = new BoxSet();
-
-                new BaseItemXmlParser<BoxSet>(_logger).Fetch(item, path, cancellationToken);
-                result.HasMetadata = true;
-                result.Item = item;
-            }
-            catch (FileNotFoundException)
-            {
-                result.HasMetadata = false;
-            }
-            finally
-            {
-                XmlParsingResourcePool.Release();
-            }
-
-            return result;
-        }
-
-        public string Name
-        {
-            get { return "Media Browser Xml"; }
+            new BaseItemXmlParser<BoxSet>(_logger).Fetch(item, path, cancellationToken);
         }
 
-        protected override FileInfo GetXmlFile(string path)
+        protected override FileInfo GetXmlFile(ItemInfo info)
         {
-            return new FileInfo(Path.Combine(path, "collection.xml"));
+            return new FileInfo(Path.Combine(info.Path, "collection.xml"));
         }
     }
 }

+ 0 - 56
MediaBrowser.Providers/CollectionFolderImageProvider.cs

@@ -1,56 +0,0 @@
-using MediaBrowser.Common.IO;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Logging;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-
-namespace MediaBrowser.Providers
-{
-    public class CollectionFolderImageProvider : ImageFromMediaLocationProvider
-    {
-        public CollectionFolderImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem)
-            : base(logManager, configurationManager, fileSystem)
-        {
-        }
-
-        public override bool Supports(BaseItem item)
-        {
-            return item is CollectionFolder && item.LocationType == LocationType.FileSystem;
-        }
-
-        public override MetadataProviderPriority Priority
-        {
-            get { return MetadataProviderPriority.Second; }
-        }
-
-        protected override FileSystemInfo GetImage(BaseItem item, ItemResolveArgs args, string filenameWithoutExtension)
-        {
-            return item.PhysicalLocations
-                .Select(i => GetImageFromLocation(i, filenameWithoutExtension))
-                .FirstOrDefault(i => i != null);
-        }
-
-        protected override Guid GetFileSystemStamp(IEnumerable<BaseItem> items)
-        {
-            var files = items.SelectMany(i => i.PhysicalLocations)
-                .Select(i => new DirectoryInfo(i))
-                .SelectMany(i => i.EnumerateFiles("*", SearchOption.TopDirectoryOnly))
-                .Where(i =>
-                {
-                    var ext = i.Extension;
-
-                    return !string.IsNullOrEmpty(ext) &&
-                        BaseItem.SupportedImageExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
-                })
-                .ToList();
-
-            return GetFileSystemStamp(files);
-        }
-    }
-}

+ 0 - 93
MediaBrowser.Providers/FolderProviderFromXml.cs

@@ -1,93 +0,0 @@
-using MediaBrowser.Common.IO;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Logging;
-using System;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Providers
-{
-    /// <summary>
-    /// Provides metadata for Folders and all subclasses by parsing folder.xml
-    /// </summary>
-    public class FolderProviderFromXml : BaseMetadataProvider
-    {
-        private readonly IFileSystem _fileSystem;
-
-        public FolderProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem)
-            : base(logManager, configurationManager)
-        {
-            _fileSystem = fileSystem;
-        }
-
-        /// <summary>
-        /// Supportses the specified item.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
-        public override bool Supports(BaseItem item)
-        {
-            return item.IsFolder && item.LocationType == LocationType.FileSystem;
-        }
-
-        /// <summary>
-        /// Gets the priority.
-        /// </summary>
-        /// <value>The priority.</value>
-        public override MetadataProviderPriority Priority
-        {
-            get { return MetadataProviderPriority.First; }
-        }
-
-        private const string XmlFileName = "folder.xml";
-        protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo)
-        {
-            var xml = item.ResolveArgs.GetMetaFileByPath(Path.Combine(item.MetaLocation, XmlFileName));
-
-            if (xml == null)
-            {
-                return false;
-            }
-
-            return _fileSystem.GetLastWriteTimeUtc(xml) > item.DateLastSaved;
-        }
-
-        /// <summary>
-        /// Fetches metadata and returns true or false indicating if any work that requires persistence was done
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="force">if set to <c>true</c> [force].</param>
-        /// <param name="providerInfo">The provider information.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task{System.Boolean}.</returns>
-        public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
-        {
-            cancellationToken.ThrowIfCancellationRequested();
-
-            var metadataFile = item.ResolveArgs.GetMetaFileByPath(Path.Combine(item.MetaLocation, XmlFileName));
-
-            if (metadataFile != null)
-            {
-                var path = metadataFile.FullName;
-
-                await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
-
-                try
-                {
-                    new BaseItemXmlParser<Folder>(Logger).Fetch((Folder)item, path, cancellationToken);
-                }
-                finally
-                {
-                    XmlParsingResourcePool.Release();
-                }
-            }
-
-            SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
-            return true;
-        }
-    }
-}

+ 52 - 0
MediaBrowser.Providers/Folders/FolderMetadataService.cs

@@ -0,0 +1,52 @@
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Providers.Manager;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Providers.Folders
+{
+    public class FolderMetadataService : MetadataService<Folder, ItemId>
+    {
+        private readonly ILibraryManager _libraryManager;
+
+        public FolderMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager)
+            : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem)
+        {
+            _libraryManager = libraryManager;
+        }
+
+        /// <summary>
+        /// Merges the specified source.
+        /// </summary>
+        /// <param name="source">The source.</param>
+        /// <param name="target">The target.</param>
+        /// <param name="lockedFields">The locked fields.</param>
+        /// <param name="replaceData">if set to <c>true</c> [replace data].</param>
+        /// <param name="mergeMetadataSettings">if set to <c>true</c> [merge metadata settings].</param>
+        protected override void MergeData(Folder source, Folder target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+        {
+            ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
+        }
+
+        protected override Task SaveItem(Folder item, ItemUpdateType reason, CancellationToken cancellationToken)
+        {
+            return _libraryManager.UpdateItem(item, reason, cancellationToken);
+        }
+
+        public override int Order
+        {
+            get
+            {
+                // Make sure the type-specific services get picked first
+                return 10;
+            }
+        }
+    }
+}

+ 33 - 0
MediaBrowser.Providers/Folders/FolderXmlProvider.cs

@@ -0,0 +1,33 @@
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Logging;
+using System.IO;
+using System.Threading;
+
+namespace MediaBrowser.Providers.Folders
+{
+    /// <summary>
+    /// Provides metadata for Folders and all subclasses by parsing folder.xml
+    /// </summary>
+    public class FolderXmlProvider : BaseXmlProvider<Folder>
+    {
+        private readonly ILogger _logger;
+
+        public FolderXmlProvider(IFileSystem fileSystem, ILogger logger)
+            : base(fileSystem)
+        {
+            _logger = logger;
+        }
+
+        protected override void Fetch(Folder item, string path, CancellationToken cancellationToken)
+        {
+            new BaseItemXmlParser<Folder>(_logger).Fetch(item, path, cancellationToken);
+        }
+
+        protected override FileInfo GetXmlFile(ItemInfo info)
+        {
+            return new FileInfo(Path.Combine(info.Path, "folder.xml"));
+        }
+    }
+}

+ 1 - 1
MediaBrowser.Providers/UserRootFolderNameProvider.cs → MediaBrowser.Providers/Folders/UserRootFolderNameProvider.cs

@@ -7,7 +7,7 @@ using System.IO;
 using System.Threading;
 using System.Threading.Tasks;
 
-namespace MediaBrowser.Providers
+namespace MediaBrowser.Providers.Folders
 {
     public class UserRootFolderNameProvider : BaseMetadataProvider
     {

+ 5 - 34
MediaBrowser.Providers/Games/GameSystemXmlProvider.cs

@@ -4,11 +4,10 @@ using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Logging;
 using System.IO;
 using System.Threading;
-using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.Games
 {
-    public class GameSystemXmlProvider : BaseXmlProvider, ILocalMetadataProvider<GameSystem>
+    public class GameSystemXmlProvider : BaseXmlProvider<GameSystem>
     {
         private readonly ILogger _logger;
 
@@ -18,42 +17,14 @@ namespace MediaBrowser.Providers.Games
             _logger = logger;
         }
 
-        public async Task<MetadataResult<GameSystem>> GetMetadata(string path, CancellationToken cancellationToken)
+        protected override void Fetch(GameSystem item, string path, CancellationToken cancellationToken)
         {
-            path = GetXmlFile(path).FullName;
-
-            var result = new MetadataResult<GameSystem>();
-
-            await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
-
-            try
-            {
-                var item = new GameSystem();
-
-                new GameSystemXmlParser(_logger).Fetch(item, path, cancellationToken);
-                result.HasMetadata = true;
-                result.Item = item;
-            }
-            catch (FileNotFoundException)
-            {
-                result.HasMetadata = false;
-            }
-            finally
-            {
-                XmlParsingResourcePool.Release();
-            }
-
-            return result;
-        }
-
-        public string Name
-        {
-            get { return "Media Browser Xml"; }
+            new GameSystemXmlParser(_logger).Fetch(item, path, cancellationToken);
         }
 
-        protected override FileInfo GetXmlFile(string path)
+        protected override FileInfo GetXmlFile(ItemInfo info)
         {
-            return new FileInfo(Path.Combine(path, "gamesystem.xml"));
+            return new FileInfo(Path.Combine(info.Path, "gamesystem.xml"));
         }
     }
 }

+ 8 - 37
MediaBrowser.Providers/Games/GameXmlProvider.cs

@@ -4,11 +4,10 @@ using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Logging;
 using System.IO;
 using System.Threading;
-using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.Games
 {
-    public class GameXmlProvider : BaseXmlProvider, ILocalMetadataProvider<Game>
+    public class GameXmlProvider : BaseXmlProvider<Game>
     {
         private readonly ILogger _logger;
 
@@ -18,57 +17,29 @@ namespace MediaBrowser.Providers.Games
             _logger = logger;
         }
 
-        public async Task<MetadataResult<Game>> GetMetadata(string path, CancellationToken cancellationToken)
+        protected override void Fetch(Game item, string path, CancellationToken cancellationToken)
         {
-            path = GetXmlFile(path).FullName;
-
-            var result = new MetadataResult<Game>();
-
-            await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
-
-            try
-            {
-                var item = new Game();
-
-                new GameXmlParser(_logger).Fetch(item, path, cancellationToken);
-                result.HasMetadata = true;
-                result.Item = item;
-            }
-            catch (FileNotFoundException)
-            {
-                result.HasMetadata = false;
-            }
-            finally
-            {
-                XmlParsingResourcePool.Release();
-            }
-
-            return result;
-        }
-
-        public string Name
-        {
-            get { return "Media Browser Xml"; }
+            new GameXmlParser(_logger).Fetch(item, path, cancellationToken);
         }
 
-        protected override FileInfo GetXmlFile(string path)
+        protected override FileInfo GetXmlFile(ItemInfo info)
         {
-            var fileInfo = FileSystem.GetFileSystemInfo(path);
+            var fileInfo = FileSystem.GetFileSystemInfo(info.Path);
 
             var directoryInfo = fileInfo as DirectoryInfo;
 
             if (directoryInfo == null)
             {
-                directoryInfo = new DirectoryInfo(Path.GetDirectoryName(path));
+                directoryInfo = new DirectoryInfo(Path.GetDirectoryName(info.Path));
             }
 
             var directoryPath = directoryInfo.FullName;
 
-            var specificFile = Path.Combine(directoryPath, Path.GetFileNameWithoutExtension(path) + ".xml");
+            var specificFile = Path.Combine(directoryPath, Path.GetFileNameWithoutExtension(info.Path) + ".xml");
 
             var file = new FileInfo(specificFile);
 
-            return file.Exists ? file : new FileInfo(Path.Combine(directoryPath, "game.xml"));
+            return info.IsInMixedFolder || file.Exists ? file : new FileInfo(Path.Combine(directoryPath, "game.xml"));
         }
     }
 }

+ 0 - 637
MediaBrowser.Providers/ImageFromMediaLocationProvider.cs

@@ -1,637 +0,0 @@
-using MediaBrowser.Common.IO;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Movies;
-using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Logging;
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Providers
-{
-    /// <summary>
-    /// Provides images for all types by looking for standard images - folder, backdrop, logo, etc.
-    /// </summary>
-    public class ImageFromMediaLocationProvider : BaseMetadataProvider
-    {
-        protected readonly IFileSystem FileSystem;
-
-        public ImageFromMediaLocationProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem)
-            : base(logManager, configurationManager)
-        {
-            FileSystem = fileSystem;
-        }
-
-        public override ItemUpdateType ItemUpdateType
-        {
-            get
-            {
-                return ItemUpdateType.ImageUpdate;
-            }
-        }
-
-        /// <summary>
-        /// Supportses the specified item.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
-        public override bool Supports(BaseItem item)
-        {
-            if (item.LocationType == LocationType.FileSystem)
-            {
-                if (item.ResolveArgs.IsDirectory)
-                {
-                    return true;
-                }
-
-                return item.IsInMixedFolder && item.Parent != null && !(item is Episode);
-            }
-            if (item.LocationType == LocationType.Virtual)
-            {
-                var season = item as Season;
-
-                if (season != null)
-                {
-                    var series = season.Series;
-
-                    if (series != null && series.LocationType == LocationType.FileSystem)
-                    {
-                        return true;
-                    }
-                }
-            }
-
-            return false;
-        }
-
-        protected override IEnumerable<BaseItem> GetItemsForFileStampComparison(BaseItem item)
-        {
-            var season = item as Season;
-            if (season != null)
-            {
-                var list = new List<BaseItem>();
-
-                if (season.LocationType == LocationType.FileSystem)
-                {
-                    list.Add(season);
-                }
-
-                var series = season.Series;
-                if (series != null && series.LocationType == LocationType.FileSystem)
-                {
-                    list.Add(series);
-                }
-
-                return list;
-            }
-
-            return base.GetItemsForFileStampComparison(item);
-        }
-
-        /// <summary>
-        /// Gets the priority.
-        /// </summary>
-        /// <value>The priority.</value>
-        public override MetadataProviderPriority Priority
-        {
-            get { return MetadataProviderPriority.First; }
-        }
-
-        /// <summary>
-        /// Returns true or false indicating if the provider should refresh when the contents of it's directory changes
-        /// </summary>
-        /// <value><c>true</c> if [refresh on file system stamp change]; otherwise, <c>false</c>.</value>
-        protected override bool RefreshOnFileSystemStampChange
-        {
-            get
-            {
-                return true;
-            }
-        }
-
-        /// <summary>
-        /// Gets the filestamp extensions.
-        /// </summary>
-        /// <value>The filestamp extensions.</value>
-        protected override string[] FilestampExtensions
-        {
-            get
-            {
-                return BaseItem.SupportedImageExtensions;
-            }
-        }
-
-        /// <summary>
-        /// Fetches metadata and returns true or false indicating if any work that requires persistence was done
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="force">if set to <c>true</c> [force].</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task{System.Boolean}.</returns>
-        public override Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
-        {
-            cancellationToken.ThrowIfCancellationRequested();
-
-            // Make sure current image paths still exist
-            item.ValidateImages();
-
-            cancellationToken.ThrowIfCancellationRequested();
-
-            var args = GetResolveArgsContainingImages(item);
-
-            PopulateBaseItemImages(item, args);
-
-            SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
-            return TrueTaskResult;
-        }
-
-        private ItemResolveArgs GetResolveArgsContainingImages(BaseItem item)
-        {
-            if (item.LocationType != LocationType.FileSystem)
-            {
-                return null;
-            }
-
-            if (item.IsInMixedFolder)
-            {
-                if (item.Parent == null)
-                {
-                    return item.ResolveArgs;
-                }
-                return item.Parent.ResolveArgs;
-            }
-
-            return item.ResolveArgs;
-        }
-
-        /// <summary>
-        /// Gets the image.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="args">The args.</param>
-        /// <param name="filenameWithoutExtension">The filename without extension.</param>
-        /// <returns>FileSystemInfo.</returns>
-        protected virtual FileSystemInfo GetImage(BaseItem item, ItemResolveArgs args, string filenameWithoutExtension)
-        {
-            if (string.IsNullOrEmpty(item.MetaLocation))
-            {
-                return null;
-            }
-
-            return BaseItem.SupportedImageExtensions
-                .Select(i => args.GetMetaFileByPath(GetFullImagePath(item, args, filenameWithoutExtension, i)))
-                .FirstOrDefault(i => i != null);
-        }
-
-        protected virtual string GetFullImagePath(BaseItem item, ItemResolveArgs args, string filenameWithoutExtension, string extension)
-        {
-            var path = item.MetaLocation;
-
-            if (item.IsInMixedFolder)
-            {
-                var pathFilenameWithoutExtension = Path.GetFileNameWithoutExtension(item.Path);
-
-                // If the image filename and path file name match, just look for an image using the same full path as the item
-                if (string.Equals(pathFilenameWithoutExtension, filenameWithoutExtension))
-                {
-                    return Path.ChangeExtension(item.Path, extension);
-                }
-
-                return Path.Combine(path, pathFilenameWithoutExtension + "-" + filenameWithoutExtension + extension);
-            }
-
-            return Path.Combine(path, filenameWithoutExtension + extension);
-        }
-
-        private readonly CultureInfo _usCulture = new CultureInfo("en-US");
-
-        /// <summary>
-        /// Fills in image paths based on files win the folder
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="args">The args.</param>
-        private void PopulateBaseItemImages(BaseItem item, ItemResolveArgs args)
-        {
-            PopulatePrimaryImage(item, args);
-
-            // Logo Image
-            var image = GetImage(item, args, "logo");
-
-            if (image != null)
-            {
-                item.SetImagePath(ImageType.Logo, image.FullName);
-            }
-
-            // Clearart
-            image = GetImage(item, args, "clearart");
-
-            if (image != null)
-            {
-                item.SetImagePath(ImageType.Art, image.FullName);
-            }
-
-            // Disc
-            image = GetImage(item, args, "disc") ??
-                GetImage(item, args, "cdart");
-
-            if (image != null)
-            {
-                item.SetImagePath(ImageType.Disc, image.FullName);
-            }
-
-            // Box Image
-            image = GetImage(item, args, "box");
-
-            if (image != null)
-            {
-                item.SetImagePath(ImageType.Box, image.FullName);
-            }
-
-            // BoxRear Image
-            image = GetImage(item, args, "boxrear");
-
-            if (image != null)
-            {
-                item.SetImagePath(ImageType.BoxRear, image.FullName);
-            }
-
-            // Thumbnail Image
-            image = GetImage(item, args, "menu");
-
-            if (image != null)
-            {
-                item.SetImagePath(ImageType.Menu, image.FullName);
-            }
-
-            PopulateBanner(item, args);
-            PopulateThumb(item, args);
-
-            // Backdrop Image
-            PopulateBackdrops(item, args);
-            PopulateScreenshots(item, args);
-        }
-
-        private void PopulatePrimaryImage(BaseItem item, ItemResolveArgs args)
-        {
-            // Primary Image
-            var image = GetImage(item, args, "folder") ??
-                GetImage(item, args, "poster") ??
-                GetImage(item, args, "cover") ??
-                GetImage(item, args, "default");
-
-            // Support plex/xbmc convention
-            if (image == null && item is Series)
-            {
-                image = GetImage(item, args, "show");
-            }
-
-            // Support plex/xbmc convention
-            if (image == null)
-            {
-                // Supprt xbmc conventions
-                var season = item as Season;
-                if (season != null && item.IndexNumber.HasValue && season.Series.LocationType == LocationType.FileSystem)
-                {
-                    image = GetSeasonImageFromSeriesFolder(season, "-poster");
-                }
-            }
-
-            // Support plex/xbmc convention
-            if (image == null && (item is Movie || item is MusicVideo || item is AdultVideo))
-            {
-                image = GetImage(item, args, "movie");
-            }
-
-            // Look for a file with the same name as the item
-            if (image == null && !string.IsNullOrEmpty(item.Path))
-            {
-                var name = Path.GetFileNameWithoutExtension(item.Path);
-
-                if (!string.IsNullOrEmpty(name))
-                {
-                    image = GetImage(item, args, name) ??
-                        GetImage(item, args, name + "-poster");
-                }
-            }
-
-            if (image != null)
-            {
-                item.SetImagePath(ImageType.Primary, image.FullName);
-            }
-        }
-
-        /// <summary>
-        /// Populates the banner.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="args">The args.</param>
-        private void PopulateBanner(BaseItem item, ItemResolveArgs args)
-        {
-            // Banner Image
-            var image = GetImage(item, args, "banner");
-
-            if (image == null)
-            {
-                // Supprt xbmc conventions
-                var season = item as Season;
-                if (season != null && item.IndexNumber.HasValue && season.Series.LocationType == LocationType.FileSystem)
-                {
-                    image = GetSeasonImageFromSeriesFolder(season, "-banner");
-                }
-            }
-
-            if (image != null)
-            {
-                item.SetImagePath(ImageType.Banner, image.FullName);
-            }
-        }
-
-        /// <summary>
-        /// Populates the thumb.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="args">The args.</param>
-        private void PopulateThumb(BaseItem item, ItemResolveArgs args)
-        {
-            // Thumbnail Image
-            var image = GetImage(item, args, "thumb") ??
-                GetImage(item, args, "landscape");
-
-            if (image == null)
-            {
-                // Supprt xbmc conventions
-                var season = item as Season;
-                if (season != null && item.IndexNumber.HasValue && season.Series.LocationType == LocationType.FileSystem)
-                {
-                    image = GetSeasonImageFromSeriesFolder(season, "-landscape");
-                }
-            }
-
-            if (image != null)
-            {
-                item.SetImagePath(ImageType.Thumb, image.FullName);
-            }
-
-        }
-
-        /// <summary>
-        /// Populates the backdrops.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="args">The args.</param>
-        private void PopulateBackdrops(BaseItem item, ItemResolveArgs args)
-        {
-            var backdropFiles = new List<string>();
-
-            PopulateBackdrops(item, args, backdropFiles, "backdrop", "backdrop");
-
-            // Support {name}-fanart.ext
-            if (!string.IsNullOrEmpty(item.Path))
-            {
-                var name = Path.GetFileNameWithoutExtension(item.Path);
-
-                if (!string.IsNullOrEmpty(name))
-                {
-                    var image = GetImage(item, args, name + "-fanart");
-
-                    if (image != null)
-                    {
-                        backdropFiles.Add(image.FullName);
-                    }
-                }
-            }
-
-            // Support plex/xbmc conventions
-            PopulateBackdrops(item, args, backdropFiles, "fanart", "fanart-");
-            PopulateBackdrops(item, args, backdropFiles, "background", "background-");
-            PopulateBackdrops(item, args, backdropFiles, "art", "art-");
-
-            var season = item as Season;
-            if (season != null && item.IndexNumber.HasValue && season.Series.LocationType == LocationType.FileSystem)
-            {
-                var image = GetSeasonImageFromSeriesFolder(season, "-fanart");
-
-                if (image != null)
-                {
-                    backdropFiles.Add(image.FullName);
-                }
-            }
-
-            if (item.LocationType == LocationType.FileSystem)
-            {
-                PopulateBackdropsFromExtraFanart(args, backdropFiles);
-            }
-
-            if (backdropFiles.Count > 0)
-            {
-                item.BackdropImagePaths = backdropFiles;
-            }
-        }
-
-        private FileSystemInfo GetSeasonImageFromSeriesFolder(Season season, string imageSuffix)
-        {
-            var series = season.Series;
-            var seriesFolderArgs = series.ResolveArgs;
-
-            var seasonNumber = season.IndexNumber;
-
-            string filename = null;
-            FileSystemInfo image;
-
-            if (seasonNumber.HasValue)
-            {
-                var seasonMarker = seasonNumber.Value == 0
-                                       ? "-specials"
-                                       : seasonNumber.Value.ToString("00", _usCulture);
-
-                // Get this one directly from the file system since we have to go up a level
-                filename = "season" + seasonMarker + imageSuffix;
-
-                image = GetImage(series, seriesFolderArgs, filename);
-
-                if (image != null && image.Exists)
-                {
-                    return image;
-                }
-            }
-
-            var previousFilename = filename;
-
-            // Try using the season name
-            filename = season.Name.ToLower().Replace(" ", string.Empty) + imageSuffix;
-
-            if (!string.Equals(previousFilename, filename))
-            {
-                image = GetImage(series, seriesFolderArgs, filename);
-
-                if (image != null && image.Exists)
-                {
-                    return image;
-                }
-            }
-
-            return null;
-        }
-
-        /// <summary>
-        /// Populates the backdrops from extra fanart.
-        /// </summary>
-        /// <param name="args">The args.</param>
-        /// <param name="backdrops">The backdrops.</param>
-        private void PopulateBackdropsFromExtraFanart(ItemResolveArgs args, List<string> backdrops)
-        {
-            if (!args.IsDirectory)
-            {
-                return;
-            }
-
-            if (args.ContainsFileSystemEntryByName("extrafanart"))
-            {
-                var path = Path.Combine(args.Path, "extrafanart");
-
-                var imageFiles = Directory.EnumerateFiles(path, "*", SearchOption.TopDirectoryOnly)
-                    .Where(i =>
-                    {
-                        var extension = Path.GetExtension(i);
-
-                        if (string.IsNullOrEmpty(extension))
-                        {
-                            return false;
-                        }
-
-                        return BaseItem.SupportedImageExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
-                    })
-                    .ToList();
-
-                backdrops.AddRange(imageFiles);
-            }
-        }
-
-        /// <summary>
-        /// Populates the backdrops.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="args">The args.</param>
-        /// <param name="backdropFiles">The backdrop files.</param>
-        /// <param name="filename">The filename.</param>
-        /// <param name="numberedSuffix">The numbered suffix.</param>
-        private void PopulateBackdrops(BaseItem item, ItemResolveArgs args, List<string> backdropFiles, string filename, string numberedSuffix)
-        {
-            var image = GetImage(item, args, filename);
-
-            if (image != null)
-            {
-                backdropFiles.Add(image.FullName);
-            }
-
-            var unfound = 0;
-            for (var i = 1; i <= 20; i++)
-            {
-                // Backdrop Image
-                image = GetImage(item, args, numberedSuffix + i);
-
-                if (image != null)
-                {
-                    backdropFiles.Add(image.FullName);
-                }
-                else
-                {
-                    unfound++;
-
-                    if (unfound >= 3)
-                    {
-                        break;
-                    }
-                }
-            }
-        }
-
-        /// <summary>
-        /// Populates the screenshots.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="args">The args.</param>
-        private void PopulateScreenshots(BaseItem item, ItemResolveArgs args)
-        {
-            // Screenshot Image
-            var image = GetImage(item, args, "screenshot");
-
-            var screenshotFiles = new List<string>();
-
-            if (image != null)
-            {
-                screenshotFiles.Add(image.FullName);
-            }
-
-            var unfound = 0;
-            for (var i = 1; i <= 20; i++)
-            {
-                // Screenshot Image
-                image = GetImage(item, args, "screenshot" + i);
-
-                if (image != null)
-                {
-                    screenshotFiles.Add(image.FullName);
-                }
-                else
-                {
-                    unfound++;
-
-                    if (unfound >= 3)
-                    {
-                        break;
-                    }
-                }
-            }
-
-            if (screenshotFiles.Count > 0)
-            {
-                var hasScreenshots = item as IHasScreenshots;
-                if (hasScreenshots != null)
-                {
-                    hasScreenshots.ScreenshotImagePaths = screenshotFiles;
-                }
-            }
-        }
-
-        protected FileSystemInfo GetImageFromLocation(string path, string filenameWithoutExtension)
-        {
-            try
-            {
-                var files = new DirectoryInfo(path)
-                    .EnumerateFiles()
-                    .Where(i =>
-                    {
-                        var fileName = Path.GetFileNameWithoutExtension(i.FullName);
-
-                        if (!string.Equals(fileName, filenameWithoutExtension, StringComparison.OrdinalIgnoreCase))
-                        {
-                            return false;
-                        }
-
-                        var ext = i.Extension;
-
-                        return !string.IsNullOrEmpty(ext) &&
-                            BaseItem.SupportedImageExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
-                    })
-                    .ToList();
-
-                return BaseItem.SupportedImageExtensions
-                    .Select(ext => files.FirstOrDefault(i => string.Equals(ext, i.Extension, StringComparison.OrdinalIgnoreCase)))
-                    .FirstOrDefault(file => file != null);
-            }
-            catch (DirectoryNotFoundException)
-            {
-                return null;
-            }
-        }
-    }
-}

+ 0 - 100
MediaBrowser.Providers/ImagesByNameProvider.cs

@@ -1,100 +0,0 @@
-using System.Collections.Generic;
-using MediaBrowser.Common.IO;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Logging;
-using System;
-using System.IO;
-using System.Linq;
-
-namespace MediaBrowser.Providers
-{
-    /// <summary>
-    /// Provides images for generic types by looking for standard images in the IBN
-    /// </summary>
-    public class ImagesByNameProvider : ImageFromMediaLocationProvider
-    {
-        public ImagesByNameProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IFileSystem fileSystem)
-            : base(logManager, configurationManager, fileSystem)
-        {
-        }
-
-        /// <summary>
-        /// Supportses the specified item.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
-        public override bool Supports(BaseItem item)
-        {
-            // Only run for these generic types since we are expensive in file i/o
-            return item is ICollectionFolder;
-        }
-
-        /// <summary>
-        /// Gets the priority.
-        /// </summary>
-        /// <value>The priority.</value>
-        public override MetadataProviderPriority Priority
-        {
-            get
-            {
-                return MetadataProviderPriority.Last;
-            }
-        }
-
-        /// <summary>
-        /// Gets the location.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns>System.String.</returns>
-        protected string GetLocation(BaseItem item)
-        {
-            var name = FileSystem.GetValidFilename(item.Name);
-
-            return Path.Combine(ConfigurationManager.ApplicationPaths.GeneralPath, name);
-        }
-
-        /// <summary>
-        /// Gets the image.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="args">The args.</param>
-        /// <param name="filenameWithoutExtension">The filename without extension.</param>
-        /// <returns>FileSystemInfo.</returns>
-        protected override FileSystemInfo GetImage(BaseItem item, ItemResolveArgs args, string filenameWithoutExtension)
-        {
-            var location = GetLocation(item);
-
-            return GetImageFromLocation(location, filenameWithoutExtension);
-        }
-
-        protected override Guid GetFileSystemStamp(IEnumerable<BaseItem> items)
-        {
-            var location = GetLocation(items.First());
-
-            try
-            {
-                var files = new DirectoryInfo(location)
-                    .EnumerateFiles("*", SearchOption.TopDirectoryOnly)
-                    .Where(i =>
-                    {
-                        var ext = i.Extension;
-
-                        return !string.IsNullOrEmpty(ext) &&
-                            BaseItem.SupportedImageExtensions.Contains(ext, StringComparer.OrdinalIgnoreCase);
-                    })
-                    .ToList();
-
-                return GetFileSystemStamp(files);
-            }
-            catch (DirectoryNotFoundException)
-            {
-                // User doesn't have the folder. No need to log or blow up
-
-                return Guid.Empty;
-            }
-        }
-    }
-}

+ 5 - 34
MediaBrowser.Providers/LiveTv/ChannelXmlProvider.cs

@@ -4,11 +4,10 @@ using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Logging;
 using System.IO;
 using System.Threading;
-using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.LiveTv
 {
-    public class ChannelXmlProvider : BaseXmlProvider, ILocalMetadataProvider<LiveTvChannel>
+    public class ChannelXmlProvider : BaseXmlProvider<LiveTvChannel>
     {
         private readonly ILogger _logger;
 
@@ -18,42 +17,14 @@ namespace MediaBrowser.Providers.LiveTv
             _logger = logger;
         }
 
-        public async Task<MetadataResult<LiveTvChannel>> GetMetadata(string path, CancellationToken cancellationToken)
+        protected override void Fetch(LiveTvChannel item, string path, CancellationToken cancellationToken)
         {
-            path = GetXmlFile(path).FullName;
-
-            var result = new MetadataResult<LiveTvChannel>();
-
-            await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
-
-            try
-            {
-                var item = new LiveTvChannel();
-
-                new BaseItemXmlParser<LiveTvChannel>(_logger).Fetch(item, path, cancellationToken);
-                result.HasMetadata = true;
-                result.Item = item;
-            }
-            catch (FileNotFoundException)
-            {
-                result.HasMetadata = false;
-            }
-            finally
-            {
-                XmlParsingResourcePool.Release();
-            }
-
-            return result;
-        }
-
-        public string Name
-        {
-            get { return "Media Browser Xml"; }
+            new BaseItemXmlParser<LiveTvChannel>(_logger).Fetch(item, path, cancellationToken);
         }
 
-        protected override FileInfo GetXmlFile(string path)
+        protected override FileInfo GetXmlFile(ItemInfo info)
         {
-            return new FileInfo(Path.Combine(path, "channel.xml"));
+            return new FileInfo(Path.Combine(info.Path, "channel.xml"));
         }
     }
 }

+ 13 - 8
MediaBrowser.Providers/Manager/ImageSaver.cs

@@ -379,14 +379,19 @@ namespace MediaBrowser.Providers.Manager
 
             if (saveLocally)
             {
-                if (item.IsInMixedFolder && !(item is Episode))
+                if (item is Episode)
+                {
+                    path = Path.Combine(Path.GetDirectoryName(item.Path), "metadata", filename + extension);
+                }
+
+                else if (item.IsInMixedFolder)
                 {
                     path = GetSavePathForItemInMixedFolder(item, type, filename, extension);
                 }
 
                 if (string.IsNullOrEmpty(path))
                 {
-                    path = Path.Combine(item.MetaLocation, filename + extension);
+                    path = Path.Combine(item.ContainingFolderPath, filename + extension);
                 }
             }
 
@@ -468,7 +473,7 @@ namespace MediaBrowser.Providers.Manager
 
                     return new[]
                         {
-                            Path.Combine(item.MetaLocation, "fanart" + extension)
+                            Path.Combine(item.ContainingFolderPath, "fanart" + extension)
                         };
                 }
 
@@ -483,8 +488,8 @@ namespace MediaBrowser.Providers.Manager
 
                 return new[]
                     {
-                        Path.Combine(item.MetaLocation, "extrafanart", extraFanartFilename + extension),
-                        Path.Combine(item.MetaLocation, "extrathumbs", "thumb" + outputIndex.ToString(UsCulture) + extension)
+                        Path.Combine(item.ContainingFolderPath, "extrafanart", extraFanartFilename + extension),
+                        Path.Combine(item.ContainingFolderPath, "extrathumbs", "thumb" + outputIndex.ToString(UsCulture) + extension)
                     };
             }
 
@@ -519,10 +524,10 @@ namespace MediaBrowser.Providers.Manager
 
                 if (item is MusicAlbum || item is MusicArtist)
                 {
-                    return new[] { Path.Combine(item.MetaLocation, "folder" + extension) };
+                    return new[] { Path.Combine(item.ContainingFolderPath, "folder" + extension) };
                 }
 
-                return new[] { Path.Combine(item.MetaLocation, "poster" + extension) };
+                return new[] { Path.Combine(item.ContainingFolderPath, "poster" + extension) };
             }
 
             if (type == ImageType.Banner)
@@ -561,7 +566,7 @@ namespace MediaBrowser.Providers.Manager
                     return new[] { GetSavePathForItemInMixedFolder(item, type, "landscape", extension) };
                 }
 
-                return new[] { Path.Combine(item.MetaLocation, "landscape" + extension) };
+                return new[] { Path.Combine(item.ContainingFolderPath, "landscape" + extension) };
             }
 
             // All other paths are the same

+ 2 - 2
MediaBrowser.Providers/Manager/ItemImageProvider.cs

@@ -93,8 +93,6 @@ namespace MediaBrowser.Providers.Manager
         /// <returns>Task.</returns>
         private async Task RefreshFromProvider(IHasImages item, IDynamicImageProvider provider, MetadataOptions savedOptions, RefreshResult result, CancellationToken cancellationToken)
         {
-            _logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name);
-
             try
             {
                 var images = provider.GetSupportedImages(item);
@@ -103,6 +101,8 @@ namespace MediaBrowser.Providers.Manager
                 {
                     if (!item.HasImage(imageType) && savedOptions.IsEnabled(imageType))
                     {
+                        _logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name);
+
                         var response = await provider.GetImage(item, imageType, cancellationToken).ConfigureAwait(false);
 
                         if (response.HasImage)

+ 11 - 5
MediaBrowser.Providers/Manager/MetadataService.cs

@@ -88,12 +88,16 @@ namespace MediaBrowser.Providers.Manager
             // Next run metadata providers
             if (refreshOptions.MetadataRefreshMode != MetadataRefreshMode.None)
             {
-                updateType = updateType | BeforeMetadataRefresh(itemOfType);
-
                 var providers = GetProviders(item, refreshResult.DateLastMetadataRefresh.HasValue, refreshOptions).ToList();
 
+                if (providers.Count > 0 || !refreshResult.DateLastMetadataRefresh.HasValue)
+                {
+                    updateType = updateType | BeforeMetadataRefresh(itemOfType);
+                }
+
                 if (providers.Count > 0)
                 {
+
                     var result = await RefreshWithProviders(itemOfType, refreshOptions, providers, cancellationToken).ConfigureAwait(false);
 
                     updateType = updateType | result.UpdateType;
@@ -145,7 +149,7 @@ namespace MediaBrowser.Providers.Manager
         {
             var type = item.GetType().Name;
             return ServerConfigurationManager.Configuration.MetadataOptions
-                .FirstOrDefault(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) ?? 
+                .FirstOrDefault(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase)) ??
                 _defaultOptions;
         }
 
@@ -278,9 +282,11 @@ namespace MediaBrowser.Providers.Manager
             {
                 Logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name);
 
+                var itemInfo = new ItemInfo { Path = item.Path, IsInMixedFolder = item.IsInMixedFolder };
+
                 try
                 {
-                    var localItem = await provider.GetMetadata(item.Path, cancellationToken).ConfigureAwait(false);
+                    var localItem = await provider.GetMetadata(itemInfo, cancellationToken).ConfigureAwait(false);
 
                     if (localItem.HasMetadata)
                     {
@@ -327,7 +333,7 @@ namespace MediaBrowser.Providers.Manager
             {
                 await RunCustomProvider(provider, item, refreshResult, cancellationToken).ConfigureAwait(false);
             }
-            
+
             return refreshResult;
         }
 

+ 15 - 181
MediaBrowser.Providers/Manager/ProviderManager.cs

@@ -48,12 +48,6 @@ namespace MediaBrowser.Providers.Manager
         /// <value>The configuration manager.</value>
         private IServerConfigurationManager ConfigurationManager { get; set; }
 
-        /// <summary>
-        /// Gets the list of currently registered metadata prvoiders
-        /// </summary>
-        /// <value>The metadata providers enumerable.</value>
-        private BaseMetadataProvider[] MetadataProviders { get; set; }
-
         private IImageProvider[] ImageProviders { get; set; }
 
         private readonly IFileSystem _fileSystem;
@@ -86,15 +80,12 @@ namespace MediaBrowser.Providers.Manager
         /// <summary>
         /// Adds the metadata providers.
         /// </summary>
-        /// <param name="providers">The providers.</param>
         /// <param name="imageProviders">The image providers.</param>
         /// <param name="metadataServices">The metadata services.</param>
         /// <param name="metadataProviders">The metadata providers.</param>
         /// <param name="metadataSavers">The metadata savers.</param>
-        public void AddParts(IEnumerable<BaseMetadataProvider> providers, IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices, IEnumerable<IMetadataProvider> metadataProviders, IEnumerable<IMetadataSaver> metadataSavers)
+        public void AddParts(IEnumerable<IImageProvider> imageProviders, IEnumerable<IMetadataService> metadataServices, IEnumerable<IMetadataProvider> metadataProviders, IEnumerable<IMetadataSaver> metadataSavers)
         {
-            MetadataProviders = providers.OrderBy(e => e.Priority).ToArray();
-
             ImageProviders = imageProviders.ToArray();
 
             _metadataServices = metadataServices.OrderBy(i => i.Order).ToArray();
@@ -111,174 +102,8 @@ namespace MediaBrowser.Providers.Manager
                 return service.RefreshMetadata(item, options, cancellationToken);
             }
 
-            return ((BaseItem)item).RefreshMetadataDirect(cancellationToken, options.ForceSave, options.ReplaceAllMetadata);
-        }
-
-        /// <summary>
-        /// Runs all metadata providers for an entity, and returns true or false indicating if at least one was refreshed and requires persistence
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <param name="force">if set to <c>true</c> [force].</param>
-        /// <returns>Task{System.Boolean}.</returns>
-        /// <exception cref="System.ArgumentNullException">item</exception>
-        public async Task<ItemUpdateType?> ExecuteMetadataProviders(BaseItem item, CancellationToken cancellationToken, bool force = false)
-        {
-            if (item == null)
-            {
-                throw new ArgumentNullException("item");
-            }
-
-            ItemUpdateType? result = null;
-
-            cancellationToken.ThrowIfCancellationRequested();
-
-            var enableInternetProviders = ConfigurationManager.Configuration.EnableInternetProviders;
-
-            var providerHistories = item.DateLastSaved == default(DateTime) ?
-                new List<BaseProviderInfo>() :
-                _providerRepo.GetProviderHistory(item.Id).ToList();
-
-            // Run the normal providers sequentially in order of priority
-            foreach (var provider in MetadataProviders)
-            {
-                cancellationToken.ThrowIfCancellationRequested();
-
-                if (!ProviderSupportsItem(provider, item))
-                {
-                    continue;
-                }
-
-                // Skip if internet providers are currently disabled
-                if (provider.RequiresInternet && !enableInternetProviders)
-                {
-                    continue;
-                }
-
-                // Put this check below the await because the needs refresh of the next tier of providers may depend on the previous ones running
-                //  This is the case for the fan art provider which depends on the movie and tv providers having run before them
-                if (provider.RequiresInternet && item.DontFetchMeta && provider.EnforceDontFetchMetadata)
-                {
-                    continue;
-                }
-
-                var providerInfo = providerHistories.FirstOrDefault(i => i.ProviderId == provider.Id);
-
-                if (providerInfo == null)
-                {
-                    providerInfo = new BaseProviderInfo
-                    {
-                        ProviderId = provider.Id
-                    };
-                    providerHistories.Add(providerInfo);
-                }
-
-                try
-                {
-                    if (!force && !provider.NeedsRefresh(item, providerInfo))
-                    {
-                        continue;
-                    }
-                }
-                catch (Exception ex)
-                {
-                    _logger.Error("Error determining NeedsRefresh for {0}", ex, item.Path);
-                }
-
-                var updateType = await FetchAsync(provider, item, providerInfo, force, cancellationToken).ConfigureAwait(false);
-
-                if (updateType.HasValue)
-                {
-                    if (result.HasValue)
-                    {
-                        result = result.Value | updateType.Value;
-                    }
-                    else
-                    {
-                        result = updateType;
-                    }
-                }
-            }
-
-            if (result.HasValue || force)
-            {
-                await _providerRepo.SaveProviderHistory(item.Id, providerHistories, cancellationToken);
-            }
-
-            return result;
-        }
-
-        /// <summary>
-        /// Providers the supports item.
-        /// </summary>
-        /// <param name="provider">The provider.</param>
-        /// <param name="item">The item.</param>
-        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
-        private bool ProviderSupportsItem(BaseMetadataProvider provider, BaseItem item)
-        {
-            try
-            {
-                return provider.Supports(item);
-            }
-            catch (Exception ex)
-            {
-                _logger.ErrorException("{0} failed in Supports for type {1}", ex, provider.GetType().Name, item.GetType().Name);
-                return false;
-            }
-        }
-
-        /// <summary>
-        /// Fetches metadata and returns true or false indicating if any work that requires persistence was done
-        /// </summary>
-        /// <param name="provider">The provider.</param>
-        /// <param name="item">The item.</param>
-        /// <param name="providerInfo">The provider information.</param>
-        /// <param name="force">if set to <c>true</c> [force].</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task{System.Boolean}.</returns>
-        /// <exception cref="System.ArgumentNullException">item</exception>
-        private async Task<ItemUpdateType?> FetchAsync(BaseMetadataProvider provider, BaseItem item, BaseProviderInfo providerInfo, bool force, CancellationToken cancellationToken)
-        {
-            if (item == null)
-            {
-                throw new ArgumentNullException("item");
-            }
-
-            cancellationToken.ThrowIfCancellationRequested();
-
-            _logger.Debug("Running {0} for {1}", provider.GetType().Name, item.Path ?? item.Name ?? "--Unknown--");
-
-            try
-            {
-                var changed = await provider.FetchAsync(item, force, providerInfo, cancellationToken).ConfigureAwait(false);
-
-                if (changed)
-                {
-                    return provider.ItemUpdateType;
-                }
-
-                return null;
-            }
-            catch (OperationCanceledException ex)
-            {
-                _logger.Debug("{0} canceled for {1}", provider.GetType().Name, item.Name);
-
-                // If the outer cancellation token is the one that caused the cancellation, throw it
-                if (cancellationToken.IsCancellationRequested && ex.CancellationToken == cancellationToken)
-                {
-                    throw;
-                }
-
-                return null;
-            }
-            catch (Exception ex)
-            {
-                _logger.ErrorException("{0} failed refreshing {1} {2}", ex, provider.GetType().Name, item.Name, item.Path ?? string.Empty);
-
-                provider.SetLastRefreshed(item, DateTime.UtcNow, providerInfo, ProviderRefreshStatus.Failure);
-
-                return ItemUpdateType.Unspecified;
-            }
+            _logger.Error("Unable to find a metadata service for item of type " + item.GetType().Name);
+            return Task.FromResult(true);
         }
 
         /// <summary>
@@ -328,9 +153,6 @@ namespace MediaBrowser.Providers.Manager
                         await dataToSave.CopyToAsync(fs, StreamDefaults.DefaultCopyToBufferSize, cancellationToken).ConfigureAwait(false);
                     }
                 }
-
-                // If this is ever used for something other than metadata we can add a file type param
-                item.ResolveArgs.AddMetadataFile(path);
             }
             finally
             {
@@ -517,6 +339,15 @@ namespace MediaBrowser.Providers.Manager
                 return false;
             }
 
+            // If this restriction is ever lifted, movie xml providers will have to be updated to prevent owned items like trailers from reading those files
+            if (item.IsOwnedItem)
+            {
+                if (provider is ILocalMetadataProvider || provider is IRemoteMetadataProvider)
+                {
+                    return false;
+                }
+            }
+
             return true;
         }
 
@@ -581,6 +412,7 @@ namespace MediaBrowser.Providers.Manager
 
             list.Add(GetPluginSummary<AdultVideo>());
             list.Add(GetPluginSummary<MusicVideo>());
+            list.Add(GetPluginSummary<Video>());
 
             list.Add(GetPluginSummary<LiveTvChannel>());
             list.Add(GetPluginSummary<LiveTvProgram>());
@@ -678,6 +510,8 @@ namespace MediaBrowser.Providers.Manager
         {
             foreach (var saver in _savers.Where(i => i.IsEnabledFor(item, updateType)))
             {
+                _logger.Debug("Saving {0} to {1}.", item.Path ?? item.Name, saver.Name);
+                
                 var fileSaver = saver as IMetadataFileSaver;
 
                 if (fileSaver != null)

+ 15 - 15
MediaBrowser.Providers/MediaBrowser.Providers.csproj

@@ -65,11 +65,13 @@
   </ItemGroup>
   <ItemGroup>
     <Compile Include="AdultVideos\AdultVideoMetadataService.cs" />
+    <Compile Include="AdultVideos\AdultVideoXmlProvider.cs" />
     <Compile Include="All\LocalImageProvider.cs" />
     <Compile Include="Books\BookMetadataService.cs" />
     <Compile Include="BoxSets\BoxSetMetadataService.cs" />
     <Compile Include="BoxSets\MovieDbBoxSetImageProvider.cs" />
     <Compile Include="BoxSets\MovieDbBoxSetProvider.cs" />
+    <Compile Include="Folders\FolderMetadataService.cs" />
     <Compile Include="GameGenres\GameGenreMetadataService.cs" />
     <Compile Include="Games\GameMetadataService.cs" />
     <Compile Include="Games\GameSystemMetadataService.cs" />
@@ -83,33 +85,34 @@
     <Compile Include="Manager\ProviderManager.cs" />
     <Compile Include="Manager\MetadataService.cs" />
     <Compile Include="BaseXmlProvider.cs" />
-    <Compile Include="CollectionFolderImageProvider.cs" />
-    <Compile Include="FolderProviderFromXml.cs" />
+    <Compile Include="Folders\FolderXmlProvider.cs" />
     <Compile Include="Games\GameXmlParser.cs" />
     <Compile Include="Games\GameXmlProvider.cs" />
     <Compile Include="Games\GameSystemXmlProvider.cs" />
-    <Compile Include="ImageFromMediaLocationProvider.cs" />
-    <Compile Include="ImagesByNameProvider.cs" />
+    <Compile Include="MediaInfo\FFProbeAudioInfo.cs" />
     <Compile Include="MediaInfo\FFProbeHelpers.cs" />
     <Compile Include="MediaInfo\FFProbeProvider.cs" />
     <Compile Include="MediaInfo\FFProbeVideoInfo.cs" />
+    <Compile Include="Movies\TrailerMetadataService.cs" />
+    <Compile Include="Movies\GenericMovieDbInfo.cs" />
     <Compile Include="Movies\MovieDbSearch.cs" />
+    <Compile Include="Movies\MovieMetadataService.cs" />
     <Compile Include="Movies\MovieXmlProvider.cs" />
+    <Compile Include="Movies\TmdbSettings.cs" />
+    <Compile Include="Movies\TrailerXmlProvider.cs" />
     <Compile Include="MusicGenres\MusicGenreImageProvider.cs" />
     <Compile Include="GameGenres\GameGenreImageProvider.cs" />
     <Compile Include="Genres\GenreImageProvider.cs" />
     <Compile Include="ImagesByName\ImageUtils.cs" />
     <Compile Include="MediaInfo\AudioImageProvider.cs" />
-    <Compile Include="MediaInfo\BaseFFProbeProvider.cs" />
-    <Compile Include="MediaInfo\FFProbeAudioInfoProvider.cs" />
-    <Compile Include="MediaInfo\FFProbeVideoInfoProvider.cs" />
     <Compile Include="MediaInfo\VideoImageProvider.cs" />
     <Compile Include="BoxSets\BoxSetXmlProvider.cs" />
-    <Compile Include="Movies\ManualMovieDbImageProvider.cs" />
-    <Compile Include="Movies\ManualFanartMovieImageProvider.cs" />
+    <Compile Include="Movies\MovieDbImageProvider.cs" />
+    <Compile Include="Movies\FanartMovieImageProvider.cs" />
     <Compile Include="MusicGenres\MusicGenreMetadataService.cs" />
     <Compile Include="Music\AlbumMetadataService.cs" />
     <Compile Include="Music\ArtistMetadataService.cs" />
+    <Compile Include="Music\AudioMetadataService.cs" />
     <Compile Include="Music\LastfmArtistProvider.cs" />
     <Compile Include="Music\MusicBrainzArtistProvider.cs" />
     <Compile Include="Music\MusicVideoMetadataService.cs" />
@@ -119,12 +122,8 @@
     <Compile Include="People\MovieDbPersonImageProvider.cs" />
     <Compile Include="Movies\MovieUpdatesPrescanTask.cs" />
     <Compile Include="Movies\MovieXmlParser.cs" />
-    <Compile Include="Movies\FanArtMovieProvider.cs" />
     <Compile Include="Movies\FanArtMovieUpdatesPrescanTask.cs" />
-    <Compile Include="Movies\MovieDbImagesProvider.cs" />
     <Compile Include="Movies\MovieDbProvider.cs" />
-    <Compile Include="Movies\MovieProviderFromXml.cs" />
-    <Compile Include="Movies\OpenMovieDatabaseProvider.cs" />
     <Compile Include="Music\AlbumXmlProvider.cs" />
     <Compile Include="Music\ArtistXmlProvider.cs" />
     <Compile Include="Music\FanArtUpdatesPrescanTask.cs" />
@@ -177,9 +176,10 @@
     <Compile Include="TV\SeriesXmlProvider.cs" />
     <Compile Include="TV\SeriesXmlParser.cs" />
     <Compile Include="TV\TvdbPrescanTask.cs" />
-    <Compile Include="UserRootFolderNameProvider.cs" />
+    <Compile Include="Folders\UserRootFolderNameProvider.cs" />
     <Compile Include="Users\UserMetadataService.cs" />
-    <Compile Include="VirtualItemImageValidator.cs" />
+    <Compile Include="Videos\VideoMetadataService.cs" />
+    <Compile Include="Years\YearMetadataService.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\MediaBrowser.Common\MediaBrowser.Common.csproj">

+ 40 - 167
MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs

@@ -1,16 +1,13 @@
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Drawing;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.MediaInfo;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.IO;
 using System;
-using System.Collections.Concurrent;
-using System.IO;
-using System.Linq;
+using System.Collections.Generic;
 using System.Threading;
 using System.Threading.Tasks;
 
@@ -19,207 +16,83 @@ namespace MediaBrowser.Providers.MediaInfo
     /// <summary>
     /// Uses ffmpeg to create video images
     /// </summary>
-    public class AudioImageProvider : BaseMetadataProvider
+    public class AudioImageProvider : IDynamicImageProvider, IHasChangeMonitor
     {
-        /// <summary>
-        /// The _locks
-        /// </summary>
-        private readonly ConcurrentDictionary<string, SemaphoreSlim> _locks = new ConcurrentDictionary<string, SemaphoreSlim>();
-
-        /// <summary>
-        /// The _media encoder
-        /// </summary>
+        private readonly IIsoManager _isoManager;
         private readonly IMediaEncoder _mediaEncoder;
+        private readonly IServerConfigurationManager _config;
 
-        /// <summary>
-        /// Initializes a new instance of the <see cref="BaseMetadataProvider" /> class.
-        /// </summary>
-        /// <param name="logManager">The log manager.</param>
-        /// <param name="configurationManager">The configuration manager.</param>
-        /// <param name="mediaEncoder">The media encoder.</param>
-        public AudioImageProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IMediaEncoder mediaEncoder)
-            : base(logManager, configurationManager)
+        public AudioImageProvider(IIsoManager isoManager, IMediaEncoder mediaEncoder, IServerConfigurationManager config)
         {
+            _isoManager = isoManager;
             _mediaEncoder = mediaEncoder;
+            _config = config;
         }
 
         /// <summary>
-        /// Gets a value indicating whether [refresh on version change].
+        /// The null mount task result
         /// </summary>
-        /// <value><c>true</c> if [refresh on version change]; otherwise, <c>false</c>.</value>
-        protected override bool RefreshOnVersionChange
-        {
-            get
-            {
-                return true;
-            }
-        }
+        protected readonly Task<IIsoMount> NullMountTaskResult = Task.FromResult<IIsoMount>(null);
 
         /// <summary>
-        /// Gets the provider version.
+        /// Mounts the iso if needed.
         /// </summary>
-        /// <value>The provider version.</value>
-        protected override string ProviderVersion
+        /// <param name="item">The item.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task{IIsoMount}.</returns>
+        protected Task<IIsoMount> MountIsoIfNeeded(Video item, CancellationToken cancellationToken)
         {
-            get
+            if (item.VideoType == VideoType.Iso)
             {
-                return "1";
+                return _isoManager.Mount(item.Path, cancellationToken);
             }
-        }
 
-        /// <summary>
-        /// Supportses the specified item.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
-        public override bool Supports(BaseItem item)
-        {
-            return item.LocationType == LocationType.FileSystem && item is Audio;
+            return NullMountTaskResult;
         }
 
-        /// <summary>
-        /// Override this to return the date that should be compared to the last refresh date
-        /// to determine if this provider should be re-fetched.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns>DateTime.</returns>
-        protected override DateTime CompareDate(BaseItem item)
+        public IEnumerable<ImageType> GetSupportedImages(IHasImages item)
         {
-            return item.DateModified;
-        }
-
-        /// <summary>
-        /// Gets the priority.
-        /// </summary>
-        /// <value>The priority.</value>
-        public override MetadataProviderPriority Priority
-        {
-            get { return MetadataProviderPriority.Last; }
-        }
-
-        public override ItemUpdateType ItemUpdateType
-        {
-            get
-            {
-                return ItemUpdateType.ImageUpdate;
-            }
+            return new List<ImageType> { ImageType.Primary };
         }
 
-        /// <summary>
-        /// Fetches metadata and returns true or false indicating if any work that requires persistence was done
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="force">if set to <c>true</c> [force].</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task{System.Boolean}.</returns>
-        public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
+        public Task<DynamicImageResponse> GetImage(IHasImages item, ImageType type, CancellationToken cancellationToken)
         {
-            item.ValidateImages();
-
             var audio = (Audio)item;
 
-            if (string.IsNullOrEmpty(audio.PrimaryImagePath) && audio.HasEmbeddedImage)
+            // Can't extract if we didn't find a video stream in the file
+            if (!audio.HasEmbeddedImage)
             {
-                try
-                {
-                    await CreateImagesForSong(audio, cancellationToken).ConfigureAwait(false);
-                }
-                catch (Exception ex)
-                {
-                    Logger.ErrorException("Error extracting image for {0}", ex, item.Name);
-                }
+                return Task.FromResult(new DynamicImageResponse { HasImage = false });
             }
 
-            SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
-            return true;
+            return GetVideoImage((Audio)item, cancellationToken);
         }
 
-        /// <summary>
-        /// Creates the images for song.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        /// <exception cref="System.InvalidOperationException">Can't extract an image unless the audio file has an embedded image.</exception>
-        private async Task CreateImagesForSong(Audio item, CancellationToken cancellationToken)
+        public async Task<DynamicImageResponse> GetVideoImage(Audio item, CancellationToken cancellationToken)
         {
-            cancellationToken.ThrowIfCancellationRequested();
+            var stream = await _mediaEncoder.ExtractImage(new[] { item.Path }, InputType.File, true, null, null, cancellationToken).ConfigureAwait(false);
 
-            var path = GetAudioImagePath(item);
-
-            if (!File.Exists(path))
+            return new DynamicImageResponse
             {
-                var semaphore = GetLock(path);
-
-                // Acquire a lock
-                await semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
-
-                // Check again
-                if (!File.Exists(path))
-                {
-                    try
-                    {
-                        var parentPath = Path.GetDirectoryName(path);
-
-                        Directory.CreateDirectory(parentPath);
-
-                        await _mediaEncoder.ExtractImage(new[] { item.Path }, InputType.File, true, null, null, path, cancellationToken).ConfigureAwait(false);
-                    }
-                    finally
-                    {
-                        semaphore.Release();
-                    }
-                }
-                else
-                {
-                    semaphore.Release();
-                }
-            }
-
-            // Image is already in the cache
-            item.SetImagePath(ImageType.Primary, path);
+                Format = ImageFormat.Jpg,
+                HasImage = true,
+                Stream = stream
+            };
         }
 
-        /// <summary>
-        /// Gets the audio image path.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns>System.String.</returns>
-        private string GetAudioImagePath(Audio item)
+        public string Name
         {
-            var album = item.Parent as MusicAlbum;
-
-            var filename = item.Album ?? string.Empty;
-            filename += item.Artists.FirstOrDefault() ?? string.Empty;
-            filename += album == null ? item.Id.ToString("N") + "_primary" + item.DateModified.Ticks : album.Id.ToString("N") + album.DateModified.Ticks + "_primary";
-
-            filename = filename.GetMD5() + ".jpg";
-
-            var prefix = filename.Substring(0, 1);
-
-            return Path.Combine(AudioImagesPath, prefix, filename);
+            get { return "Embedded Image"; }
         }
 
-        /// <summary>
-        /// Gets the audio images data path.
-        /// </summary>
-        /// <value>The audio images data path.</value>
-        public string AudioImagesPath
+        public bool Supports(IHasImages item)
         {
-            get
-            {
-                return Path.Combine(ConfigurationManager.ApplicationPaths.DataPath, "extracted-audio-images");
-            }
+            return item.LocationType == LocationType.FileSystem && item is Audio;
         }
 
-        /// <summary>
-        /// Gets the lock.
-        /// </summary>
-        /// <param name="filename">The filename.</param>
-        /// <returns>SemaphoreSlim.</returns>
-        private SemaphoreSlim GetLock(string filename)
+        public bool HasChanged(IHasMetadata item, DateTime date)
         {
-            return _locks.GetOrAdd(filename, key => new SemaphoreSlim(1, 1));
+            return item.DateModified > date;
         }
     }
 }

+ 0 - 145
MediaBrowser.Providers/MediaInfo/BaseFFProbeProvider.cs

@@ -1,145 +0,0 @@
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.MediaInfo;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Logging;
-using MediaBrowser.Model.Serialization;
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Providers.MediaInfo
-{
-    /// <summary>
-    /// Provides a base class for extracting media information through ffprobe
-    /// </summary>
-    /// <typeparam name="T"></typeparam>
-    public abstract class BaseFFProbeProvider<T> : BaseMetadataProvider
-        where T : BaseItem, IHasMediaStreams
-    {
-        protected BaseFFProbeProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IMediaEncoder mediaEncoder, IJsonSerializer jsonSerializer)
-            : base(logManager, configurationManager)
-        {
-            JsonSerializer = jsonSerializer;
-            MediaEncoder = mediaEncoder;
-        }
-
-        protected readonly IMediaEncoder MediaEncoder;
-        protected readonly IJsonSerializer JsonSerializer;
-
-        /// <summary>
-        /// Gets the priority.
-        /// </summary>
-        /// <value>The priority.</value>
-        public override MetadataProviderPriority Priority
-        {
-            get { return MetadataProviderPriority.First; }
-        }
-
-        protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
-
-        /// <summary>
-        /// Supportses the specified item.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
-        public override bool Supports(BaseItem item)
-        {
-            return item.LocationType == LocationType.FileSystem && item is T;
-        }
-
-        /// <summary>
-        /// Override this to return the date that should be compared to the last refresh date
-        /// to determine if this provider should be re-fetched.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns>DateTime.</returns>
-        protected override DateTime CompareDate(BaseItem item)
-        {
-            return item.DateModified;
-        }
-
-        /// <summary>
-        /// The null mount task result
-        /// </summary>
-        protected readonly Task<IIsoMount> NullMountTaskResult = Task.FromResult<IIsoMount>(null);
-
-        /// <summary>
-        /// Gets the provider version.
-        /// </summary>
-        /// <value>The provider version.</value>
-        protected override string ProviderVersion
-        {
-            get
-            {
-                return "ffmpeg20131209";
-            }
-        }
-
-        /// <summary>
-        /// Gets a value indicating whether [refresh on version change].
-        /// </summary>
-        /// <value><c>true</c> if [refresh on version change]; otherwise, <c>false</c>.</value>
-        protected override bool RefreshOnVersionChange
-        {
-            get
-            {
-                return true;
-            }
-        }
-
-        /// <summary>
-        /// Gets the media info.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="isoMount">The iso mount.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task{MediaInfoResult}.</returns>
-        /// <exception cref="System.ArgumentNullException">inputPath
-        /// or
-        /// cache</exception>
-        protected async Task<InternalMediaInfoResult> GetMediaInfo(BaseItem item, IIsoMount isoMount, CancellationToken cancellationToken)
-        {
-            cancellationToken.ThrowIfCancellationRequested();
-
-            var type = InputType.File;
-            var inputPath = isoMount == null ? new[] { item.Path } : new[] { isoMount.MountedPath };
-
-            var video = item as Video;
-
-            if (video != null)
-            {
-                inputPath = MediaEncoderHelpers.GetInputArgument(video.Path, video.LocationType == LocationType.Remote, video.VideoType, video.IsoType, isoMount, video.PlayableStreamFileNames, out type);
-            }
-
-            return await MediaEncoder.GetMediaInfo(inputPath, type, item is Audio, cancellationToken).ConfigureAwait(false);
-        }
-
-        /// <summary>
-        /// Mounts the iso if needed.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>IsoMount.</returns>
-        protected virtual Task<IIsoMount> MountIsoIfNeeded(T item, CancellationToken cancellationToken)
-        {
-            return NullMountTaskResult;
-        }
-
-        /// <summary>
-        /// Called when [pre fetch].
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="mount">The mount.</param>
-        protected virtual void OnPreFetch(T item, IIsoMount mount)
-        {
-
-        }
-    }
-}

+ 24 - 21
MediaBrowser.Providers/MediaInfo/FFProbeAudioInfoProvider.cs → MediaBrowser.Providers/MediaInfo/FFProbeAudioInfo.cs

@@ -1,41 +1,36 @@
 using MediaBrowser.Common.Extensions;
-using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.MediaInfo;
 using MediaBrowser.Controller.Persistence;
-using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Logging;
-using MediaBrowser.Model.Serialization;
 using System;
 using System.Collections.Generic;
+using System.Globalization;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.MediaInfo
 {
-    /// <summary>
-    /// Extracts audio information using ffprobe
-    /// </summary>
-    public class FFProbeAudioInfoProvider : BaseFFProbeProvider<Audio>
+    class FFProbeAudioInfo
     {
+        private readonly IMediaEncoder _mediaEncoder;
         private readonly IItemRepository _itemRepo;
 
-        public FFProbeAudioInfoProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IMediaEncoder mediaEncoder, IJsonSerializer jsonSerializer, IItemRepository itemRepo)
-            : base(logManager, configurationManager, mediaEncoder, jsonSerializer)
+        private readonly CultureInfo _usCulture = new CultureInfo("en-US");
+        
+        public FFProbeAudioInfo(IMediaEncoder mediaEncoder, IItemRepository itemRepo)
         {
+            _mediaEncoder = mediaEncoder;
             _itemRepo = itemRepo;
         }
 
-        public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
+        public async Task<ItemUpdateType> Probe<T>(T item, CancellationToken cancellationToken)
+            where T : Audio
         {
-            var myItem = (Audio)item;
-
-            OnPreFetch(myItem, null);
-
-            var result = await GetMediaInfo(item, null, cancellationToken).ConfigureAwait(false);
+            var result = await GetMediaInfo(item, cancellationToken).ConfigureAwait(false);
 
             cancellationToken.ThrowIfCancellationRequested();
 
@@ -43,11 +38,19 @@ namespace MediaBrowser.Providers.MediaInfo
 
             cancellationToken.ThrowIfCancellationRequested();
 
-            await Fetch(myItem, cancellationToken, result).ConfigureAwait(false);
+            await Fetch(item, cancellationToken, result).ConfigureAwait(false);
 
-            SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
+            return ItemUpdateType.MetadataImport;
+        }
 
-            return true;
+        private async Task<InternalMediaInfoResult> GetMediaInfo(BaseItem item, CancellationToken cancellationToken)
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+
+            const InputType type = InputType.File;
+            var inputPath = new[] { item.Path };
+
+            return await _mediaEncoder.GetMediaInfo(inputPath, type, false, cancellationToken).ConfigureAwait(false);
         }
 
         /// <summary>
@@ -82,7 +85,7 @@ namespace MediaBrowser.Providers.MediaInfo
                     // If we got something, parse it
                     if (!string.IsNullOrEmpty(duration))
                     {
-                        audio.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(duration, UsCulture)).Ticks;
+                        audio.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(duration, _usCulture)).Ticks;
                     }
                 }
             }
@@ -277,6 +280,6 @@ namespace MediaBrowser.Providers.MediaInfo
 
             return null;
         }
-    }
 
+    }
 }

+ 43 - 5
MediaBrowser.Providers/MediaInfo/FFProbeProvider.cs

@@ -1,4 +1,5 @@
 using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
@@ -18,10 +19,14 @@ using System.Threading.Tasks;
 namespace MediaBrowser.Providers.MediaInfo
 {
     public class FFProbeProvider : ICustomMetadataProvider<Episode>,
-        ICustomMetadataProvider<MusicVideo>, 
-        ICustomMetadataProvider<Movie>, 
-        ICustomMetadataProvider<AdultVideo>, 
-        ICustomMetadataProvider<LiveTvVideoRecording>, 
+        ICustomMetadataProvider<MusicVideo>,
+        ICustomMetadataProvider<Movie>,
+        ICustomMetadataProvider<AdultVideo>,
+        ICustomMetadataProvider<LiveTvVideoRecording>,
+        ICustomMetadataProvider<LiveTvAudioRecording>,
+        ICustomMetadataProvider<Trailer>,
+        ICustomMetadataProvider<Video>,
+        ICustomMetadataProvider<Audio>,
         IHasChangeMonitor
     {
         private readonly ILogger _logger;
@@ -30,7 +35,7 @@ namespace MediaBrowser.Providers.MediaInfo
         private readonly IItemRepository _itemRepo;
         private readonly IBlurayExaminer _blurayExaminer;
         private readonly ILocalizationManager _localization;
-        
+
         public string Name
         {
             get { return "ffprobe"; }
@@ -61,6 +66,26 @@ namespace MediaBrowser.Providers.MediaInfo
             return FetchVideoInfo(item, cancellationToken);
         }
 
+        public Task<ItemUpdateType> FetchAsync(Trailer item, CancellationToken cancellationToken)
+        {
+            return FetchVideoInfo(item, cancellationToken);
+        }
+
+        public Task<ItemUpdateType> FetchAsync(Video item, CancellationToken cancellationToken)
+        {
+            return FetchVideoInfo(item, cancellationToken);
+        }
+
+        public Task<ItemUpdateType> FetchAsync(Audio item, CancellationToken cancellationToken)
+        {
+            return FetchAudioInfo(item, cancellationToken);
+        }
+
+        public Task<ItemUpdateType> FetchAsync(LiveTvAudioRecording item, CancellationToken cancellationToken)
+        {
+            return FetchAudioInfo(item, cancellationToken);
+        }
+
         public FFProbeProvider(ILogger logger, IIsoManager isoManager, IMediaEncoder mediaEncoder, IItemRepository itemRepo, IBlurayExaminer blurayExaminer, ILocalizationManager localization)
         {
             _logger = logger;
@@ -95,6 +120,19 @@ namespace MediaBrowser.Providers.MediaInfo
             return prober.ProbeVideo(item, cancellationToken);
         }
 
+        public Task<ItemUpdateType> FetchAudioInfo<T>(T item, CancellationToken cancellationToken)
+            where T : Audio
+        {
+            if (item.LocationType != LocationType.FileSystem)
+            {
+                return _cachedTask;
+            }
+
+            var prober = new FFProbeAudioInfo(_mediaEncoder, _itemRepo);
+
+            return prober.Probe(item, cancellationToken);
+        }
+
         public bool HasChanged(IHasMetadata item, DateTime date)
         {
             return item.DateModified > date;

+ 63 - 63
MediaBrowser.Providers/MediaInfo/FFProbeVideoInfo.cs

@@ -321,69 +321,69 @@ namespace MediaBrowser.Providers.MediaInfo
         /// <param name="currentStreams">The current streams.</param>
         private void AddExternalSubtitles(Video video, List<MediaStream> currentStreams)
         {
-            var useParent = !video.ResolveArgs.IsDirectory;
-
-            if (useParent && video.Parent == null)
-            {
-                return;
-            }
-
-            var fileSystemChildren = useParent
-                                         ? video.Parent.ResolveArgs.FileSystemChildren
-                                         : video.ResolveArgs.FileSystemChildren;
-
-            var startIndex = currentStreams.Count;
-            var streams = new List<MediaStream>();
-
-            var videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(video.Path);
-
-            foreach (var file in fileSystemChildren
-                .Where(f => !f.Attributes.HasFlag(FileAttributes.Directory) && SubtitleExtensions.Contains(Path.GetExtension(f.FullName), StringComparer.OrdinalIgnoreCase)))
-            {
-                var fullName = file.FullName;
-
-                var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fullName);
-
-                // If the subtitle file matches the video file name
-                if (string.Equals(videoFileNameWithoutExtension, fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))
-                {
-                    streams.Add(new MediaStream
-                    {
-                        Index = startIndex++,
-                        Type = MediaStreamType.Subtitle,
-                        IsExternal = true,
-                        Path = fullName,
-                        Codec = Path.GetExtension(fullName).ToLower().TrimStart('.')
-                    });
-                }
-                else if (fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension + ".", StringComparison.OrdinalIgnoreCase))
-                {
-                    // Support xbmc naming conventions - 300.spanish.srt
-                    var language = fileNameWithoutExtension.Split('.').LastOrDefault();
-
-                    // Try to translate to three character code
-                    // Be flexible and check against both the full and three character versions
-                    var culture = _localization.GetCultures()
-                        .FirstOrDefault(i => string.Equals(i.DisplayName, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Name, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.ThreeLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.TwoLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase));
-
-                    if (culture != null)
-                    {
-                        language = culture.ThreeLetterISOLanguageName;
-                    }
-
-                    streams.Add(new MediaStream
-                    {
-                        Index = startIndex++,
-                        Type = MediaStreamType.Subtitle,
-                        IsExternal = true,
-                        Path = fullName,
-                        Codec = Path.GetExtension(fullName).ToLower().TrimStart('.'),
-                        Language = language
-                    });
-                }
-            }
-
-            currentStreams.AddRange(streams);
+            //var useParent = !video.ResolveArgs.IsDirectory;
+
+            //if (useParent && video.Parent == null)
+            //{
+            //    return;
+            //}
+
+            //var fileSystemChildren = useParent
+            //                             ? video.Parent.ResolveArgs.FileSystemChildren
+            //                             : video.ResolveArgs.FileSystemChildren;
+
+            //var startIndex = currentStreams.Count;
+            //var streams = new List<MediaStream>();
+
+            //var videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(video.Path);
+
+            //foreach (var file in fileSystemChildren
+            //    .Where(f => !f.Attributes.HasFlag(FileAttributes.Directory) && SubtitleExtensions.Contains(Path.GetExtension(f.FullName), StringComparer.OrdinalIgnoreCase)))
+            //{
+            //    var fullName = file.FullName;
+
+            //    var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fullName);
+
+            //    // If the subtitle file matches the video file name
+            //    if (string.Equals(videoFileNameWithoutExtension, fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))
+            //    {
+            //        streams.Add(new MediaStream
+            //        {
+            //            Index = startIndex++,
+            //            Type = MediaStreamType.Subtitle,
+            //            IsExternal = true,
+            //            Path = fullName,
+            //            Codec = Path.GetExtension(fullName).ToLower().TrimStart('.')
+            //        });
+            //    }
+            //    else if (fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension + ".", StringComparison.OrdinalIgnoreCase))
+            //    {
+            //        // Support xbmc naming conventions - 300.spanish.srt
+            //        var language = fileNameWithoutExtension.Split('.').LastOrDefault();
+
+            //        // Try to translate to three character code
+            //        // Be flexible and check against both the full and three character versions
+            //        var culture = _localization.GetCultures()
+            //            .FirstOrDefault(i => string.Equals(i.DisplayName, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Name, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.ThreeLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.TwoLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase));
+
+            //        if (culture != null)
+            //        {
+            //            language = culture.ThreeLetterISOLanguageName;
+            //        }
+
+            //        streams.Add(new MediaStream
+            //        {
+            //            Index = startIndex++,
+            //            Type = MediaStreamType.Subtitle,
+            //            IsExternal = true,
+            //            Path = fullName,
+            //            Codec = Path.GetExtension(fullName).ToLower().TrimStart('.'),
+            //            Language = language
+            //        });
+            //    }
+            //}
+
+            //currentStreams.AddRange(streams);
         }
 
         /// <summary>

+ 0 - 686
MediaBrowser.Providers/MediaInfo/FFProbeVideoInfoProvider.cs

@@ -1,686 +0,0 @@
-using DvdLib.Ifo;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Localization;
-using MediaBrowser.Controller.MediaInfo;
-using MediaBrowser.Controller.Persistence;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
-using MediaBrowser.Model.Logging;
-using MediaBrowser.Model.MediaInfo;
-using MediaBrowser.Model.Serialization;
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Providers.MediaInfo
-{
-    /// <summary>
-    /// Extracts video information using ffprobe
-    /// </summary>
-    public class FFProbeVideoInfoProvider : BaseFFProbeProvider<Video>
-    {
-        private readonly IItemRepository _itemRepo;
-
-        public FFProbeVideoInfoProvider(IIsoManager isoManager, IBlurayExaminer blurayExaminer, IJsonSerializer jsonSerializer, ILogManager logManager, IServerConfigurationManager configurationManager, IMediaEncoder mediaEncoder, ILocalizationManager localization, IItemRepository itemRepo)
-            : base(logManager, configurationManager, mediaEncoder, jsonSerializer)
-        {
-            if (isoManager == null)
-            {
-                throw new ArgumentNullException("isoManager");
-            }
-            if (blurayExaminer == null)
-            {
-                throw new ArgumentNullException("blurayExaminer");
-            }
-
-            _blurayExaminer = blurayExaminer;
-            _localization = localization;
-            _itemRepo = itemRepo;
-            _isoManager = isoManager;
-        }
-
-        /// <summary>
-        /// Gets or sets the bluray examiner.
-        /// </summary>
-        /// <value>The bluray examiner.</value>
-        private readonly IBlurayExaminer _blurayExaminer;
-
-        /// <summary>
-        /// The _iso manager
-        /// </summary>
-        private readonly IIsoManager _isoManager;
-
-        private readonly ILocalizationManager _localization;
-
-        /// <summary>
-        /// Returns true or false indicating if the provider should refresh when the contents of it's directory changes
-        /// </summary>
-        /// <value><c>true</c> if [refresh on file system stamp change]; otherwise, <c>false</c>.</value>
-        protected override bool RefreshOnFileSystemStampChange
-        {
-            get
-            {
-                // Need this in case external subtitle files change
-                return true;
-            }
-        }
-
-        /// <summary>
-        /// Gets the filestamp extensions.
-        /// </summary>
-        /// <value>The filestamp extensions.</value>
-        protected override string[] FilestampExtensions
-        {
-            get
-            {
-                return new[] { ".srt", ".ssa", ".ass" };
-            }
-        }
-
-        public override MetadataProviderPriority Priority
-        {
-            get
-            {
-                return MetadataProviderPriority.Second;
-            }
-        }
-
-        /// <summary>
-        /// Supports video files and dvd structures
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
-        public override bool Supports(BaseItem item)
-        {
-            if (item.LocationType != LocationType.FileSystem)
-            {
-                return false;
-            }
-
-            var video = item as Video;
-
-            if (video != null)
-            {
-                if (video.VideoType == VideoType.Iso)
-                {
-                    return _isoManager.CanMount(item.Path);
-                }
-
-                return video.VideoType == VideoType.VideoFile || video.VideoType == VideoType.Dvd || video.VideoType == VideoType.BluRay;
-            }
-
-            return false;
-        }
-
-        /// <summary>
-        /// Called when [pre fetch].
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="mount">The mount.</param>
-        protected override void OnPreFetch(Video item, IIsoMount mount)
-        {
-            if (item.VideoType == VideoType.Iso)
-            {
-                item.IsoType = DetermineIsoType(mount);
-            }
-
-            if (item.VideoType == VideoType.Dvd || (item.IsoType.HasValue && item.IsoType == IsoType.Dvd))
-            {
-                FetchFromDvdLib(item, mount);
-            }
-
-            base.OnPreFetch(item, mount);
-        }
-
-        private void FetchFromDvdLib(Video item, IIsoMount mount)
-        {
-            var path = mount == null ? item.Path : mount.MountedPath;
-            var dvd = new Dvd(path);
-
-            item.RunTimeTicks = dvd.Titles.Select(GetRuntime).Max();
-
-            var primaryTitle = dvd.Titles.OrderByDescending(GetRuntime).FirstOrDefault();
-
-            uint? titleNumber = null;
-
-            if (primaryTitle != null)
-            {
-                titleNumber = primaryTitle.TitleNumber;
-            }
-
-            item.PlayableStreamFileNames = GetPrimaryPlaylistVobFiles(item, mount, titleNumber)
-                .Select(Path.GetFileName)
-                .ToList();
-        }
-
-        private long GetRuntime(Title title)
-        {
-            return title.ProgramChains
-                    .Select(i => (TimeSpan)i.PlaybackTime)
-                    .Select(i => i.Ticks)
-                    .Sum();
-        }
-
-        public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
-        {
-            var video = (Video)item;
-
-            var isoMount = await MountIsoIfNeeded(video, cancellationToken).ConfigureAwait(false);
-
-            try
-            {
-                OnPreFetch(video, isoMount);
-
-                // If we didn't find any satisfying the min length, just take them all
-                if (video.VideoType == VideoType.Dvd || (video.IsoType.HasValue && video.IsoType == IsoType.Dvd))
-                {
-                    if (video.PlayableStreamFileNames.Count == 0)
-                    {
-                        Logger.Error("No playable vobs found in dvd structure, skipping ffprobe.");
-                        SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
-                        return true;
-                    }
-                }
-
-                var result = await GetMediaInfo(item, isoMount, cancellationToken).ConfigureAwait(false);
-
-                cancellationToken.ThrowIfCancellationRequested();
-
-                FFProbeHelpers.NormalizeFFProbeResult(result);
-
-                cancellationToken.ThrowIfCancellationRequested();
-
-                await Fetch(video, force, providerInfo, cancellationToken, result, isoMount).ConfigureAwait(false);
-
-            }
-            finally
-            {
-                if (isoMount != null)
-                {
-                    isoMount.Dispose();
-                }
-            }
-
-            SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
-            return true;
-        }
-
-        /// <summary>
-        /// Mounts the iso if needed.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>IsoMount.</returns>
-        protected override Task<IIsoMount> MountIsoIfNeeded(Video item, CancellationToken cancellationToken)
-        {
-            if (item.VideoType == VideoType.Iso)
-            {
-                return _isoManager.Mount(item.Path, cancellationToken);
-            }
-
-            return base.MountIsoIfNeeded(item, cancellationToken);
-        }
-
-        /// <summary>
-        /// Determines the type of the iso.
-        /// </summary>
-        /// <param name="isoMount">The iso mount.</param>
-        /// <returns>System.Nullable{IsoType}.</returns>
-        private IsoType? DetermineIsoType(IIsoMount isoMount)
-        {
-            var folders = Directory.EnumerateDirectories(isoMount.MountedPath).Select(Path.GetFileName).ToList();
-
-            if (folders.Contains("video_ts", StringComparer.OrdinalIgnoreCase))
-            {
-                return IsoType.Dvd;
-            }
-            if (folders.Contains("bdmv", StringComparer.OrdinalIgnoreCase))
-            {
-                return IsoType.BluRay;
-            }
-
-            return null;
-        }
-
-        private IEnumerable<string> GetPrimaryPlaylistVobFiles(Video video, IIsoMount isoMount, uint? titleNumber)
-        {
-            // min size 300 mb
-            const long minPlayableSize = 314572800;
-
-            var root = isoMount != null ? isoMount.MountedPath : video.Path;
-
-            // Try to eliminate menus and intros by skipping all files at the front of the list that are less than the minimum size
-            // Once we reach a file that is at least the minimum, return all subsequent ones
-            var allVobs = Directory.EnumerateFiles(root, "*", SearchOption.AllDirectories)
-                .Where(file => string.Equals(Path.GetExtension(file), ".vob", StringComparison.OrdinalIgnoreCase))
-                .ToList();
-
-            // If we didn't find any satisfying the min length, just take them all
-            if (allVobs.Count == 0)
-            {
-                Logger.Error("No vobs found in dvd structure.");
-                return new List<string>();
-            }
-
-            if (titleNumber.HasValue)
-            {
-                var prefix = string.Format("VTS_0{0}_", titleNumber.Value.ToString(UsCulture));
-                var vobs = allVobs.Where(i => Path.GetFileName(i).StartsWith(prefix, StringComparison.OrdinalIgnoreCase)).ToList();
-
-                if (vobs.Count > 0)
-                {
-                    return vobs;
-                }
-
-                Logger.Debug("Could not determine vob file list for {0} using DvdLib. Will scan using file sizes.", video.Path);
-            }
-
-            var files = allVobs
-                .SkipWhile(f => new FileInfo(f).Length < minPlayableSize)
-                .ToList();
-
-            // If we didn't find any satisfying the min length, just take them all
-            if (files.Count == 0)
-            {
-                Logger.Warn("Vob size filter resulted in zero matches. Taking all vobs.");
-                files = allVobs;
-            }
-
-            // Assuming they're named "vts_05_01", take all files whose second part matches that of the first file
-            if (files.Count > 0)
-            {
-                var parts = Path.GetFileNameWithoutExtension(files[0]).Split('_');
-
-                if (parts.Length == 3)
-                {
-                    var title = parts[1];
-
-                    files = files.TakeWhile(f =>
-                    {
-                        var fileParts = Path.GetFileNameWithoutExtension(f).Split('_');
-
-                        return fileParts.Length == 3 && string.Equals(title, fileParts[1], StringComparison.OrdinalIgnoreCase);
-
-                    }).ToList();
-
-                    // If this resulted in not getting any vobs, just take them all
-                    if (files.Count == 0)
-                    {
-                        Logger.Warn("Vob filename filter resulted in zero matches. Taking all vobs.");
-                        files = allVobs;
-                    }
-                }
-            }
-
-            return files;
-        }
-
-        /// <summary>
-        /// Fetches the specified video.
-        /// </summary>
-        /// <param name="video">The video.</param>
-        /// <param name="force">if set to <c>true</c> [force].</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <param name="data">The data.</param>
-        /// <param name="isoMount">The iso mount.</param>
-        /// <returns>Task.</returns>
-        protected async Task Fetch(Video video, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken, InternalMediaInfoResult data, IIsoMount isoMount)
-        {
-            if (data.format != null)
-            {
-                // For dvd's this may not always be accurate, so don't set the runtime if the item already has one
-                var needToSetRuntime = video.VideoType != VideoType.Dvd || video.RunTimeTicks == null || video.RunTimeTicks.Value == 0;
-
-                if (needToSetRuntime && !string.IsNullOrEmpty(data.format.duration))
-                {
-                    video.RunTimeTicks = TimeSpan.FromSeconds(double.Parse(data.format.duration, UsCulture)).Ticks;
-                }
-            }
-
-            var mediaStreams = MediaEncoderHelpers.GetMediaInfo(data).MediaStreams;
-
-            var chapters = data.Chapters ?? new List<ChapterInfo>();
-
-            if (video.VideoType == VideoType.BluRay || (video.IsoType.HasValue && video.IsoType.Value == IsoType.BluRay))
-            {
-                var inputPath = isoMount != null ? isoMount.MountedPath : video.Path;
-                FetchBdInfo(video, chapters, mediaStreams, inputPath, cancellationToken);
-            }
-
-            AddExternalSubtitles(video, mediaStreams);
-
-            FetchWtvInfo(video, force, data);
-
-            video.IsHD = mediaStreams.Any(i => i.Type == MediaStreamType.Video && i.Width.HasValue && i.Width.Value >= 1270);
-
-            if (chapters.Count == 0 && mediaStreams.Any(i => i.Type == MediaStreamType.Video))
-            {
-                AddDummyChapters(video, chapters);
-            }
-
-            var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
-
-            video.VideoBitRate = videoStream == null ? null : videoStream.BitRate;
-            video.DefaultVideoStreamIndex = videoStream == null ? (int?)null : videoStream.Index;
-
-            video.HasSubtitles = mediaStreams.Any(i => i.Type == MediaStreamType.Subtitle);
-
-            await FFMpegManager.Instance.PopulateChapterImages(video, chapters, false, false, cancellationToken).ConfigureAwait(false);
-
-            var videoFileChanged = CompareDate(video) > providerInfo.LastRefreshed;
-
-            await _itemRepo.SaveMediaStreams(video.Id, mediaStreams, cancellationToken).ConfigureAwait(false);
-
-            // Only save chapters if forcing, if the video changed, or if there are not already any saved ones
-            if (force || videoFileChanged || _itemRepo.GetChapter(video.Id, 0) == null)
-            {
-                await _itemRepo.SaveChapters(video.Id, chapters, cancellationToken).ConfigureAwait(false);
-            }
-        }
-
-        /// <summary>
-        /// Fetches the WTV info.
-        /// </summary>
-        /// <param name="video">The video.</param>
-        /// <param name="force">if set to <c>true</c> [force].</param>
-        /// <param name="data">The data.</param>
-        private void FetchWtvInfo(Video video, bool force, InternalMediaInfoResult data)
-        {
-            if (data.format == null || data.format.tags == null)
-            {
-                return;
-            }
-
-            if (force || video.Genres.Count == 0)
-            {
-                if (!video.LockedFields.Contains(MetadataFields.Genres))
-                {
-                    var genres = FFProbeHelpers.GetDictionaryValue(data.format.tags, "genre");
-
-                    if (!string.IsNullOrEmpty(genres))
-                    {
-                        video.Genres = genres.Split(new[] { ';', '/', ',' }, StringSplitOptions.RemoveEmptyEntries)
-                            .Where(i => !string.IsNullOrWhiteSpace(i))
-                            .Select(i => i.Trim())
-                            .ToList();
-                    }
-                }
-            }
-
-            if (force || string.IsNullOrEmpty(video.Overview))
-            {
-                if (!video.LockedFields.Contains(MetadataFields.Overview))
-                {
-                    var overview = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/SubTitleDescription");
-
-                    if (!string.IsNullOrWhiteSpace(overview))
-                    {
-                        video.Overview = overview;
-                    }
-                }
-            }
-
-            if (force || string.IsNullOrEmpty(video.OfficialRating))
-            {
-                var officialRating = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/ParentalRating");
-
-                if (!string.IsNullOrWhiteSpace(officialRating))
-                {
-                    if (!video.LockedFields.Contains(MetadataFields.OfficialRating))
-                    {
-                        video.OfficialRating = officialRating;
-                    }
-                }
-            }
-
-            if (force || video.People.Count == 0)
-            {
-                if (!video.LockedFields.Contains(MetadataFields.Cast))
-                {
-                    var people = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/MediaCredits");
-
-                    if (!string.IsNullOrEmpty(people))
-                    {
-                        video.People = people.Split(new[] { ';', '/' }, StringSplitOptions.RemoveEmptyEntries)
-                            .Where(i => !string.IsNullOrWhiteSpace(i))
-                            .Select(i => new PersonInfo { Name = i.Trim(), Type = PersonType.Actor })
-                            .ToList();
-                    }
-                }
-            }
-
-            if (force || !video.ProductionYear.HasValue)
-            {
-                var year = FFProbeHelpers.GetDictionaryValue(data.format.tags, "WM/OriginalReleaseTime");
-
-                if (!string.IsNullOrWhiteSpace(year))
-                {
-                    int val;
-
-                    if (int.TryParse(year, NumberStyles.Integer, UsCulture, out val))
-                    {
-                        video.ProductionYear = val;
-                    }
-                }
-            }
-        }
-
-        /// <summary>
-        /// Adds the external subtitles.
-        /// </summary>
-        /// <param name="video">The video.</param>
-        /// <param name="currentStreams">The current streams.</param>
-        private void AddExternalSubtitles(Video video, List<MediaStream> currentStreams)
-        {
-            var useParent = !video.ResolveArgs.IsDirectory;
-
-            if (useParent && video.Parent == null)
-            {
-                return;
-            }
-
-            var fileSystemChildren = useParent
-                                         ? video.Parent.ResolveArgs.FileSystemChildren
-                                         : video.ResolveArgs.FileSystemChildren;
-
-            var startIndex = currentStreams.Count;
-            var streams = new List<MediaStream>();
-
-            var videoFileNameWithoutExtension = Path.GetFileNameWithoutExtension(video.Path);
-
-            foreach (var file in fileSystemChildren
-                .Where(f => !f.Attributes.HasFlag(FileAttributes.Directory) && FilestampExtensions.Contains(Path.GetExtension(f.FullName), StringComparer.OrdinalIgnoreCase)))
-            {
-                var fullName = file.FullName;
-
-                var fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fullName);
-
-                // If the subtitle file matches the video file name
-                if (string.Equals(videoFileNameWithoutExtension, fileNameWithoutExtension, StringComparison.OrdinalIgnoreCase))
-                {
-                    streams.Add(new MediaStream
-                    {
-                        Index = startIndex++,
-                        Type = MediaStreamType.Subtitle,
-                        IsExternal = true,
-                        Path = fullName,
-                        Codec = Path.GetExtension(fullName).ToLower().TrimStart('.')
-                    });
-                }
-                else if (fileNameWithoutExtension.StartsWith(videoFileNameWithoutExtension + ".", StringComparison.OrdinalIgnoreCase))
-                {
-                    // Support xbmc naming conventions - 300.spanish.srt
-                    var language = fileNameWithoutExtension.Split('.').LastOrDefault();
-
-                    // Try to translate to three character code
-                    // Be flexible and check against both the full and three character versions
-                    var culture = _localization.GetCultures()
-                        .FirstOrDefault(i => string.Equals(i.DisplayName, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Name, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.ThreeLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase) || string.Equals(i.TwoLetterISOLanguageName, language, StringComparison.OrdinalIgnoreCase));
-
-                    if (culture != null)
-                    {
-                        language = culture.ThreeLetterISOLanguageName;
-                    }
-
-                    streams.Add(new MediaStream
-                    {
-                        Index = startIndex++,
-                        Type = MediaStreamType.Subtitle,
-                        IsExternal = true,
-                        Path = fullName,
-                        Codec = Path.GetExtension(fullName).ToLower().TrimStart('.'),
-                        Language = language
-                    });
-                }
-            }
-
-            currentStreams.AddRange(streams);
-        }
-
-        /// <summary>
-        /// The dummy chapter duration
-        /// </summary>
-        private readonly long _dummyChapterDuration = TimeSpan.FromMinutes(5).Ticks;
-
-        /// <summary>
-        /// Adds the dummy chapters.
-        /// </summary>
-        /// <param name="video">The video.</param>
-        /// <param name="chapters">The chapters.</param>
-        private void AddDummyChapters(Video video, List<ChapterInfo> chapters)
-        {
-            var runtime = video.RunTimeTicks ?? 0;
-
-            if (runtime < 0)
-            {
-                throw new ArgumentException(string.Format("{0} has invalid runtime of {1}", video.Name, runtime));
-            }
-
-            if (runtime < _dummyChapterDuration)
-            {
-                return;
-            }
-
-            long currentChapterTicks = 0;
-            var index = 1;
-
-            // Limit to 100 chapters just in case there's some incorrect metadata here
-            while (currentChapterTicks < runtime && index < 100)
-            {
-                chapters.Add(new ChapterInfo
-                {
-                    Name = "Chapter " + index,
-                    StartPositionTicks = currentChapterTicks
-                });
-
-                index++;
-                currentChapterTicks += _dummyChapterDuration;
-            }
-        }
-
-        /// <summary>
-        /// Fetches the bd info.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="chapters">The chapters.</param>
-        /// <param name="mediaStreams">The media streams.</param>
-        /// <param name="inputPath">The input path.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        private void FetchBdInfo(BaseItem item, List<ChapterInfo> chapters, List<MediaStream> mediaStreams, string inputPath, CancellationToken cancellationToken)
-        {
-            var video = (Video)item;
-
-            var result = GetBDInfo(inputPath);
-
-            cancellationToken.ThrowIfCancellationRequested();
-
-            int? currentHeight = null;
-            int? currentWidth = null;
-            int? currentBitRate = null;
-
-            var videoStream = mediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video);
-
-            // Grab the values that ffprobe recorded
-            if (videoStream != null)
-            {
-                currentBitRate = videoStream.BitRate;
-                currentWidth = videoStream.Width;
-                currentHeight = videoStream.Height;
-            }
-
-            // Fill video properties from the BDInfo result
-            Fetch(video, mediaStreams, result, chapters);
-
-            videoStream = mediaStreams.FirstOrDefault(s => s.Type == MediaStreamType.Video);
-
-            // Use the ffprobe values if these are empty
-            if (videoStream != null)
-            {
-                videoStream.BitRate = IsEmpty(videoStream.BitRate) ? currentBitRate : videoStream.BitRate;
-                videoStream.Width = IsEmpty(videoStream.Width) ? currentWidth : videoStream.Width;
-                videoStream.Height = IsEmpty(videoStream.Height) ? currentHeight : videoStream.Height;
-            }
-        }
-
-        /// <summary>
-        /// Determines whether the specified num is empty.
-        /// </summary>
-        /// <param name="num">The num.</param>
-        /// <returns><c>true</c> if the specified num is empty; otherwise, <c>false</c>.</returns>
-        private bool IsEmpty(int? num)
-        {
-            return !num.HasValue || num.Value == 0;
-        }
-
-        /// <summary>
-        /// Fills video properties from the VideoStream of the largest playlist
-        /// </summary>
-        /// <param name="video">The video.</param>
-        /// <param name="mediaStreams">The media streams.</param>
-        /// <param name="stream">The stream.</param>
-        /// <param name="chapters">The chapters.</param>
-        private void Fetch(Video video, List<MediaStream> mediaStreams, BlurayDiscInfo stream, List<ChapterInfo> chapters)
-        {
-            // Check all input for null/empty/zero
-
-            mediaStreams.Clear();
-            mediaStreams.AddRange(stream.MediaStreams);
-
-            video.MainFeaturePlaylistName = stream.PlaylistName;
-
-            if (stream.RunTimeTicks.HasValue && stream.RunTimeTicks.Value > 0)
-            {
-                video.RunTimeTicks = stream.RunTimeTicks;
-            }
-
-            video.PlayableStreamFileNames = stream.Files.ToList();
-
-            if (stream.Chapters != null)
-            {
-                chapters.Clear();
-
-                chapters.AddRange(stream.Chapters.Select(c => new ChapterInfo
-                {
-                    StartPositionTicks = TimeSpan.FromSeconds(c).Ticks
-
-                }));
-            }
-        }
-
-        /// <summary>
-        /// Gets information about the longest playlist on a bdrom
-        /// </summary>
-        /// <param name="path">The path.</param>
-        /// <returns>VideoStream.</returns>
-        private BlurayDiscInfo GetBDInfo(string path)
-        {
-            return _blurayExaminer.GetDiscInfo(path);
-        }
-    }
-}

+ 27 - 30
MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs

@@ -12,7 +12,7 @@ using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.MediaInfo
 {
-    public class VideoImageProvider : IDynamicImageProvider
+    public class VideoImageProvider : IDynamicImageProvider, IHasChangeMonitor
     {
         private readonly IIsoManager _isoManager;
         private readonly IMediaEncoder _mediaEncoder;
@@ -25,34 +25,6 @@ namespace MediaBrowser.Providers.MediaInfo
             _config = config;
         }
 
-        /// <summary>
-        /// Qualifieses for extraction.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
-        private bool QualifiesForExtraction(Video item)
-        {
-            // No support for this
-            if (item.VideoType == VideoType.HdDvd)
-            {
-                return false;
-            }
-
-            // Can't extract from iso's if we weren't unable to determine iso type
-            if (item.VideoType == VideoType.Iso && !item.IsoType.HasValue)
-            {
-                return false;
-            }
-
-            // Can't extract if we didn't find a video stream in the file
-            if (!item.DefaultVideoStreamIndex.HasValue)
-            {
-                return false;
-            }
-
-            return true;
-        }
-
         /// <summary>
         /// The null mount task result
         /// </summary>
@@ -81,7 +53,27 @@ namespace MediaBrowser.Providers.MediaInfo
 
         public Task<DynamicImageResponse> GetImage(IHasImages item, ImageType type, CancellationToken cancellationToken)
         {
-            return GetVideoImage((Video)item, cancellationToken);
+            var video = (Video)item;
+
+            // No support for this
+            if (video.VideoType == VideoType.HdDvd)
+            {
+                return Task.FromResult(new DynamicImageResponse { HasImage = false });
+            }
+
+            // Can't extract from iso's if we weren't unable to determine iso type
+            if (video.VideoType == VideoType.Iso && !video.IsoType.HasValue)
+            {
+                return Task.FromResult(new DynamicImageResponse { HasImage = false });
+            }
+
+            // Can't extract if we didn't find a video stream in the file
+            if (!video.DefaultVideoStreamIndex.HasValue)
+            {
+                return Task.FromResult(new DynamicImageResponse { HasImage = false });
+            }
+
+            return GetVideoImage(video, cancellationToken);
         }
 
         public async Task<DynamicImageResponse> GetVideoImage(Video item, CancellationToken cancellationToken)
@@ -133,5 +125,10 @@ namespace MediaBrowser.Providers.MediaInfo
 
             return item.LocationType == LocationType.FileSystem && item is Video;
         }
+
+        public bool HasChanged(IHasMetadata item, DateTime date)
+        {
+            return item.DateModified > date;
+        }
     }
 }

+ 0 - 356
MediaBrowser.Providers/Movies/FanArtMovieProvider.cs

@@ -1,356 +0,0 @@
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.IO;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Movies;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Logging;
-using MediaBrowser.Model.Providers;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Model.Net;
-using System.Net;
-using MediaBrowser.Providers.Music;
-
-namespace MediaBrowser.Providers.Movies
-{
-    /// <summary>
-    /// Class FanArtMovieProvider
-    /// </summary>
-    class FanartMovieProvider : BaseMetadataProvider
-    {
-        /// <summary>
-        /// Gets the HTTP client.
-        /// </summary>
-        /// <value>The HTTP client.</value>
-        protected IHttpClient HttpClient { get; private set; }
-
-        /// <summary>
-        /// The _provider manager
-        /// </summary>
-        private readonly IProviderManager _providerManager;
-
-        internal static FanartMovieProvider Current { get; private set; }
-        private readonly IFileSystem _fileSystem;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="FanartMovieProvider" /> class.
-        /// </summary>
-        /// <param name="httpClient">The HTTP client.</param>
-        /// <param name="logManager">The log manager.</param>
-        /// <param name="configurationManager">The configuration manager.</param>
-        /// <param name="providerManager">The provider manager.</param>
-        /// <exception cref="System.ArgumentNullException">httpClient</exception>
-        public FanartMovieProvider(IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager, IFileSystem fileSystem)
-            : base(logManager, configurationManager)
-        {
-            if (httpClient == null)
-            {
-                throw new ArgumentNullException("httpClient");
-            }
-            HttpClient = httpClient;
-            _providerManager = providerManager;
-            _fileSystem = fileSystem;
-            Current = this;
-        }
-
-        public override ItemUpdateType ItemUpdateType
-        {
-            get
-            {
-                return ItemUpdateType.ImageUpdate;
-            }
-        }
-
-        /// <summary>
-        /// Gets a value indicating whether [refresh on version change].
-        /// </summary>
-        /// <value><c>true</c> if [refresh on version change]; otherwise, <c>false</c>.</value>
-        protected override bool RefreshOnVersionChange
-        {
-            get
-            {
-                return true;
-            }
-        }
-
-        /// <summary>
-        /// Gets the provider version.
-        /// </summary>
-        /// <value>The provider version.</value>
-        protected override string ProviderVersion
-        {
-            get
-            {
-                return "13";
-            }
-        }
-
-        public override MetadataProviderPriority Priority
-        {
-            get
-            {
-                return MetadataProviderPriority.Fifth;
-            }
-        }
-
-        /// <summary>
-        /// The fan art base URL
-        /// </summary>
-        protected string FanArtBaseUrl = "http://api.fanart.tv/webservice/movie/{0}/{1}/xml/all/1/1";
-
-        /// <summary>
-        /// Supportses the specified item.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
-        public override bool Supports(BaseItem item)
-        {
-            var trailer = item as Trailer;
-
-            if (trailer != null)
-            {
-                return !trailer.IsLocalTrailer;
-            }
-
-            return item is Movie || item is MusicVideo;
-        }
-
-        /// <summary>
-        /// Needses the refresh internal.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="providerInfo">The provider info.</param>
-        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
-        protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
-        {
-            if (string.IsNullOrEmpty(item.GetProviderId(MetadataProviders.Tmdb)))
-            {
-                return false;
-            }
-
-            return base.NeedsRefreshInternal(item, providerInfo);
-        }
-
-        protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo)
-        {
-            var id = item.GetProviderId(MetadataProviders.Tmdb);
-
-            if (!string.IsNullOrEmpty(id))
-            {
-                // Process images
-                var xmlPath = GetFanartXmlPath(id);
-
-                var fileInfo = new FileInfo(xmlPath);
-
-                return !fileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(fileInfo) > providerInfo.LastRefreshed;
-            }
-
-            return base.NeedsRefreshBasedOnCompareDate(item, providerInfo);
-        }
-
-        /// <summary>
-        /// Gets the movie data path.
-        /// </summary>
-        /// <param name="appPaths">The app paths.</param>
-        /// <param name="tmdbId">The TMDB id.</param>
-        /// <returns>System.String.</returns>
-        internal static string GetMovieDataPath(IApplicationPaths appPaths, string tmdbId)
-        {
-            var dataPath = Path.Combine(GetMoviesDataPath(appPaths), tmdbId);
-
-            return dataPath;
-        }
-
-        /// <summary>
-        /// Gets the movie data path.
-        /// </summary>
-        /// <param name="appPaths">The app paths.</param>
-        /// <returns>System.String.</returns>
-        internal static string GetMoviesDataPath(IApplicationPaths appPaths)
-        {
-            var dataPath = Path.Combine(appPaths.DataPath, "fanart-movies");
-
-            return dataPath;
-        }
-
-        /// <summary>
-        /// Fetches metadata and returns true or false indicating if any work that requires persistence was done
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="force">if set to <c>true</c> [force].</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task{System.Boolean}.</returns>
-        public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
-        {
-            cancellationToken.ThrowIfCancellationRequested();
-
-            var movieId = item.GetProviderId(MetadataProviders.Tmdb);
-
-            if (!string.IsNullOrEmpty(movieId))
-            {
-                var xmlPath = GetFanartXmlPath(movieId);
-
-                // Only download the xml if it doesn't already exist. The prescan task will take care of getting updates
-                if (!File.Exists(xmlPath))
-                {
-                    await DownloadMovieXml(movieId, cancellationToken).ConfigureAwait(false);
-                }
-
-                var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualFanartMovieImageProvider.ProviderName).ConfigureAwait(false);
-
-                await FetchImages(item, images.ToList(), cancellationToken).ConfigureAwait(false);
-            }
-
-            SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
-            return true;
-        }
-
-        public string GetFanartXmlPath(string tmdbId)
-        {
-            var movieDataPath = GetMovieDataPath(ConfigurationManager.ApplicationPaths, tmdbId);
-            return Path.Combine(movieDataPath, "fanart.xml");
-        }
-
-        /// <summary>
-        /// Downloads the movie XML.
-        /// </summary>
-        /// <param name="tmdbId">The TMDB id.</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task.</returns>
-        internal async Task DownloadMovieXml(string tmdbId, CancellationToken cancellationToken)
-        {
-            cancellationToken.ThrowIfCancellationRequested();
-
-            var url = string.Format(FanArtBaseUrl, FanartArtistProvider.ApiKey, tmdbId);
-
-            var xmlPath = GetFanartXmlPath(tmdbId);
-
-            Directory.CreateDirectory(Path.GetDirectoryName(xmlPath));
-
-            using (var response = await HttpClient.Get(new HttpRequestOptions
-            {
-                Url = url,
-                ResourcePool = FanartArtistProvider.FanArtResourcePool,
-                CancellationToken = cancellationToken
-
-            }).ConfigureAwait(false))
-            {
-                using (var xmlFileStream = _fileSystem.GetFileStream(xmlPath, FileMode.Create, FileAccess.Write, FileShare.Read, true))
-                {
-                    await response.CopyToAsync(xmlFileStream).ConfigureAwait(false);
-                }
-            }
-        }
-
-        private readonly Task _cachedTask = Task.FromResult(true);
-        internal Task EnsureMovieXml(string tmdbId, CancellationToken cancellationToken)
-        {
-            var path = GetFanartXmlPath(tmdbId);
-
-            var fileInfo = _fileSystem.GetFileSystemInfo(path);
-
-            if (fileInfo.Exists)
-            {
-                if (ConfigurationManager.Configuration.EnableFanArtUpdates || (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 7)
-                {
-                    return _cachedTask;
-                }
-            }
-
-            return DownloadMovieXml(tmdbId, cancellationToken);
-        }
-
-        private async Task FetchImages(BaseItem item, List<RemoteImageInfo> images, CancellationToken cancellationToken)
-        {
-            cancellationToken.ThrowIfCancellationRequested();
-
-            var options = ConfigurationManager.Configuration.GetMetadataOptions("Movie") ?? new MetadataOptions();
-
-            if (options.IsEnabled(ImageType.Primary) && !item.HasImage(ImageType.Primary))
-            {
-                await SaveImage(item, images, ImageType.Primary, cancellationToken).ConfigureAwait(false);
-            }
-
-            cancellationToken.ThrowIfCancellationRequested();
-
-            if (options.IsEnabled(ImageType.Logo) && !item.HasImage(ImageType.Logo))
-            {
-                await SaveImage(item, images, ImageType.Logo, cancellationToken).ConfigureAwait(false);
-            }
-
-            cancellationToken.ThrowIfCancellationRequested();
-
-            if (options.IsEnabled(ImageType.Art) && !item.HasImage(ImageType.Art))
-            {
-                await SaveImage(item, images, ImageType.Art, cancellationToken).ConfigureAwait(false);
-            }
-
-            cancellationToken.ThrowIfCancellationRequested();
-
-            if (options.IsEnabled(ImageType.Disc) && !item.HasImage(ImageType.Disc))
-            {
-                await SaveImage(item, images, ImageType.Disc, cancellationToken).ConfigureAwait(false);
-            }
-
-            cancellationToken.ThrowIfCancellationRequested();
-
-            if (options.IsEnabled(ImageType.Banner) && !item.HasImage(ImageType.Banner))
-            {
-                await SaveImage(item, images, ImageType.Banner, cancellationToken).ConfigureAwait(false);
-            }
-
-            cancellationToken.ThrowIfCancellationRequested();
-
-            if (options.IsEnabled(ImageType.Thumb) && !item.HasImage(ImageType.Thumb))
-            {
-                await SaveImage(item, images, ImageType.Thumb, cancellationToken).ConfigureAwait(false);
-            }
-
-            cancellationToken.ThrowIfCancellationRequested();
-
-            var backdropLimit = options.GetLimit(ImageType.Backdrop);
-
-            if (backdropLimit > 0 &&
-                item.BackdropImagePaths.Count < backdropLimit)
-            {
-                foreach (var image in images.Where(i => i.Type == ImageType.Backdrop))
-                {
-                    await _providerManager.SaveImage(item, image.Url, FanartArtistProvider.FanArtResourcePool, ImageType.Backdrop, null, cancellationToken)
-                                        .ConfigureAwait(false);
-
-                    if (item.BackdropImagePaths.Count >= backdropLimit) break;
-                }
-            }
-        }
-
-        private async Task SaveImage(BaseItem item, List<RemoteImageInfo> images, ImageType type, CancellationToken cancellationToken)
-        {
-            foreach (var image in images.Where(i => i.Type == type))
-            {
-                try
-                {
-                    await _providerManager.SaveImage(item, image.Url, FanartArtistProvider.FanArtResourcePool, type, null, cancellationToken).ConfigureAwait(false);
-                    break;
-                }
-                catch (HttpException ex)
-                {
-                    // Sometimes fanart has bad url's in their xml
-                    if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound)
-                    {
-                        continue;
-                    }
-                    break;
-                }
-            }
-        }
-    }
-}

+ 2 - 2
MediaBrowser.Providers/Movies/FanArtMovieUpdatesPrescanTask.cs

@@ -60,7 +60,7 @@ namespace MediaBrowser.Providers.Movies
                 return;
             }
 
-            var path = FanartMovieProvider.GetMoviesDataPath(_config.CommonApplicationPaths);
+            var path = FanartMovieImageProvider.GetMoviesDataPath(_config.CommonApplicationPaths);
 
             Directory.CreateDirectory(path);
             
@@ -136,7 +136,7 @@ namespace MediaBrowser.Providers.Movies
             {
                 _logger.Info("Updating movie " + id);
 
-                await FanartMovieProvider.Current.DownloadMovieXml(id, cancellationToken).ConfigureAwait(false);
+                await FanartMovieImageProvider.Current.DownloadMovieXml(id, cancellationToken).ConfigureAwait(false);
 
                 numComplete++;
                 double percent = numComplete;

+ 94 - 7
MediaBrowser.Providers/Movies/ManualFanartMovieImageProvider.cs → MediaBrowser.Providers/Movies/FanartMovieImageProvider.cs

@@ -1,4 +1,5 @@
-using MediaBrowser.Common.IO;
+using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.IO;
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
@@ -7,6 +8,7 @@ using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Providers;
+using MediaBrowser.Providers.Music;
 using System;
 using System.Collections.Generic;
 using System.Globalization;
@@ -16,22 +18,27 @@ using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
 using System.Xml;
-using MediaBrowser.Providers.Music;
 
 namespace MediaBrowser.Providers.Movies
 {
-    public class ManualFanartMovieImageProvider : IRemoteImageProvider, IHasChangeMonitor, IHasOrder
+    public class FanartMovieImageProvider : IRemoteImageProvider, IHasChangeMonitor, IHasOrder
     {
         private readonly CultureInfo _usCulture = new CultureInfo("en-US");
         private readonly IServerConfigurationManager _config;
         private readonly IHttpClient _httpClient;
         private readonly IFileSystem _fileSystem;
 
-        public ManualFanartMovieImageProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem)
+        private const string FanArtBaseUrl = "http://api.fanart.tv/webservice/movie/{0}/{1}/xml/all/1/1";
+
+        internal static FanartMovieImageProvider Current;
+
+        public FanartMovieImageProvider(IServerConfigurationManager config, IHttpClient httpClient, IFileSystem fileSystem)
         {
             _config = config;
             _httpClient = httpClient;
             _fileSystem = fileSystem;
+
+            Current = this;
         }
 
         public string Name
@@ -86,9 +93,9 @@ namespace MediaBrowser.Providers.Movies
 
             if (!string.IsNullOrEmpty(movieId))
             {
-                await FanartMovieProvider.Current.EnsureMovieXml(movieId, cancellationToken).ConfigureAwait(false);
+                await EnsureMovieXml(movieId, cancellationToken).ConfigureAwait(false);
 
-                var xmlPath = FanartMovieProvider.Current.GetFanartXmlPath(movieId);
+                var xmlPath = GetFanartXmlPath(movieId);
 
                 try
                 {
@@ -344,7 +351,7 @@ namespace MediaBrowser.Providers.Movies
             if (!string.IsNullOrEmpty(id))
             {
                 // Process images
-                var xmlPath = FanartMovieProvider.Current.GetFanartXmlPath(id);
+                var xmlPath = GetFanartXmlPath(id);
 
                 var fileInfo = new FileInfo(xmlPath);
 
@@ -353,5 +360,85 @@ namespace MediaBrowser.Providers.Movies
 
             return false;
         }
+
+        /// <summary>
+        /// Gets the movie data path.
+        /// </summary>
+        /// <param name="appPaths">The app paths.</param>
+        /// <param name="tmdbId">The TMDB id.</param>
+        /// <returns>System.String.</returns>
+        internal static string GetMovieDataPath(IApplicationPaths appPaths, string tmdbId)
+        {
+            var dataPath = Path.Combine(GetMoviesDataPath(appPaths), tmdbId);
+
+            return dataPath;
+        }
+
+        /// <summary>
+        /// Gets the movie data path.
+        /// </summary>
+        /// <param name="appPaths">The app paths.</param>
+        /// <returns>System.String.</returns>
+        internal static string GetMoviesDataPath(IApplicationPaths appPaths)
+        {
+            var dataPath = Path.Combine(appPaths.DataPath, "fanart-movies");
+
+            return dataPath;
+        }
+
+        public string GetFanartXmlPath(string tmdbId)
+        {
+            var movieDataPath = GetMovieDataPath(_config.ApplicationPaths, tmdbId);
+            return Path.Combine(movieDataPath, "fanart.xml");
+        }
+
+        /// <summary>
+        /// Downloads the movie XML.
+        /// </summary>
+        /// <param name="tmdbId">The TMDB id.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        internal async Task DownloadMovieXml(string tmdbId, CancellationToken cancellationToken)
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+
+            var url = string.Format(FanArtBaseUrl, FanartArtistProvider.ApiKey, tmdbId);
+
+            var xmlPath = GetFanartXmlPath(tmdbId);
+
+            Directory.CreateDirectory(Path.GetDirectoryName(xmlPath));
+
+            using (var response = await _httpClient.Get(new HttpRequestOptions
+            {
+                Url = url,
+                ResourcePool = FanartArtistProvider.FanArtResourcePool,
+                CancellationToken = cancellationToken
+
+            }).ConfigureAwait(false))
+            {
+                using (var xmlFileStream = _fileSystem.GetFileStream(xmlPath, FileMode.Create, FileAccess.Write, FileShare.Read, true))
+                {
+                    await response.CopyToAsync(xmlFileStream).ConfigureAwait(false);
+                }
+            }
+        }
+
+        private readonly Task _cachedTask = Task.FromResult(true);
+        internal Task EnsureMovieXml(string tmdbId, CancellationToken cancellationToken)
+        {
+            var path = GetFanartXmlPath(tmdbId);
+
+            var fileInfo = _fileSystem.GetFileSystemInfo(path);
+
+            if (fileInfo.Exists)
+            {
+                if (_config.Configuration.EnableFanArtUpdates || (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 7)
+                {
+                    return _cachedTask;
+                }
+            }
+
+            return DownloadMovieXml(tmdbId, cancellationToken);
+        }
     }
 }

+ 245 - 0
MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs

@@ -0,0 +1,245 @@
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Serialization;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Net;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Providers.Movies
+{
+    public class GenericMovieDbInfo<T>
+        where T : Video, new()
+    {
+        private readonly ILogger _logger;
+        private readonly IJsonSerializer _jsonSerializer;
+
+        private readonly CultureInfo _usCulture = new CultureInfo("en-US");
+
+        public GenericMovieDbInfo(ILogger logger, IJsonSerializer jsonSerializer)
+        {
+            _logger = logger;
+            _jsonSerializer = jsonSerializer;
+        }
+
+        public async Task<MetadataResult<T>> GetMetadata(ItemId itemId, CancellationToken cancellationToken)
+        {
+            var result = new MetadataResult<T>();
+
+            var tmdbId = itemId.GetProviderId(MetadataProviders.Tmdb);
+            var imdbId = itemId.GetProviderId(MetadataProviders.Imdb);
+
+            // Don't search for music video id's because it is very easy to misidentify. 
+            if (string.IsNullOrEmpty(tmdbId) && string.IsNullOrEmpty(imdbId) && typeof(T) != typeof(MusicVideo))
+            {
+                tmdbId = await new MovieDbSearch(_logger, _jsonSerializer)
+                    .FindMovieId(itemId, cancellationToken).ConfigureAwait(false);
+            }
+
+            if (!string.IsNullOrEmpty(tmdbId) || !string.IsNullOrEmpty(imdbId))
+            {
+                cancellationToken.ThrowIfCancellationRequested();
+
+                result.Item = await FetchMovieData(tmdbId, imdbId, itemId.MetadataLanguage, itemId.MetadataCountryCode, cancellationToken).ConfigureAwait(false);
+
+                result.HasMetadata = result.Item != null;
+            }
+
+            return result;
+        }
+
+        /// <summary>
+        /// Fetches the movie data.
+        /// </summary>
+        /// <param name="tmdbId">The TMDB identifier.</param>
+        /// <param name="imdbId">The imdb identifier.</param>
+        /// <param name="language">The language.</param>
+        /// <param name="preferredCountryCode">The preferred country code.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task{`0}.</returns>
+        private async Task<T> FetchMovieData(string tmdbId, string imdbId, string language, string preferredCountryCode, CancellationToken cancellationToken)
+        {
+            string dataFilePath = null;
+            MovieDbProvider.CompleteMovieData movieInfo = null;
+
+            // Id could be ImdbId or TmdbId
+            if (string.IsNullOrEmpty(tmdbId))
+            {
+                movieInfo = await MovieDbProvider.Current.FetchMainResult(imdbId, language, cancellationToken).ConfigureAwait(false);
+                if (movieInfo == null) return null;
+
+                tmdbId = movieInfo.id.ToString(_usCulture);
+
+                dataFilePath = MovieDbProvider.Current.GetDataFilePath(tmdbId, language);
+                Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath));
+                _jsonSerializer.SerializeToFile(movieInfo, dataFilePath);
+            }
+
+            await MovieDbProvider.Current.EnsureMovieInfo(tmdbId, language, cancellationToken).ConfigureAwait(false);
+
+            dataFilePath = dataFilePath ?? MovieDbProvider.Current.GetDataFilePath(tmdbId, language);
+            movieInfo = movieInfo ?? _jsonSerializer.DeserializeFromFile<MovieDbProvider.CompleteMovieData>(dataFilePath);
+
+            var item = new T();
+
+            ProcessMainInfo(item, preferredCountryCode, movieInfo);
+
+            return item;
+        }
+
+        /// <summary>
+        /// Processes the main info.
+        /// </summary>
+        /// <param name="movie">The movie.</param>
+        /// <param name="preferredCountryCode">The preferred country code.</param>
+        /// <param name="movieData">The movie data.</param>
+        private void ProcessMainInfo(T movie, string preferredCountryCode, MovieDbProvider.CompleteMovieData movieData)
+        {
+            movie.Name = movieData.title ?? movieData.original_title ?? movieData.name ?? movie.Name;
+
+            // Bug in Mono: WebUtility.HtmlDecode should return null if the string is null but in Mono it generate an System.ArgumentNullException.
+            movie.Overview = movieData.overview != null ? WebUtility.HtmlDecode(movieData.overview) : null;
+            movie.Overview = movie.Overview != null ? movie.Overview.Replace("\n\n", "\n") : null;
+
+            movie.HomePageUrl = movieData.homepage;
+
+            var hasBudget = movie as IHasBudget;
+            if (hasBudget != null)
+            {
+                hasBudget.Budget = movieData.budget;
+                hasBudget.Revenue = movieData.revenue;
+            }
+
+            if (!string.IsNullOrEmpty(movieData.tagline))
+            {
+                var hasTagline = movie as IHasTaglines;
+                if (hasTagline != null)
+                {
+                    hasTagline.Taglines.Clear();
+                    hasTagline.AddTagline(movieData.tagline);
+                }
+            }
+
+            movie.SetProviderId(MetadataProviders.Tmdb, movieData.id.ToString(_usCulture));
+            movie.SetProviderId(MetadataProviders.Imdb, movieData.imdb_id);
+
+            if (movieData.belongs_to_collection != null)
+            {
+                movie.SetProviderId(MetadataProviders.TmdbCollection,
+                                    movieData.belongs_to_collection.id.ToString(CultureInfo.InvariantCulture));
+
+                var movieItem = movie as Movie;
+
+                if (movieItem != null)
+                {
+                    movieItem.TmdbCollectionName = movieData.belongs_to_collection.name;
+                }
+            }
+            else
+            {
+                movie.SetProviderId(MetadataProviders.TmdbCollection, null); // clear out any old entry
+            }
+
+            float rating;
+            string voteAvg = movieData.vote_average.ToString(CultureInfo.InvariantCulture);
+
+            if (float.TryParse(voteAvg, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out rating))
+            {
+                movie.CommunityRating = rating;
+            }
+
+            movie.VoteCount = movieData.vote_count;
+
+            //release date and certification are retrieved based on configured country and we fall back on US if not there and to minimun release date if still no match
+            if (movieData.releases != null && movieData.releases.countries != null)
+            {
+                var ourRelease = movieData.releases.countries.FirstOrDefault(c => c.iso_3166_1.Equals(preferredCountryCode, StringComparison.OrdinalIgnoreCase)) ?? new MovieDbProvider.Country();
+                var usRelease = movieData.releases.countries.FirstOrDefault(c => c.iso_3166_1.Equals("US", StringComparison.OrdinalIgnoreCase)) ?? new MovieDbProvider.Country();
+                var minimunRelease = movieData.releases.countries.OrderBy(c => c.release_date).FirstOrDefault() ?? new MovieDbProvider.Country();
+
+                var ratingPrefix = string.Equals(preferredCountryCode, "us", StringComparison.OrdinalIgnoreCase) ? "" : preferredCountryCode + "-";
+                movie.OfficialRating = !string.IsNullOrEmpty(ourRelease.certification)
+                                           ? ratingPrefix + ourRelease.certification
+                                           : !string.IsNullOrEmpty(usRelease.certification)
+                                                 ? usRelease.certification
+                                                 : !string.IsNullOrEmpty(minimunRelease.certification)
+                                                       ? minimunRelease.iso_3166_1 + "-" + minimunRelease.certification
+                                                       : null;
+            }
+
+            if (movieData.release_date.Year != 1)
+            {
+                //no specific country release info at all
+                movie.PremiereDate = movieData.release_date.ToUniversalTime();
+                movie.ProductionYear = movieData.release_date.Year;
+            }
+
+            //studios
+            if (movieData.production_companies != null)
+            {
+                movie.Studios.Clear();
+
+                foreach (var studio in movieData.production_companies.Select(c => c.name))
+                {
+                    movie.AddStudio(studio);
+                }
+            }
+
+            // genres
+            // Movies get this from imdb
+            var genres = movieData.genres ?? new List<MovieDbProvider.GenreItem>();
+
+            foreach (var genre in genres.Select(g => g.name))
+            {
+                movie.AddGenre(genre);
+            }
+
+            //Actors, Directors, Writers - all in People
+            //actors come from cast
+            if (movieData.casts != null && movieData.casts.cast != null)
+            {
+                foreach (var actor in movieData.casts.cast.OrderBy(a => a.order)) movie.AddPerson(new PersonInfo { Name = actor.name.Trim(), Role = actor.character, Type = PersonType.Actor, SortOrder = actor.order });
+            }
+
+            //and the rest from crew
+            if (movieData.casts != null && movieData.casts.crew != null)
+            {
+                foreach (var person in movieData.casts.crew) movie.AddPerson(new PersonInfo { Name = person.name.Trim(), Role = person.job, Type = person.department });
+            }
+
+            if (movieData.keywords != null && movieData.keywords.keywords != null)
+            {
+                var hasTags = movie as IHasKeywords;
+                if (hasTags != null)
+                {
+                    hasTags.Keywords = movieData.keywords.keywords.Select(i => i.name).ToList();
+                }
+            }
+
+            if (movieData.trailers != null && movieData.trailers.youtube != null &&
+                movieData.trailers.youtube.Count > 0)
+            {
+                var hasTrailers = movie as IHasTrailers;
+                if (hasTrailers != null)
+                {
+                    hasTrailers.RemoteTrailers = movieData.trailers.youtube.Select(i => new MediaUrl
+                    {
+                        Url = string.Format("http://www.youtube.com/watch?v={0}", i.source),
+                        IsDirectLink = false,
+                        Name = i.name,
+                        VideoSize = string.Equals("hd", i.size, StringComparison.OrdinalIgnoreCase) ? VideoSize.HighDefinition : VideoSize.StandardDefinition
+
+                    }).ToList();
+                }
+            }
+        }
+
+    }
+}

+ 12 - 4
MediaBrowser.Providers/Movies/ManualMovieDbImageProvider.cs → MediaBrowser.Providers/Movies/MovieDbImageProvider.cs

@@ -15,12 +15,12 @@ using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.Movies
 {
-    class ManualMovieDbImageProvider : IRemoteImageProvider, IHasOrder
+    class MovieDbImageProvider : IRemoteImageProvider, IHasOrder
     {
         private readonly IJsonSerializer _jsonSerializer;
         private readonly IHttpClient _httpClient;
 
-        public ManualMovieDbImageProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient)
+        public MovieDbImageProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient)
         {
             _jsonSerializer = jsonSerializer;
             _httpClient = httpClient;
@@ -168,9 +168,17 @@ namespace MediaBrowser.Providers.Movies
         private async Task<MovieDbProvider.Images> FetchImages(BaseItem item, IJsonSerializer jsonSerializer,
             CancellationToken cancellationToken)
         {
-            await MovieDbProvider.Current.EnsureMovieInfo(item, cancellationToken).ConfigureAwait(false);
+            var tmdbId = item.GetProviderId(MetadataProviders.Tmdb);
+            var language = item.GetPreferredMetadataLanguage();
+
+            if (string.IsNullOrEmpty(tmdbId))
+            {
+                return null;
+            }
+
+            await MovieDbProvider.Current.EnsureMovieInfo(tmdbId, language, cancellationToken).ConfigureAwait(false);
 
-            var path = MovieDbProvider.Current.GetDataFilePath(item);
+            var path = MovieDbProvider.Current.GetDataFilePath(tmdbId, language);
 
             if (!string.IsNullOrEmpty(path))
             {

+ 0 - 247
MediaBrowser.Providers/Movies/MovieDbImagesProvider.cs

@@ -1,247 +0,0 @@
-using MediaBrowser.Common.IO;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Movies;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Logging;
-using MediaBrowser.Model.Providers;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Providers.Movies
-{
-    /// <summary>
-    /// Class MovieDbImagesProvider
-    /// </summary>
-    public class MovieDbImagesProvider : BaseMetadataProvider
-    {
-        /// <summary>
-        /// The _provider manager
-        /// </summary>
-        private readonly IProviderManager _providerManager;
-
-        private readonly IFileSystem _fileSystem;
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="MovieDbImagesProvider"/> class.
-        /// </summary>
-        /// <param name="logManager">The log manager.</param>
-        /// <param name="configurationManager">The configuration manager.</param>
-        /// <param name="providerManager">The provider manager.</param>
-        public MovieDbImagesProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager, IFileSystem fileSystem)
-            : base(logManager, configurationManager)
-        {
-            _providerManager = providerManager;
-            _fileSystem = fileSystem;
-        }
-
-        /// <summary>
-        /// Gets the priority.
-        /// </summary>
-        /// <value>The priority.</value>
-        public override MetadataProviderPriority Priority
-        {
-            get { return MetadataProviderPriority.Fourth; }
-        }
-
-        /// <summary>
-        /// Supports the specified item.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
-        public override bool Supports(BaseItem item)
-        {
-            var trailer = item as Trailer;
-
-            if (trailer != null)
-            {
-                return !trailer.IsLocalTrailer;
-            }
-
-            // Don't support local trailers
-            return item is Movie || item is MusicVideo;
-        }
-
-        public override ItemUpdateType ItemUpdateType
-        {
-            get
-            {
-                return ItemUpdateType.ImageUpdate;
-            }
-        }
-
-        /// <summary>
-        /// Gets a value indicating whether [requires internet].
-        /// </summary>
-        /// <value><c>true</c> if [requires internet]; otherwise, <c>false</c>.</value>
-        public override bool RequiresInternet
-        {
-            get
-            {
-                return true;
-            }
-        }
-
-        /// <summary>
-        /// Gets a value indicating whether [refresh on version change].
-        /// </summary>
-        /// <value><c>true</c> if [refresh on version change]; otherwise, <c>false</c>.</value>
-        protected override bool RefreshOnVersionChange
-        {
-            get
-            {
-                return true;
-            }
-        }
-
-        /// <summary>
-        /// Gets the provider version.
-        /// </summary>
-        /// <value>The provider version.</value>
-        protected override string ProviderVersion
-        {
-            get
-            {
-                return "3";
-            }
-        }
-
-        /// <summary>
-        /// Needses the refresh internal.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="providerInfo">The provider info.</param>
-        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
-        protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
-        {
-            if (string.IsNullOrEmpty(item.GetProviderId(MetadataProviders.Tmdb)))
-            {
-                return false;
-            }
-
-            var options = ConfigurationManager.Configuration.GetMetadataOptions("Movie") ?? new MetadataOptions();
-            
-            // Don't refresh if we already have both poster and backdrop and we're not refreshing images
-            if (item.HasImage(ImageType.Primary) &&
-                item.BackdropImagePaths.Count >= options.GetLimit(ImageType.Backdrop) &&
-                !item.LockedFields.Contains(MetadataFields.Images))
-            {
-                return false;
-            }
-
-            return base.NeedsRefreshInternal(item, providerInfo);
-        }
-
-        protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo)
-        {
-            var path = MovieDbProvider.Current.GetDataFilePath(item);
-
-            if (!string.IsNullOrEmpty(path))
-            {
-                var fileInfo = new FileInfo(path);
-
-                if (fileInfo.Exists)
-                {
-                    return _fileSystem.GetLastWriteTimeUtc(fileInfo) > providerInfo.LastRefreshed;
-                }
-            }
-
-            return false;
-        }
-
-        /// <summary>
-        /// Fetches metadata and returns true or false indicating if any work that requires persistence was done
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="force">if set to <c>true</c> [force].</param>
-        /// <param name="cancellationToken">The cancellation token</param>
-        /// <returns>Task{System.Boolean}.</returns>
-        public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
-        {
-            var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualMovieDbImageProvider.ProviderName).ConfigureAwait(false);
-            await ProcessImages(item, images.ToList(), cancellationToken).ConfigureAwait(false);
-
-            SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
-            return true;
-        }
-
-        /// <summary>
-        /// Processes the images.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="images">The images.</param>
-        /// <param name="cancellationToken">The cancellation token</param>
-        /// <returns>Task.</returns>
-        private async Task ProcessImages(BaseItem item, List<RemoteImageInfo> images, CancellationToken cancellationToken)
-        {
-            cancellationToken.ThrowIfCancellationRequested();
-
-            var eligiblePosters = images
-                .Where(i => i.Type == ImageType.Primary)
-                .ToList();
-
-            //        poster
-            if (eligiblePosters.Count > 0 && !item.HasImage(ImageType.Primary) && !item.LockedFields.Contains(MetadataFields.Images))
-            {
-                var poster = eligiblePosters[0];
-
-                var url = poster.Url;
-
-                var img = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions
-                {
-                    Url = url,
-                    CancellationToken = cancellationToken
-
-                }).ConfigureAwait(false);
-
-                await _providerManager.SaveImage(item, img, MimeTypes.GetMimeType(url), ImageType.Primary, null, cancellationToken)
-                                    .ConfigureAwait(false);
-            }
-
-            cancellationToken.ThrowIfCancellationRequested();
-
-            var options = ConfigurationManager.Configuration.GetMetadataOptions("Movie") ?? new MetadataOptions();
-            
-            var eligibleBackdrops = images
-                .Where(i => i.Type == ImageType.Backdrop && i.Width.HasValue && i.Width.Value >= options.GetMinWidth(ImageType.Backdrop))
-                .ToList();
-
-            var backdropLimit = options.GetLimit(ImageType.Backdrop);
-
-            // backdrops - only download if earlier providers didn't find any (fanart)
-            if (eligibleBackdrops.Count > 0 &&
-                options.IsEnabled(ImageType.Backdrop) &&
-                item.BackdropImagePaths.Count < backdropLimit &&
-                !item.LockedFields.Contains(MetadataFields.Backdrops))
-            {
-                for (var i = 0; i < eligibleBackdrops.Count; i++)
-                {
-                    var url = eligibleBackdrops[i].Url;
-
-                    var img = await MovieDbProvider.Current.GetMovieDbResponse(new HttpRequestOptions
-                    {
-                        Url = url,
-                        CancellationToken = cancellationToken
-
-                    }).ConfigureAwait(false);
-
-                    await _providerManager.SaveImage(item, img, MimeTypes.GetMimeType(url), ImageType.Backdrop, null, cancellationToken)
-                      .ConfigureAwait(false);
-
-                    if (item.BackdropImagePaths.Count >= backdropLimit)
-                    {
-                        break;
-                    }
-                }
-            }
-        }
-    }
-}

+ 53 - 457
MediaBrowser.Providers/Movies/MovieDbProvider.cs

@@ -5,16 +5,11 @@ using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Serialization;
-using MediaBrowser.Providers.Savers;
 using System;
 using System.Collections.Generic;
-using System.Globalization;
 using System.IO;
-using System.Linq;
-using System.Net;
 using System.Threading;
 using System.Threading.Tasks;
 
@@ -23,114 +18,55 @@ namespace MediaBrowser.Providers.Movies
     /// <summary>
     /// Class MovieDbProvider
     /// </summary>
-    public class MovieDbProvider : BaseMetadataProvider, IDisposable
+    public class MovieDbProvider : IRemoteMetadataProvider<Movie>, IDisposable
     {
-        protected static CultureInfo EnUs = new CultureInfo("en-US");
-
-        protected readonly IProviderManager ProviderManager;
-
-        /// <summary>
-        /// The movie db
-        /// </summary>
         internal readonly SemaphoreSlim MovieDbResourcePool = new SemaphoreSlim(1, 1);
 
         internal static MovieDbProvider Current { get; private set; }
 
-        /// <summary>
-        /// Gets the json serializer.
-        /// </summary>
-        /// <value>The json serializer.</value>
-        protected IJsonSerializer JsonSerializer { get; private set; }
-
-        /// <summary>
-        /// Gets the HTTP client.
-        /// </summary>
-        /// <value>The HTTP client.</value>
-        protected IHttpClient HttpClient { get; private set; }
+        private readonly IJsonSerializer _jsonSerializer;
+        private readonly IHttpClient _httpClient;
         private readonly IFileSystem _fileSystem;
+        private readonly IServerConfigurationManager _configurationManager;
+        private readonly ILogger _logger;
 
-        /// <summary>
-        /// Initializes a new instance of the <see cref="MovieDbProvider" /> class.
-        /// </summary>
-        /// <param name="logManager">The log manager.</param>
-        /// <param name="configurationManager">The configuration manager.</param>
-        /// <param name="jsonSerializer">The json serializer.</param>
-        /// <param name="httpClient">The HTTP client.</param>
-        /// <param name="providerManager">The provider manager.</param>
-        public MovieDbProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IHttpClient httpClient, IProviderManager providerManager, IFileSystem fileSystem)
-            : base(logManager, configurationManager)
-        {
-            JsonSerializer = jsonSerializer;
-            HttpClient = httpClient;
-            ProviderManager = providerManager;
+        public MovieDbProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILogger logger)
+        {
+            _jsonSerializer = jsonSerializer;
+            _httpClient = httpClient;
             _fileSystem = fileSystem;
+            _configurationManager = configurationManager;
+            _logger = logger;
             Current = this;
         }
 
-        /// <summary>
-        /// Releases unmanaged and - optionally - managed resources.
-        /// </summary>
-        /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
-        protected virtual void Dispose(bool dispose)
+        public Task<MetadataResult<Movie>> GetMetadata(ItemId id, CancellationToken cancellationToken)
         {
-            if (dispose)
-            {
-                MovieDbResourcePool.Dispose();
-            }
+            return GetItemMetadata<Movie>(id, cancellationToken);
         }
 
-        /// <summary>
-        /// Gets the priority.
-        /// </summary>
-        /// <value>The priority.</value>
-        public override MetadataProviderPriority Priority
+        public Task<MetadataResult<T>> GetItemMetadata<T>(ItemId id, CancellationToken cancellationToken)
+            where T : Video, new ()
         {
-            get { return MetadataProviderPriority.Third; }
+            var movieDb = new GenericMovieDbInfo<T>(_logger, _jsonSerializer);
+
+            return movieDb.GetMetadata(id, cancellationToken);
         }
 
-        /// <summary>
-        /// Supportses the specified item.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
-        public override bool Supports(BaseItem item)
+        public string Name
         {
-            var trailer = item as Trailer;
-
-            if (trailer != null)
-            {
-                return !trailer.IsLocalTrailer;
-            }
-
-            // Don't support local trailers
-            return item is Movie || item is MusicVideo;
+            get { return "TheMovieDb"; }
         }
 
         /// <summary>
-        /// Gets a value indicating whether [requires internet].
+        /// Releases unmanaged and - optionally - managed resources.
         /// </summary>
-        /// <value><c>true</c> if [requires internet]; otherwise, <c>false</c>.</value>
-        public override bool RequiresInternet
-        {
-            get
-            {
-                return true;
-            }
-        }
-
-        protected override bool RefreshOnVersionChange
-        {
-            get
-            {
-                return true;
-            }
-        }
-
-        protected override string ProviderVersion
+        /// <param name="dispose"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
+        protected virtual void Dispose(bool dispose)
         {
-            get
+            if (dispose)
             {
-                return "3";
+                MovieDbResourcePool.Dispose();
             }
         }
 
@@ -170,7 +106,7 @@ namespace MediaBrowser.Providers.Movies
 
                 }).ConfigureAwait(false))
                 {
-                    _tmdbSettings = JsonSerializer.DeserializeFromStream<TmdbSettingsResult>(json);
+                    _tmdbSettings = _jsonSerializer.DeserializeFromStream<TmdbSettingsResult>(json);
 
                     return _tmdbSettings;
                 }
@@ -187,30 +123,6 @@ namespace MediaBrowser.Providers.Movies
         internal static string ApiKey = "f6bd687ffa63cd282b6ff2c6877f2669";
         internal static string AcceptHeader = "application/json,image/*";
 
-        protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
-        {
-            if (string.IsNullOrEmpty(item.GetProviderId(MetadataProviders.Tmdb)))
-            {
-                return true;
-            }
-
-            return base.NeedsRefreshInternal(item, providerInfo);
-        }
-
-        protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo)
-        {
-            var path = GetDataFilePath(item);
-
-            if (!string.IsNullOrEmpty(path))
-            {
-                var fileInfo = new FileInfo(path);
-
-                return !fileInfo.Exists || _fileSystem.GetLastWriteTimeUtc(fileInfo) > providerInfo.LastRefreshed;
-            }
-
-            return base.NeedsRefreshBasedOnCompareDate(item, providerInfo);
-        }
-
         /// <summary>
         /// Gets the movie data path.
         /// </summary>
@@ -231,121 +143,6 @@ namespace MediaBrowser.Providers.Movies
             return dataPath;
         }
 
-        /// <summary>
-        /// Fetches metadata and returns true or false indicating if any work that requires persistence was done
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="force">if set to <c>true</c> [force].</param>
-        /// <param name="cancellationToken">The cancellation token</param>
-        /// <returns>Task{System.Boolean}.</returns>
-        public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
-        {
-            cancellationToken.ThrowIfCancellationRequested();
-
-            var id = item.GetProviderId(MetadataProviders.Tmdb);
-
-            if (string.IsNullOrEmpty(id))
-            {
-                id = item.GetProviderId(MetadataProviders.Imdb);
-            }
-
-            // Don't search for music video id's because it is very easy to misidentify. 
-            if (string.IsNullOrEmpty(id) && !(item is MusicVideo))
-            {
-                id = await new MovieDbSearch(Logger, JsonSerializer)
-                    .FindMovieId(GetId(item), cancellationToken).ConfigureAwait(false);
-            }
-
-            if (!string.IsNullOrEmpty(id))
-            {
-                cancellationToken.ThrowIfCancellationRequested();
-
-                await FetchMovieData(item, id, force, cancellationToken).ConfigureAwait(false);
-            }
-
-            SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
-            return true;
-        }
-
-        private ItemId GetId(IHasMetadata item)
-        {
-            return new ItemId
-            {
-                MetadataCountryCode = item.GetPreferredMetadataCountryCode(),
-                MetadataLanguage = item.GetPreferredMetadataLanguage(),
-                Name = item.Name,
-                ProviderIds = item.ProviderIds
-            };
-        }
-
-        /// <summary>
-        /// Determines whether [has alt meta] [the specified item].
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns><c>true</c> if [has alt meta] [the specified item]; otherwise, <c>false</c>.</returns>
-        internal static bool HasAltMeta(BaseItem item)
-        {
-            var path = MovieXmlSaver.GetMovieSavePath((Video)item);
-
-            if (item.LocationType == LocationType.FileSystem)
-            {
-                // If mixed with multiple movies in one folder, resolve args won't have the file system children
-                return item.ResolveArgs.ContainsMetaFileByName(Path.GetFileName(path)) || File.Exists(path);
-            }
-
-            return false;
-        }
-
-        private readonly CultureInfo _usCulture = new CultureInfo("en-US");
-
-        /// <summary>
-        /// Fetches the movie data.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="id">The id.</param>
-        /// <param name="isForcedRefresh">if set to <c>true</c> [is forced refresh].</param>
-        /// <param name="cancellationToken">The cancellation token</param>
-        /// <returns>Task.</returns>
-        private async Task FetchMovieData(BaseItem item, string id, bool isForcedRefresh, CancellationToken cancellationToken)
-        {
-            // Id could be ImdbId or TmdbId
-
-            var language = item.GetPreferredMetadataLanguage();
-
-            var dataFilePath = GetDataFilePath(item);
-
-            var tmdbId = item.GetProviderId(MetadataProviders.Tmdb);
-
-            if (string.IsNullOrEmpty(dataFilePath) || !File.Exists(dataFilePath))
-            {
-                var mainResult = await FetchMainResult(id, language, cancellationToken).ConfigureAwait(false);
-
-                if (mainResult == null) return;
-
-                tmdbId = mainResult.id.ToString(_usCulture);
-
-                dataFilePath = GetDataFilePath(tmdbId, language);
-
-                var directory = Path.GetDirectoryName(dataFilePath);
-
-                Directory.CreateDirectory(directory);
-
-                JsonSerializer.SerializeToFile(mainResult, dataFilePath);
-            }
-
-            if (isForcedRefresh || ConfigurationManager.Configuration.EnableTmdbUpdates || !HasAltMeta(item))
-            {
-                dataFilePath = GetDataFilePath(tmdbId, language);
-
-                if (!string.IsNullOrEmpty(dataFilePath))
-                {
-                    var mainResult = JsonSerializer.DeserializeFromFile<CompleteMovieData>(dataFilePath);
-
-                    ProcessMainInfo(item, mainResult);
-                }
-            }
-        }
-
         /// <summary>
         /// Downloads the movie info.
         /// </summary>
@@ -363,60 +160,49 @@ namespace MediaBrowser.Providers.Movies
 
             Directory.CreateDirectory(Path.GetDirectoryName(dataFilePath));
 
-            JsonSerializer.SerializeToFile(mainResult, dataFilePath);
+            _jsonSerializer.SerializeToFile(mainResult, dataFilePath);
         }
 
         private readonly Task _cachedTask = Task.FromResult(true);
-        internal Task EnsureMovieInfo(BaseItem item, CancellationToken cancellationToken)
+        internal Task EnsureMovieInfo(string tmdbId, string language, CancellationToken cancellationToken)
         {
-            var path = GetDataFilePath(item);
-
-            if (string.IsNullOrEmpty(path))
+            if (string.IsNullOrEmpty(tmdbId))
+            {
+                throw new ArgumentNullException("tmdbId");
+            }
+            if (string.IsNullOrEmpty(language))
             {
-                return _cachedTask;
+                throw new ArgumentNullException("language");
             }
-            
+
+            var path = GetDataFilePath(tmdbId, language);
+
             var fileInfo = _fileSystem.GetFileSystemInfo(path);
 
             if (fileInfo.Exists)
             {
                 // If it's recent or automatic updates are enabled, don't re-download
-                if ((ConfigurationManager.Configuration.EnableTmdbUpdates) || (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 7)
+                if ((_configurationManager.Configuration.EnableTmdbUpdates) || (DateTime.UtcNow - _fileSystem.GetLastWriteTimeUtc(fileInfo)).TotalDays <= 7)
                 {
                     return _cachedTask;
                 }
             }
 
-            var id = item.GetProviderId(MetadataProviders.Tmdb);
-
-            if (string.IsNullOrEmpty(id))
-            {
-                return _cachedTask;
-            }
-
-            return DownloadMovieInfo(id, item.GetPreferredMetadataLanguage(), cancellationToken);
+            return DownloadMovieInfo(tmdbId, language, cancellationToken);
         }
 
-        /// <summary>
-        /// Gets the data file path.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns>System.String.</returns>
-        internal string GetDataFilePath(BaseItem item)
+        internal string GetDataFilePath(string tmdbId, string preferredLanguage)
         {
-            var id = item.GetProviderId(MetadataProviders.Tmdb);
-
-            if (string.IsNullOrEmpty(id))
+            if (string.IsNullOrEmpty(tmdbId))
             {
-                return null;
+                throw new ArgumentNullException("tmdbId");
+            }
+            if (string.IsNullOrEmpty(preferredLanguage))
+            {
+                throw new ArgumentNullException("preferredLanguage");
             }
 
-            return GetDataFilePath(id, item.GetPreferredMetadataLanguage());
-        }
-
-        private string GetDataFilePath(string tmdbId, string preferredLanguage)
-        {
-            var path = GetMovieDataPath(ConfigurationManager.ApplicationPaths, tmdbId);
+            var path = GetMovieDataPath(_configurationManager.ApplicationPaths, tmdbId);
 
             var filename = string.Format("all-{0}.json",
                 preferredLanguage ?? string.Empty);
@@ -431,7 +217,7 @@ namespace MediaBrowser.Providers.Movies
         /// <param name="language">The language.</param>
         /// <param name="cancellationToken">The cancellation token</param>
         /// <returns>Task{CompleteMovieData}.</returns>
-        private async Task<CompleteMovieData> FetchMainResult(string id, string language, CancellationToken cancellationToken)
+        internal async Task<CompleteMovieData> FetchMainResult(string id, string language, CancellationToken cancellationToken)
         {
             var url = string.Format(GetMovieInfo3, id, ApiKey);
 
@@ -461,7 +247,7 @@ namespace MediaBrowser.Providers.Movies
 
             }).ConfigureAwait(false))
             {
-                mainResult = JsonSerializer.DeserializeFromStream<CompleteMovieData>(json);
+                mainResult = _jsonSerializer.DeserializeFromStream<CompleteMovieData>(json);
             }
 
             cancellationToken.ThrowIfCancellationRequested();
@@ -470,7 +256,7 @@ namespace MediaBrowser.Providers.Movies
             {
                 if (!string.IsNullOrEmpty(language) && !string.Equals(language, "en", StringComparison.OrdinalIgnoreCase))
                 {
-                    Logger.Info("MovieDbProvider couldn't find meta for language " + language + ". Trying English...");
+                    _logger.Info("MovieDbProvider couldn't find meta for language " + language + ". Trying English...");
 
                     url = string.Format(GetMovieInfo3, id, ApiKey) + "&include_image_language=en,null&language=en";
 
@@ -482,12 +268,12 @@ namespace MediaBrowser.Providers.Movies
 
                     }).ConfigureAwait(false))
                     {
-                        mainResult = JsonSerializer.DeserializeFromStream<CompleteMovieData>(json);
+                        mainResult = _jsonSerializer.DeserializeFromStream<CompleteMovieData>(json);
                     }
 
                     if (String.IsNullOrEmpty(mainResult.overview))
                     {
-                        Logger.Error("MovieDbProvider - Unable to find information for (id:" + id + ")");
+                        _logger.Error("MovieDbProvider - Unable to find information for (id:" + id + ")");
                         return null;
                     }
                 }
@@ -495,183 +281,6 @@ namespace MediaBrowser.Providers.Movies
             return mainResult;
         }
 
-        /// <summary>
-        /// Processes the main info.
-        /// </summary>
-        /// <param name="movie">The movie.</param>
-        /// <param name="movieData">The movie data.</param>
-        private void ProcessMainInfo(BaseItem movie, CompleteMovieData movieData)
-        {
-            if (!movie.LockedFields.Contains(MetadataFields.Name))
-            {
-                movie.Name = movieData.title ?? movieData.original_title ?? movieData.name ?? movie.Name;
-            }
-            if (!movie.LockedFields.Contains(MetadataFields.Overview))
-            {
-                // Bug in Mono: WebUtility.HtmlDecode should return null if the string is null but in Mono it generate an System.ArgumentNullException.
-                movie.Overview = movieData.overview != null ? WebUtility.HtmlDecode(movieData.overview) : null;
-                movie.Overview = movie.Overview != null ? movie.Overview.Replace("\n\n", "\n") : null;
-            }
-            movie.HomePageUrl = movieData.homepage;
-
-            var hasBudget = movie as IHasBudget;
-            if (hasBudget != null)
-            {
-                hasBudget.Budget = movieData.budget;
-                hasBudget.Revenue = movieData.revenue;
-            }
-
-            if (!string.IsNullOrEmpty(movieData.tagline))
-            {
-                var hasTagline = movie as IHasTaglines;
-                if (hasTagline != null)
-                {
-                    hasTagline.Taglines.Clear();
-                    hasTagline.AddTagline(movieData.tagline);
-                }
-            }
-
-            movie.SetProviderId(MetadataProviders.Tmdb, movieData.id.ToString(_usCulture));
-            movie.SetProviderId(MetadataProviders.Imdb, movieData.imdb_id);
-
-            if (movieData.belongs_to_collection != null)
-            {
-                movie.SetProviderId(MetadataProviders.TmdbCollection,
-                                    movieData.belongs_to_collection.id.ToString(CultureInfo.InvariantCulture));
-
-                var movieItem = movie as Movie;
-
-                if (movieItem != null)
-                {
-                    movieItem.TmdbCollectionName = movieData.belongs_to_collection.name;
-                }
-            }
-            else
-            {
-                movie.SetProviderId(MetadataProviders.TmdbCollection, null); // clear out any old entry
-            }
-
-            float rating;
-            string voteAvg = movieData.vote_average.ToString(CultureInfo.InvariantCulture);
-
-            // tmdb appears to have unified their numbers to always report "7.3" regardless of country
-            // so I removed the culture-specific processing here because it was not working for other countries -ebr
-            // Movies get this from imdb
-            if (!(movie is Movie) && float.TryParse(voteAvg, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out rating))
-            {
-                movie.CommunityRating = rating;
-            }
-
-            // Movies get this from imdb
-            if (!(movie is Movie))
-            {
-                movie.VoteCount = movieData.vote_count;
-            }
-
-            var preferredCountryCode = movie.GetPreferredMetadataCountryCode();
-
-            //release date and certification are retrieved based on configured country and we fall back on US if not there and to minimun release date if still no match
-            if (movieData.releases != null && movieData.releases.countries != null)
-            {
-                var ourRelease = movieData.releases.countries.FirstOrDefault(c => c.iso_3166_1.Equals(preferredCountryCode, 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();
-
-                if (!movie.LockedFields.Contains(MetadataFields.OfficialRating))
-                {
-                    var ratingPrefix = string.Equals(preferredCountryCode, "us", StringComparison.OrdinalIgnoreCase) ? "" : preferredCountryCode + "-";
-                    movie.OfficialRating = !string.IsNullOrEmpty(ourRelease.certification)
-                                               ? ratingPrefix + ourRelease.certification
-                                               : !string.IsNullOrEmpty(usRelease.certification)
-                                                     ? usRelease.certification
-                                                     : !string.IsNullOrEmpty(minimunRelease.certification)
-                                                           ? minimunRelease.iso_3166_1 + "-" + minimunRelease.certification
-                                                           : null;
-                }
-            }
-
-            if (movieData.release_date.Year != 1)
-            {
-                //no specific country release info at all
-                movie.PremiereDate = movieData.release_date.ToUniversalTime();
-                movie.ProductionYear = movieData.release_date.Year;
-            }
-
-            //studios
-            if (movieData.production_companies != null && !movie.LockedFields.Contains(MetadataFields.Studios))
-            {
-                movie.Studios.Clear();
-
-                foreach (var studio in movieData.production_companies.Select(c => c.name))
-                {
-                    movie.AddStudio(studio);
-                }
-            }
-
-            // genres
-            // Movies get this from imdb
-            var genres = movieData.genres ?? new List<GenreItem>();
-            if (!movie.LockedFields.Contains(MetadataFields.Genres))
-            {
-                // Only grab them if a boxset or there are no genres.
-                // For movies and trailers we'll use imdb via omdb
-                // But omdb data is for english users only so fetch if language is not english
-                if (!(movie is Movie) || movie.Genres.Count == 0 || !string.Equals(movie.GetPreferredMetadataLanguage(), "en", StringComparison.OrdinalIgnoreCase))
-                {
-                    movie.Genres.Clear();
-
-                    foreach (var genre in genres.Select(g => g.name))
-                    {
-                        movie.AddGenre(genre);
-                    }
-                }
-            }
-
-            if (!movie.LockedFields.Contains(MetadataFields.Cast))
-            {
-                movie.People.Clear();
-
-                //Actors, Directors, Writers - all in People
-                //actors come from cast
-                if (movieData.casts != null && movieData.casts.cast != null)
-                {
-                    foreach (var actor in movieData.casts.cast.OrderBy(a => a.order)) movie.AddPerson(new PersonInfo { Name = actor.name.Trim(), Role = actor.character, Type = PersonType.Actor, SortOrder = actor.order });
-                }
-
-                //and the rest from crew
-                if (movieData.casts != null && movieData.casts.crew != null)
-                {
-                    foreach (var person in movieData.casts.crew) movie.AddPerson(new PersonInfo { Name = person.name.Trim(), Role = person.job, Type = person.department });
-                }
-            }
-
-            if (movieData.keywords != null && movieData.keywords.keywords != null && !movie.LockedFields.Contains(MetadataFields.Keywords))
-            {
-                var hasTags = movie as IHasKeywords;
-                if (hasTags != null)
-                {
-                    hasTags.Keywords = movieData.keywords.keywords.Select(i => i.name).ToList();
-                }
-            }
-
-            if (movieData.trailers != null && movieData.trailers.youtube != null &&
-                movieData.trailers.youtube.Count > 0)
-            {
-                var hasTrailers = movie as IHasTrailers;
-                if (hasTrailers != null)
-                {
-                    hasTrailers.RemoteTrailers = movieData.trailers.youtube.Select(i => new MediaUrl
-                    {
-                        Url = string.Format("http://www.youtube.com/watch?v={0}", i.source),
-                        IsDirectLink = false,
-                        Name = i.name,
-                        VideoSize = string.Equals("hd", i.size, StringComparison.OrdinalIgnoreCase) ? VideoSize.HighDefinition : VideoSize.StandardDefinition
-
-                    }).ToList();
-                }
-            }
-        }
-
         private DateTime _lastRequestDate = DateTime.MinValue;
 
         /// <summary>
@@ -695,7 +304,7 @@ namespace MediaBrowser.Providers.Movies
 
                 _lastRequestDate = DateTime.Now;
 
-                return await HttpClient.Get(options).ConfigureAwait(false);
+                return await _httpClient.Get(options).ConfigureAwait(false);
             }
             finally
             {
@@ -897,18 +506,5 @@ namespace MediaBrowser.Providers.Movies
             public Keywords keywords { get; set; }
             public Trailers trailers { get; set; }
         }
-
-        internal class TmdbImageSettings
-        {
-            public List<string> backdrop_sizes { get; set; }
-            public string base_url { get; set; }
-            public List<string> poster_sizes { get; set; }
-            public List<string> profile_sizes { get; set; }
-        }
-
-        internal class TmdbSettingsResult
-        {
-            public TmdbImageSettings images { get; set; }
-        }
     }
 }

+ 43 - 0
MediaBrowser.Providers/Movies/MovieMetadataService.cs

@@ -0,0 +1,43 @@
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Providers.Manager;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Providers.Movies
+{
+    public class MovieMetadataService : MetadataService<Movie, ItemId>
+    {
+        private readonly ILibraryManager _libraryManager;
+
+        public MovieMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager)
+            : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem)
+        {
+            _libraryManager = libraryManager;
+        }
+
+        /// <summary>
+        /// Merges the specified source.
+        /// </summary>
+        /// <param name="source">The source.</param>
+        /// <param name="target">The target.</param>
+        /// <param name="lockedFields">The locked fields.</param>
+        /// <param name="replaceData">if set to <c>true</c> [replace data].</param>
+        /// <param name="mergeMetadataSettings">if set to <c>true</c> [merge metadata settings].</param>
+        protected override void MergeData(Movie source, Movie target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+        {
+            ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
+        }
+
+        protected override Task SaveItem(Movie item, ItemUpdateType reason, CancellationToken cancellationToken)
+        {
+            return _libraryManager.UpdateItem(item, reason, cancellationToken);
+        }
+    }
+}

+ 0 - 112
MediaBrowser.Providers/Movies/MovieProviderFromXml.cs

@@ -1,112 +0,0 @@
-using MediaBrowser.Common.IO;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Persistence;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Logging;
-using MediaBrowser.Providers.Savers;
-using System;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Providers.Movies
-{
-    /// <summary>
-    /// Class MovieProviderFromXml
-    /// </summary>
-    public class MovieProviderFromXml : BaseMetadataProvider
-    {
-        private readonly IItemRepository _itemRepo;
-        private readonly IFileSystem _fileSystem;
-
-        public MovieProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager, IItemRepository itemRepo, IFileSystem fileSystem)
-            : base(logManager, configurationManager)
-        {
-            _itemRepo = itemRepo;
-            _fileSystem = fileSystem;
-        }
-
-        /// <summary>
-        /// Supportses the specified item.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
-        public override bool Supports(BaseItem item)
-        {
-            if (item.LocationType != LocationType.FileSystem)
-            {
-                return false;
-            }
-
-            var trailer = item as Trailer;
-
-            if (trailer != null)
-            {
-                return !trailer.IsLocalTrailer;
-            }
-
-            // Check parent for null to avoid running this against things like video backdrops
-            return item is Video && !(item is Episode) && item.Parent != null;
-        }
-
-        /// <summary>
-        /// Gets the priority.
-        /// </summary>
-        /// <value>The priority.</value>
-        public override MetadataProviderPriority Priority
-        {
-            get { return MetadataProviderPriority.First; }
-        }
-
-        protected override bool NeedsRefreshBasedOnCompareDate(BaseItem item, BaseProviderInfo providerInfo)
-        {
-            var savePath = MovieXmlSaver.GetMovieSavePath((Video)item);
-
-            var xml = item.ResolveArgs.GetMetaFileByPath(savePath) ?? new FileInfo(savePath);
-
-            if (!xml.Exists)
-            {
-                return false;
-            }
-
-            return _fileSystem.GetLastWriteTimeUtc(xml) > item.DateLastSaved;
-        }
-
-        /// <summary>
-        /// Fetches metadata and returns true or false indicating if any work that requires persistence was done
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="force">if set to <c>true</c> [force].</param>
-        /// <param name="cancellationToken">The cancellation token.</param>
-        /// <returns>Task{System.Boolean}.</returns>
-        public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
-        {
-            cancellationToken.ThrowIfCancellationRequested();
-
-            var video = (Video)item;
-
-            var path = MovieXmlSaver.GetMovieSavePath(video);
-
-            if (File.Exists(path))
-            {
-                await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
-
-                try
-                {
-                    new MovieXmlParser(Logger).FetchAsync(video, path, cancellationToken);
-                }
-                finally
-                {
-                    XmlParsingResourcePool.Release();
-                }
-            }
-
-            SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
-
-            return true;
-        }
-    }
-}

+ 10 - 38
MediaBrowser.Providers/Movies/MovieXmlProvider.cs

@@ -4,11 +4,10 @@ using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Logging;
 using System.IO;
 using System.Threading;
-using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.Movies
 {
-    public class MovieXmlProvider : BaseXmlProvider, ILocalMetadataProvider<Movie>
+    public class MovieXmlProvider : BaseXmlProvider<Movie>
     {
         private readonly ILogger _logger;
 
@@ -18,61 +17,34 @@ namespace MediaBrowser.Providers.Movies
             _logger = logger;
         }
 
-        public async Task<MetadataResult<Movie>> GetMetadata(string path, CancellationToken cancellationToken)
+        protected override void Fetch(Movie item, string path, CancellationToken cancellationToken)
         {
-            path = GetXmlFile(path).FullName;
-
-            var result = new MetadataResult<Movie>();
-
-            await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
-
-            try
-            {
-                result.Item = new Movie();
-
-                new MovieXmlParser(_logger).Fetch(result.Item, path, cancellationToken);
-                result.HasMetadata = true;
-            }
-            catch (FileNotFoundException)
-            {
-                result.HasMetadata = false;
-            }
-            finally
-            {
-                XmlParsingResourcePool.Release();
-            }
-
-            return result;
-        }
-
-        public string Name
-        {
-            get { return "Media Browser Xml"; }
+            new MovieXmlParser(_logger).Fetch(item, path, cancellationToken);
         }
 
-        protected override FileInfo GetXmlFile(string path)
+        protected override FileInfo GetXmlFile(ItemInfo info)
         {
-            return GetXmlFileInfo(path, FileSystem);
+            return GetXmlFileInfo(info, FileSystem);
         }
 
-        public static FileInfo GetXmlFileInfo(string path, IFileSystem _fileSystem)
+        public static FileInfo GetXmlFileInfo(ItemInfo info, IFileSystem fileSystem)
         {
-            var fileInfo = _fileSystem.GetFileSystemInfo(path);
+            var fileInfo = fileSystem.GetFileSystemInfo(info.Path);
 
             var directoryInfo = fileInfo as DirectoryInfo;
 
             if (directoryInfo == null)
             {
-                directoryInfo = new DirectoryInfo(Path.GetDirectoryName(path));
+                directoryInfo = new DirectoryInfo(Path.GetDirectoryName(info.Path));
             }
 
             var directoryPath = directoryInfo.FullName;
 
-            var specificFile = Path.Combine(directoryPath, Path.GetFileNameWithoutExtension(path) + ".xml");
+            var specificFile = Path.Combine(directoryPath, Path.GetFileNameWithoutExtension(info.Path) + ".xml");
 
             var file = new FileInfo(specificFile);
 
-            return file.Exists ? file : new FileInfo(Path.Combine(directoryPath, "movie.xml"));
+            return info.IsInMixedFolder || file.Exists ? file : new FileInfo(Path.Combine(directoryPath, "movie.xml"));
         }
     }
 }

+ 0 - 285
MediaBrowser.Providers/Movies/OpenMovieDatabaseProvider.cs

@@ -1,285 +0,0 @@
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Movies;
-using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Logging;
-using MediaBrowser.Model.Serialization;
-using System;
-using System.Globalization;
-using System.Linq;
-using System.Net;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Providers.Movies
-{
-    public class OpenMovieDatabaseProvider : BaseMetadataProvider
-    {
-        private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
-
-        /// <summary>
-        /// Gets the json serializer.
-        /// </summary>
-        /// <value>The json serializer.</value>
-        protected IJsonSerializer JsonSerializer { get; private set; }
-
-        /// <summary>
-        /// Gets the HTTP client.
-        /// </summary>
-        /// <value>The HTTP client.</value>
-        protected IHttpClient HttpClient { get; private set; }
-
-        public OpenMovieDatabaseProvider(ILogManager logManager, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IHttpClient httpClient)
-            : base(logManager, configurationManager)
-        {
-            JsonSerializer = jsonSerializer;
-            HttpClient = httpClient;
-        }
-
-        /// <summary>
-        /// Gets the provider version.
-        /// </summary>
-        /// <value>The provider version.</value>
-        protected override string ProviderVersion
-        {
-            get
-            {
-                return "13";
-            }
-        }
-
-        /// <summary>
-        /// Gets a value indicating whether [requires internet].
-        /// </summary>
-        /// <value><c>true</c> if [requires internet]; otherwise, <c>false</c>.</value>
-        public override bool RequiresInternet
-        {
-            get
-            {
-                return true;
-            }
-        }
-
-        /// <summary>
-        /// Gets a value indicating whether [refresh on version change].
-        /// </summary>
-        /// <value><c>true</c> if [refresh on version change]; otherwise, <c>false</c>.</value>
-        protected override bool RefreshOnVersionChange
-        {
-            get
-            {
-                return true;
-            }
-        }
-
-        /// <summary>
-        /// Supports the specified item.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
-        public override bool Supports(BaseItem item)
-        {
-            var trailer = item as Trailer;
-
-            // Don't support local trailers
-            if (trailer != null)
-            {
-                return !trailer.IsLocalTrailer;
-            }
-
-            return item is Movie || item is MusicVideo || item is Series;
-        }
-
-        /// <summary>
-        /// Gets the priority.
-        /// </summary>
-        /// <value>The priority.</value>
-        public override MetadataProviderPriority Priority
-        {
-            get
-            {
-                // Run after moviedb and xml providers
-                return MetadataProviderPriority.Fifth;
-            }
-        }
-
-        protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
-
-        public override async Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
-        {
-            var imdbId = item.GetProviderId(MetadataProviders.Imdb);
-
-            if (string.IsNullOrEmpty(imdbId))
-            {
-                SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
-                return true;
-            }
-
-            var imdbParam = imdbId.StartsWith("tt", StringComparison.OrdinalIgnoreCase) ? imdbId : "tt" + imdbId;
-
-            var url = string.Format("http://www.omdbapi.com/?i={0}&tomatoes=true", imdbParam);
-
-            using (var stream = await HttpClient.Get(new HttpRequestOptions
-            {
-                Url = url,
-                ResourcePool = _resourcePool,
-                CancellationToken = cancellationToken
-
-            }).ConfigureAwait(false))
-            {
-                var result = JsonSerializer.DeserializeFromStream<RootObject>(stream);
-
-                var hasCriticRating = item as IHasCriticRating;
-                if (hasCriticRating != null)
-                {
-                    // Seeing some bogus RT data on omdb for series, so filter it out here
-                    // RT doesn't even have tv series
-                    int tomatoMeter;
-
-                    if (!string.IsNullOrEmpty(result.tomatoMeter)
-                        && int.TryParse(result.tomatoMeter, NumberStyles.Integer, UsCulture, out tomatoMeter)
-                        && tomatoMeter >= 0)
-                    {
-                        hasCriticRating.CriticRating = tomatoMeter;
-                    }
-
-                    if (!string.IsNullOrEmpty(result.tomatoConsensus)
-                        && !string.Equals(result.tomatoConsensus, "n/a", StringComparison.OrdinalIgnoreCase)
-                        && !string.Equals(result.tomatoConsensus, "No consensus yet.", StringComparison.OrdinalIgnoreCase))
-                    {
-                        hasCriticRating.CriticRatingSummary = WebUtility.HtmlDecode(result.tomatoConsensus);
-                    }
-                }
-
-                int voteCount;
-
-                if (!string.IsNullOrEmpty(result.imdbVotes)
-                    && int.TryParse(result.imdbVotes, NumberStyles.Number, UsCulture, out voteCount)
-                    && voteCount >= 0)
-                {
-                    item.VoteCount = voteCount;
-                }
-
-                float imdbRating;
-
-                if (!string.IsNullOrEmpty(result.imdbRating)
-                    && float.TryParse(result.imdbRating, NumberStyles.Any, UsCulture, out imdbRating)
-                    && imdbRating >= 0)
-                {
-                    item.CommunityRating = imdbRating;
-                }
-
-                if (!string.IsNullOrEmpty(result.Website)
-                        && !string.Equals(result.Website, "n/a", StringComparison.OrdinalIgnoreCase))
-                {
-                    item.HomePageUrl = result.Website;
-                }
-
-                ParseAdditionalMetadata(item, result);
-            }
-
-            SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
-            return true;
-        }
-
-        private void ParseAdditionalMetadata(BaseItem item, RootObject result)
-        {
-            // Grab series genres because imdb data is better than tvdb. Leave movies alone
-            // But only do it if english is the preferred language because this data will not be localized
-            if (!item.LockedFields.Contains(MetadataFields.Genres) &&
-                ShouldFetchGenres(item) &&
-                !string.IsNullOrWhiteSpace(result.Genre) &&
-                !string.Equals(result.Genre, "n/a", StringComparison.OrdinalIgnoreCase))
-            {
-                item.Genres.Clear();
-
-                foreach (var genre in result.Genre
-                    .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
-                    .Select(i => i.Trim())
-                    .Where(i => !string.IsNullOrWhiteSpace(i)))
-                {
-                    item.AddGenre(genre);
-                }
-            }
-
-            var hasMetascore = item as IHasMetascore;
-            if (hasMetascore != null)
-            {
-                float metascore;
-
-                if (!string.IsNullOrEmpty(result.Metascore) && float.TryParse(result.Metascore, NumberStyles.Any, UsCulture, out metascore) && metascore >= 0)
-                {
-                    hasMetascore.Metascore = metascore;
-                }
-            }
-
-            var hasAwards = item as IHasAwards;
-            if (hasAwards != null && !string.IsNullOrEmpty(result.Awards) && 
-                !string.Equals(result.Awards, "n/a", StringComparison.OrdinalIgnoreCase))
-            {
-                hasAwards.AwardSummary = WebUtility.HtmlDecode(result.Awards);
-            }
-        }
-
-        private bool ShouldFetchGenres(BaseItem item)
-        {
-            var lang = item.GetPreferredMetadataLanguage();
-
-            // The data isn't localized and so can only be used for english users
-            if (!string.Equals(lang, "en", StringComparison.OrdinalIgnoreCase))
-            {
-                return false;
-            }
-
-            // Only fetch if other providers didn't get anything
-            if (item is Trailer)
-            {
-                return item.Genres.Count == 0;
-            }
-
-            return item is Series || item is Movie;
-        }
-
-        protected class RootObject
-        {
-            public string Title { get; set; }
-            public string Year { get; set; }
-            public string Rated { get; set; }
-            public string Released { get; set; }
-            public string Runtime { get; set; }
-            public string Genre { get; set; }
-            public string Director { get; set; }
-            public string Writer { get; set; }
-            public string Actors { get; set; }
-            public string Plot { get; set; }
-            public string Poster { get; set; }
-            public string imdbRating { get; set; }
-            public string imdbVotes { get; set; }
-            public string imdbID { get; set; }
-            public string Type { get; set; }
-            public string tomatoMeter { get; set; }
-            public string tomatoImage { get; set; }
-            public string tomatoRating { get; set; }
-            public string tomatoReviews { get; set; }
-            public string tomatoFresh { get; set; }
-            public string tomatoRotten { get; set; }
-            public string tomatoConsensus { get; set; }
-            public string tomatoUserMeter { get; set; }
-            public string tomatoUserRating { get; set; }
-            public string tomatoUserReviews { get; set; }
-            public string DVD { get; set; }
-            public string BoxOffice { get; set; }
-            public string Production { get; set; }
-            public string Website { get; set; }
-            public string Response { get; set; }
-
-            public string Language { get; set; }
-            public string Country { get; set; }
-            public string Awards { get; set; }
-            public string Metascore { get; set; }
-        }
-    }
-}

+ 17 - 0
MediaBrowser.Providers/Movies/TmdbSettings.cs

@@ -0,0 +1,17 @@
+using System.Collections.Generic;
+
+namespace MediaBrowser.Providers.Movies
+{
+    internal class TmdbImageSettings
+    {
+        public List<string> backdrop_sizes { get; set; }
+        public string base_url { get; set; }
+        public List<string> poster_sizes { get; set; }
+        public List<string> profile_sizes { get; set; }
+    }
+
+    internal class TmdbSettingsResult
+    {
+        public TmdbImageSettings images { get; set; }
+    }
+}

+ 43 - 0
MediaBrowser.Providers/Movies/TrailerMetadataService.cs

@@ -0,0 +1,43 @@
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Providers.Manager;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Providers.Movies
+{
+    public class TrailerMetadataService : MetadataService<Trailer, ItemId>
+    {
+        private readonly ILibraryManager _libraryManager;
+
+        public TrailerMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager)
+            : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem)
+        {
+            _libraryManager = libraryManager;
+        }
+
+        /// <summary>
+        /// Merges the specified source.
+        /// </summary>
+        /// <param name="source">The source.</param>
+        /// <param name="target">The target.</param>
+        /// <param name="lockedFields">The locked fields.</param>
+        /// <param name="replaceData">if set to <c>true</c> [replace data].</param>
+        /// <param name="mergeMetadataSettings">if set to <c>true</c> [merge metadata settings].</param>
+        protected override void MergeData(Trailer source, Trailer target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+        {
+            ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
+        }
+
+        protected override Task SaveItem(Trailer item, ItemUpdateType reason, CancellationToken cancellationToken)
+        {
+            return _libraryManager.UpdateItem(item, reason, cancellationToken);
+        }
+    }
+}

+ 30 - 0
MediaBrowser.Providers/Movies/TrailerXmlProvider.cs

@@ -0,0 +1,30 @@
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Logging;
+using System.IO;
+using System.Threading;
+
+namespace MediaBrowser.Providers.Movies
+{
+    public class TrailerXmlProvider : BaseXmlProvider<Trailer>
+    {
+        private readonly ILogger _logger;
+
+        public TrailerXmlProvider(IFileSystem fileSystem, ILogger logger)
+            : base(fileSystem)
+        {
+            _logger = logger;
+        }
+
+        protected override void Fetch(Trailer item, string path, CancellationToken cancellationToken)
+        {
+            new MovieXmlParser(_logger).Fetch(item, path, cancellationToken);
+        }
+
+        protected override FileInfo GetXmlFile(ItemInfo info)
+        {
+            return MovieXmlProvider.GetXmlFileInfo(info, FileSystem);
+        }
+    }
+}

+ 5 - 34
MediaBrowser.Providers/Music/AlbumXmlProvider.cs

@@ -4,11 +4,10 @@ using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Logging;
 using System.IO;
 using System.Threading;
-using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.Music
 {
-    class AlbumXmlProvider : BaseXmlProvider, ILocalMetadataProvider<MusicAlbum>
+    class AlbumXmlProvider : BaseXmlProvider<MusicAlbum>
     {
         private readonly ILogger _logger;
 
@@ -18,42 +17,14 @@ namespace MediaBrowser.Providers.Music
             _logger = logger;
         }
 
-        public async Task<MetadataResult<MusicAlbum>> GetMetadata(string path, CancellationToken cancellationToken)
+        protected override void Fetch(MusicAlbum item, string path, CancellationToken cancellationToken)
         {
-            path = GetXmlFile(path).FullName;
-
-            var result = new MetadataResult<MusicAlbum>();
-
-            await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
-
-            try
-            {
-                var item = new MusicAlbum();
-
-                new BaseItemXmlParser<MusicAlbum>(_logger).Fetch(item, path, cancellationToken);
-                result.HasMetadata = true;
-                result.Item = item;
-            }
-            catch (FileNotFoundException)
-            {
-                result.HasMetadata = false;
-            }
-            finally
-            {
-                XmlParsingResourcePool.Release();
-            }
-
-            return result;
-        }
-
-        public string Name
-        {
-            get { return "Media Browser Xml"; }
+            new BaseItemXmlParser<MusicAlbum>(_logger).Fetch(item, path, cancellationToken);
         }
 
-        protected override FileInfo GetXmlFile(string path)
+        protected override FileInfo GetXmlFile(ItemInfo info)
         {
-            return new FileInfo(Path.Combine(path, "album.xml"));
+            return new FileInfo(Path.Combine(info.Path, "album.xml"));
         }
     }
 }

+ 5 - 34
MediaBrowser.Providers/Music/ArtistXmlProvider.cs

@@ -4,11 +4,10 @@ using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Logging;
 using System.IO;
 using System.Threading;
-using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.Music
 {
-    class ArtistXmlProvider : BaseXmlProvider, ILocalMetadataProvider<MusicArtist>
+    class ArtistXmlProvider : BaseXmlProvider<MusicArtist>
     {
         private readonly ILogger _logger;
 
@@ -18,42 +17,14 @@ namespace MediaBrowser.Providers.Music
             _logger = logger;
         }
 
-        public async Task<MetadataResult<MusicArtist>> GetMetadata(string path, CancellationToken cancellationToken)
+        protected override void Fetch(MusicArtist item, string path, CancellationToken cancellationToken)
         {
-            path = GetXmlFile(path).FullName;
-
-            var result = new MetadataResult<MusicArtist>();
-
-            await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
-
-            try
-            {
-                var item = new MusicArtist();
-
-                new BaseItemXmlParser<MusicArtist>(_logger).Fetch(item, path, cancellationToken);
-                result.HasMetadata = true;
-                result.Item = item;
-            }
-            catch (FileNotFoundException)
-            {
-                result.HasMetadata = false;
-            }
-            finally
-            {
-                XmlParsingResourcePool.Release();
-            }
-
-            return result;
-        }
-
-        public string Name
-        {
-            get { return "Media Browser Xml"; }
+            new BaseItemXmlParser<MusicArtist>(_logger).Fetch(item, path, cancellationToken);
         }
 
-        protected override FileInfo GetXmlFile(string path)
+        protected override FileInfo GetXmlFile(ItemInfo info)
         {
-            return new FileInfo(Path.Combine(path, "artist.xml"));
+            return new FileInfo(Path.Combine(info.Path, "artist.xml"));
         }
     }
 }

+ 53 - 0
MediaBrowser.Providers/Music/AudioMetadataService.cs

@@ -0,0 +1,53 @@
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Providers.Manager;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Providers.Music
+{
+    public class AudioMetadataService : MetadataService<Audio, ItemId>
+    {
+        private readonly ILibraryManager _libraryManager;
+
+        public AudioMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager)
+            : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem)
+        {
+            _libraryManager = libraryManager;
+        }
+
+        /// <summary>
+        /// Merges the specified source.
+        /// </summary>
+        /// <param name="source">The source.</param>
+        /// <param name="target">The target.</param>
+        /// <param name="lockedFields">The locked fields.</param>
+        /// <param name="replaceData">if set to <c>true</c> [replace data].</param>
+        /// <param name="mergeMetadataSettings">if set to <c>true</c> [merge metadata settings].</param>
+        protected override void MergeData(Audio source, Audio target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+        {
+            ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
+
+            if (replaceData || target.Artists.Count == 0)
+            {
+                target.Artists = source.Artists;
+            }
+
+            if (replaceData || string.IsNullOrEmpty(target.Album))
+            {
+                target.Album = source.Album;
+            }
+        }
+
+        protected override Task SaveItem(Audio item, ItemUpdateType reason, CancellationToken cancellationToken)
+        {
+            return _libraryManager.UpdateItem(item, reason, cancellationToken);
+        }
+    }
+}

+ 5 - 34
MediaBrowser.Providers/Music/MusicVideoXmlProvider.cs

@@ -5,11 +5,10 @@ using MediaBrowser.Model.Logging;
 using MediaBrowser.Providers.Movies;
 using System.IO;
 using System.Threading;
-using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.Music
 {
-    class MusicVideoXmlProvider : BaseXmlProvider, ILocalMetadataProvider<MusicVideo>
+    class MusicVideoXmlProvider : BaseXmlProvider<MusicVideo>
     {
         private readonly ILogger _logger;
 
@@ -19,42 +18,14 @@ namespace MediaBrowser.Providers.Music
             _logger = logger;
         }
 
-        public async Task<MetadataResult<MusicVideo>> GetMetadata(string path, CancellationToken cancellationToken)
+        protected override void Fetch(MusicVideo item, string path, CancellationToken cancellationToken)
         {
-            path = GetXmlFile(path).FullName;
-
-            var result = new MetadataResult<MusicVideo>();
-
-            await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
-
-            try
-            {
-                var item = new MusicVideo();
-
-                new MusicVideoXmlParser(_logger).Fetch(item, path, cancellationToken);
-                result.HasMetadata = true;
-                result.Item = item;
-            }
-            catch (FileNotFoundException)
-            {
-                result.HasMetadata = false;
-            }
-            finally
-            {
-                XmlParsingResourcePool.Release();
-            }
-
-            return result;
-        }
-
-        public string Name
-        {
-            get { return "Media Browser Xml"; }
+            new MusicVideoXmlParser(_logger).Fetch(item, path, cancellationToken);
         }
 
-        protected override FileInfo GetXmlFile(string path)
+        protected override FileInfo GetXmlFile(ItemInfo info)
         {
-            return MovieXmlProvider.GetXmlFileInfo(path, FileSystem);
+            return MovieXmlProvider.GetXmlFileInfo(info, FileSystem);
         }
     }
 }

+ 5 - 34
MediaBrowser.Providers/People/PersonXmlProvider.cs

@@ -4,11 +4,10 @@ using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Logging;
 using System.IO;
 using System.Threading;
-using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.People
 {
-    public class PersonXmlProvider : BaseXmlProvider, ILocalMetadataProvider<Person>
+    public class PersonXmlProvider : BaseXmlProvider<Person>
     {
         private readonly ILogger _logger;
 
@@ -18,42 +17,14 @@ namespace MediaBrowser.Providers.People
             _logger = logger;
         }
 
-        public async Task<MetadataResult<Person>> GetMetadata(string path, CancellationToken cancellationToken)
+        protected override void Fetch(Person item, string path, CancellationToken cancellationToken)
         {
-            path = GetXmlFile(path).FullName;
-
-            var result = new MetadataResult<Person>();
-
-            await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
-
-            try
-            {
-                var person = new Person();
-
-                new BaseItemXmlParser<Person>(_logger).Fetch(person, path, cancellationToken);
-                result.HasMetadata = true;
-                result.Item = person;
-            }
-            catch (FileNotFoundException)
-            {
-                result.HasMetadata = false;
-            }
-            finally
-            {
-                XmlParsingResourcePool.Release();
-            }
-
-            return result;
-        }
-
-        public string Name
-        {
-            get { return "Media Browser Xml"; }
+            new BaseItemXmlParser<Person>(_logger).Fetch(item, path, cancellationToken);
         }
 
-        protected override FileInfo GetXmlFile(string path)
+        protected override FileInfo GetXmlFile(ItemInfo info)
         {
-            return new FileInfo(Path.Combine(path, "person.xml"));
+            return new FileInfo(Path.Combine(info.Path, "person.xml"));
         }
     }
 }

+ 84 - 1
MediaBrowser.Providers/ProviderUtils.cs

@@ -93,7 +93,10 @@ namespace MediaBrowser.Providers
             {
                 if (replaceData || !target.RunTimeTicks.HasValue)
                 {
-                    target.RunTimeTicks = source.RunTimeTicks;
+                    if (!(target is Audio) && !(target is Video))
+                    {
+                        target.RunTimeTicks = source.RunTimeTicks;
+                    }
                 }
             }
 
@@ -159,6 +162,11 @@ namespace MediaBrowser.Providers
 
             MergeAlbumArtist(source, target, lockedFields, replaceData);
             MergeBudget(source, target, lockedFields, replaceData);
+            MergeMetascore(source, target, lockedFields, replaceData);
+            MergeCriticRating(source, target, lockedFields, replaceData);
+            MergeAwards(source, target, lockedFields, replaceData);
+            MergeTaglines(source, target, lockedFields, replaceData);
+            MergeTrailers(source, target, lockedFields, replaceData);
 
             if (mergeMetadataSettings)
             {
@@ -218,5 +226,80 @@ namespace MediaBrowser.Providers
                 }
             }
         }
+
+        private static void MergeMetascore(BaseItem source, BaseItem target, List<MetadataFields> lockedFields, bool replaceData)
+        {
+            var sourceCast = source as IHasMetascore;
+            var targetCast = target as IHasMetascore;
+
+            if (sourceCast != null && targetCast != null)
+            {
+                if (replaceData || !targetCast.Metascore.HasValue)
+                {
+                    targetCast.Metascore = sourceCast.Metascore;
+                }
+            }
+        }
+
+        private static void MergeAwards(BaseItem source, BaseItem target, List<MetadataFields> lockedFields, bool replaceData)
+        {
+            var sourceCast = source as IHasAwards;
+            var targetCast = target as IHasAwards;
+
+            if (sourceCast != null && targetCast != null)
+            {
+                if (replaceData || string.IsNullOrEmpty(targetCast.AwardSummary))
+                {
+                    targetCast.AwardSummary = sourceCast.AwardSummary;
+                }
+            }
+        }
+
+        private static void MergeCriticRating(BaseItem source, BaseItem target, List<MetadataFields> lockedFields, bool replaceData)
+        {
+            var sourceCast = source as IHasCriticRating;
+            var targetCast = target as IHasCriticRating;
+
+            if (sourceCast != null && targetCast != null)
+            {
+                if (replaceData || !targetCast.CriticRating.HasValue)
+                {
+                    targetCast.CriticRating = sourceCast.CriticRating;
+                }
+
+                if (replaceData || string.IsNullOrEmpty(targetCast.CriticRatingSummary))
+                {
+                    targetCast.CriticRatingSummary = sourceCast.CriticRatingSummary;
+                }
+            }
+        }
+
+        private static void MergeTaglines(BaseItem source, BaseItem target, List<MetadataFields> lockedFields, bool replaceData)
+        {
+            var sourceCast = source as IHasTaglines;
+            var targetCast = target as IHasTaglines;
+
+            if (sourceCast != null && targetCast != null)
+            {
+                if (replaceData || targetCast.Taglines.Count == 0)
+                {
+                    targetCast.Taglines = sourceCast.Taglines;
+                }
+            }
+        }
+
+        private static void MergeTrailers(BaseItem source, BaseItem target, List<MetadataFields> lockedFields, bool replaceData)
+        {
+            var sourceCast = source as IHasTrailers;
+            var targetCast = target as IHasTrailers;
+
+            if (sourceCast != null && targetCast != null)
+            {
+                if (replaceData || targetCast.RemoteTrailers.Count == 0)
+                {
+                    targetCast.RemoteTrailers = sourceCast.RemoteTrailers;
+                }
+            }
+        }
     }
 }

+ 1 - 7
MediaBrowser.Providers/RefreshIntrosTask.cs

@@ -90,20 +90,14 @@ namespace MediaBrowser.Providers
             }
 
             var dbItem = _libraryManager.GetItemById(item.Id);
-            var isNewItem = false;
 
             if (dbItem != null)
             {
-                dbItem.ResetResolveArgs(item.ResolveArgs);
                 item = dbItem;
             }
-            else
-            {
-                isNewItem = true;
-            }
 
             // Force the save if it's a new item
-            await item.RefreshMetadata(cancellationToken, isNewItem).ConfigureAwait(false);
+            await item.RefreshMetadata(cancellationToken).ConfigureAwait(false);
         }
     }
 }

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

@@ -119,7 +119,7 @@ namespace MediaBrowser.Providers.Savers
                 return Path.ChangeExtension(item.Path, ".xml");
             }
 
-            return Path.Combine(item.MetaLocation, "game.xml");
+            return Path.Combine(item.ContainingFolderPath, "game.xml");
         }
     }
 }

+ 2 - 9
MediaBrowser.Providers/Savers/MovieXmlSaver.cs

@@ -50,16 +50,9 @@ namespace MediaBrowser.Providers.Savers
             // If new metadata has been downloaded and save local is on
             if (item.IsSaveLocalMetadataEnabled() && (wasMetadataEdited || wasMetadataDownloaded))
             {
-                var trailer = item as Trailer;
-
-                // Don't support local trailers
-                if (trailer != null)
-                {
-                    return !trailer.IsLocalTrailer;
-                }
                 var video = item as Video;
                 // Check parent for null to avoid running this against things like video backdrops
-                return video != null && !(item is Episode) && video.Parent != null;
+                return video != null && !(item is Episode) && !video.IsOwnedItem;
             }
 
             return false;
@@ -145,7 +138,7 @@ namespace MediaBrowser.Providers.Savers
                 return Path.ChangeExtension(item.Path, ".xml");
             }
 
-            return Path.Combine(item.MetaLocation, "movie.xml");
+            return Path.Combine(item.ContainingFolderPath, "movie.xml");
         }
     }
 }

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

@@ -1,4 +1,5 @@
-using MediaBrowser.Common.IO;
+using System.IO;
+using MediaBrowser.Common.IO;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
@@ -102,8 +103,10 @@ namespace MediaBrowser.Providers.TV
                 var currentIndexNumberEnd = item.IndexNumberEnd;
                 var currentParentIndexNumber = item.ParentIndexNumber;
 
-                item.IndexNumber = item.IndexNumber ?? TVUtils.GetEpisodeNumberFromFile(item.Path, item.Parent is Season);
-                item.IndexNumberEnd = item.IndexNumberEnd ?? TVUtils.GetEndingEpisodeNumberFromFile(item.Path);
+                var filename = Path.GetFileName(item.Path);
+
+                item.IndexNumber = item.IndexNumber ?? TVUtils.GetEpisodeNumberFromFile(filename, item.Parent is Season);
+                item.IndexNumberEnd = item.IndexNumberEnd ?? TVUtils.GetEndingEpisodeNumberFromFile(filename);
 
                 if (!item.ParentIndexNumber.HasValue)
                 {

+ 6 - 34
MediaBrowser.Providers/TV/EpisodeXmlProvider.cs

@@ -4,11 +4,10 @@ using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Logging;
 using System.IO;
 using System.Threading;
-using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.TV
 {
-    public class EpisodeXmlProvider : BaseXmlProvider, ILocalMetadataProvider<Episode>
+    public class EpisodeXmlProvider : BaseXmlProvider<Episode>
     {
         private readonly ILogger _logger;
 
@@ -18,43 +17,16 @@ namespace MediaBrowser.Providers.TV
             _logger = logger;
         }
 
-        public async Task<MetadataResult<Episode>> GetMetadata(string path, CancellationToken cancellationToken)
+        protected override void Fetch(Episode item, string path, CancellationToken cancellationToken)
         {
-            path = GetXmlFile(path).FullName;
-
-            var result = new MetadataResult<Episode>();
-
-            await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
-
-            try
-            {
-                result.Item = new Episode();
-
-                new EpisodeXmlParser(_logger).Fetch(result.Item, path, cancellationToken);
-                result.HasMetadata = true;
-            }
-            catch (FileNotFoundException)
-            {
-                result.HasMetadata = false;
-            }
-            finally
-            {
-                XmlParsingResourcePool.Release();
-            }
-
-            return result;
-        }
-
-        public string Name
-        {
-            get { return "Media Browser Xml"; }
+            new EpisodeXmlParser(_logger).Fetch(item, path, cancellationToken);
         }
 
-        protected override FileInfo GetXmlFile(string path)
+        protected override FileInfo GetXmlFile(ItemInfo info)
         {
-            var metadataPath = Path.GetDirectoryName(path);
+            var metadataPath = Path.GetDirectoryName(info.Path);
             metadataPath = Path.Combine(metadataPath, "metadata");
-            var metadataFile = Path.Combine(metadataPath, Path.ChangeExtension(Path.GetFileName(path), ".xml"));
+            var metadataFile = Path.Combine(metadataPath, Path.ChangeExtension(Path.GetFileName(info.Path), ".xml"));
 
             return new FileInfo(metadataFile);
         }

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

@@ -4,14 +4,13 @@ using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Logging;
 using System.IO;
 using System.Threading;
-using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.TV
 {
     /// <summary>
     /// Class SeriesProviderFromXml
     /// </summary>
-    public class SeasonXmlProvider : BaseXmlProvider, ILocalMetadataProvider<Season>
+    public class SeasonXmlProvider : BaseXmlProvider<Season>
     {
         private readonly ILogger _logger;
 
@@ -21,42 +20,14 @@ namespace MediaBrowser.Providers.TV
             _logger = logger;
         }
 
-        public async Task<MetadataResult<Season>> GetMetadata(string path, CancellationToken cancellationToken)
+        protected override void Fetch(Season item, string path, CancellationToken cancellationToken)
         {
-            path = GetXmlFile(path).FullName;
-
-            var result = new MetadataResult<Season>();
-
-            await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
-
-            try
-            {
-                var person = new Season();
-
-                new BaseItemXmlParser<Season>(_logger).Fetch(person, path, cancellationToken);
-                result.HasMetadata = true;
-                result.Item = person;
-            }
-            catch (FileNotFoundException)
-            {
-                result.HasMetadata = false;
-            }
-            finally
-            {
-                XmlParsingResourcePool.Release();
-            }
-
-            return result;
-        }
-
-        public string Name
-        {
-            get { return "Media Browser Xml"; }
+            new BaseItemXmlParser<Season>(_logger).Fetch(item, path, cancellationToken);
         }
 
-        protected override FileInfo GetXmlFile(string path)
+        protected override FileInfo GetXmlFile(ItemInfo info)
         {
-            return new FileInfo(Path.Combine(path, "season.xml"));
+            return new FileInfo(Path.Combine(info.Path, "season.xml"));
         }
     }
 }

+ 11 - 4
MediaBrowser.Providers/TV/SeriesPostScanTask.cs

@@ -2,6 +2,7 @@
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using System;
@@ -53,7 +54,7 @@ namespace MediaBrowser.Providers.TV
             await new MissingEpisodeProvider(_logger, _config).Run(seriesGroups, cancellationToken).ConfigureAwait(false);
 
             var numComplete = 0;
-            
+
             foreach (var series in seriesList)
             {
                 cancellationToken.ThrowIfCancellationRequested();
@@ -171,7 +172,9 @@ namespace MediaBrowser.Providers.TV
             {
                 foreach (var series in group)
                 {
-                    await series.RefreshMetadata(cancellationToken, true)
+                    await series.RefreshMetadata(new MetadataRefreshOptions
+                    {
+                    }, cancellationToken)
                         .ConfigureAwait(false);
 
                     await series.ValidateChildren(new Progress<double>(), cancellationToken, true)
@@ -438,7 +441,9 @@ namespace MediaBrowser.Providers.TV
 
             await season.AddChild(episode, cancellationToken).ConfigureAwait(false);
 
-            await episode.RefreshMetadata(cancellationToken).ConfigureAwait(false);
+            await episode.RefreshMetadata(new MetadataRefreshOptions
+            {
+            }, cancellationToken).ConfigureAwait(false);
         }
 
         /// <summary>
@@ -464,7 +469,9 @@ namespace MediaBrowser.Providers.TV
             };
 
             await series.AddChild(season, cancellationToken).ConfigureAwait(false);
-            await season.RefreshMetadata(cancellationToken).ConfigureAwait(false);
+            await season.RefreshMetadata(new MetadataRefreshOptions
+            {
+            }, cancellationToken).ConfigureAwait(false);
 
             return season;
         }

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

@@ -4,14 +4,13 @@ using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Logging;
 using System.IO;
 using System.Threading;
-using System.Threading.Tasks;
 
 namespace MediaBrowser.Providers.TV
 {
     /// <summary>
     /// Class SeriesProviderFromXml
     /// </summary>
-    public class SeriesXmlProvider : BaseXmlProvider, ILocalMetadataProvider<Series>
+    public class SeriesXmlProvider : BaseXmlProvider<Series>
     {
         private readonly ILogger _logger;
 
@@ -21,42 +20,14 @@ namespace MediaBrowser.Providers.TV
             _logger = logger;
         }
 
-        public async Task<MetadataResult<Series>> GetMetadata(string path, CancellationToken cancellationToken)
+        protected override void Fetch(Series item, string path, CancellationToken cancellationToken)
         {
-            path = GetXmlFile(path).FullName;
-
-            var result = new MetadataResult<Series>();
-
-            await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
-
-            try
-            {
-                var person = new Series();
-
-                new SeriesXmlParser(_logger).Fetch(person, path, cancellationToken);
-                result.HasMetadata = true;
-                result.Item = person;
-            }
-            catch (FileNotFoundException)
-            {
-                result.HasMetadata = false;
-            }
-            finally
-            {
-                XmlParsingResourcePool.Release();
-            }
-
-            return result;
-        }
-
-        public string Name
-        {
-            get { return "Media Browser Xml"; }
+            new SeriesXmlParser(_logger).Fetch(item, path, cancellationToken);
         }
 
-        protected override FileInfo GetXmlFile(string path)
+        protected override FileInfo GetXmlFile(ItemInfo info)
         {
-            return new FileInfo(Path.Combine(path, "series.xml"));
+            return new FileInfo(Path.Combine(info.Path, "series.xml"));
         }
     }
 }

+ 8 - 1
MediaBrowser.Providers/TV/TvdbEpisodeProvider.cs

@@ -185,7 +185,14 @@ namespace MediaBrowser.Providers.TV
             var file = Path.Combine(seriesDataPath, string.Format("episode-{0}-{1}.xml", seasonNumber.Value, episodeNumber));
             var success = false;
             var usingAbsoluteData = false;
-            var episode = new Episode();
+
+            var episode = new Episode
+            {
+                 IndexNumber = id.IndexNumber,
+                 ParentIndexNumber = id.ParentIndexNumber,
+                 IndexNumberEnd = id.IndexNumberEnd
+            };
+
             try
             {
                 FetchMainEpisodeInfo(episode, file, cancellationToken);

+ 52 - 0
MediaBrowser.Providers/Videos/VideoMetadataService.cs

@@ -0,0 +1,52 @@
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Providers.Manager;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Providers.Videos
+{
+    public class VideoMetadataService : MetadataService<Video, ItemId>
+    {
+        private readonly ILibraryManager _libraryManager;
+
+        public VideoMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager)
+            : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem)
+        {
+            _libraryManager = libraryManager;
+        }
+
+        /// <summary>
+        /// Merges the specified source.
+        /// </summary>
+        /// <param name="source">The source.</param>
+        /// <param name="target">The target.</param>
+        /// <param name="lockedFields">The locked fields.</param>
+        /// <param name="replaceData">if set to <c>true</c> [replace data].</param>
+        /// <param name="mergeMetadataSettings">if set to <c>true</c> [merge metadata settings].</param>
+        protected override void MergeData(Video source, Video target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+        {
+            ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
+        }
+
+        protected override Task SaveItem(Video item, ItemUpdateType reason, CancellationToken cancellationToken)
+        {
+            return _libraryManager.UpdateItem(item, reason, cancellationToken);
+        }
+
+        public override int Order
+        {
+            get
+            {
+                // Make sure the type-specific services get picked first
+                return 10;
+            }
+        }
+    }
+}

+ 0 - 57
MediaBrowser.Providers/VirtualItemImageValidator.cs

@@ -1,57 +0,0 @@
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.TV;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Logging;
-using System;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace MediaBrowser.Providers
-{
-    public class VirtualItemImageValidator : BaseMetadataProvider
-    {
-        public VirtualItemImageValidator(ILogManager logManager, IServerConfigurationManager configurationManager) 
-            : base(logManager, configurationManager)
-        {
-        }
-
-        public override bool Supports(BaseItem item)
-        {
-            var locationType = item.LocationType;
-
-            // The regular provider will get virtual seasons
-            if (item.LocationType == LocationType.Virtual)
-            {
-                var season = item as Season;
-
-                if (season != null)
-                {
-                    var series = season.Series;
-
-                    if (series != null && series.LocationType == LocationType.FileSystem)
-                    {
-                        return false;
-                    }
-                }
-            }
-
-            return locationType == LocationType.Virtual ||
-                   locationType == LocationType.Remote;
-        }
-
-        public override Task<bool> FetchAsync(BaseItem item, bool force, BaseProviderInfo providerInfo, CancellationToken cancellationToken)
-        {
-            item.ValidateImages();
-
-            SetLastRefreshed(item, DateTime.UtcNow, providerInfo);
-            return TrueTaskResult;
-        }
-
-        public override MetadataProviderPriority Priority
-        {
-            get { return MetadataProviderPriority.First; }
-        }
-    }
-}

+ 43 - 0
MediaBrowser.Providers/Years/YearMetadataService.cs

@@ -0,0 +1,43 @@
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+using MediaBrowser.Providers.Manager;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Providers.Years
+{
+    public class YearMetadataService : MetadataService<Year, ItemId>
+    {
+        private readonly ILibraryManager _libraryManager;
+
+        public YearMetadataService(IServerConfigurationManager serverConfigurationManager, ILogger logger, IProviderManager providerManager, IProviderRepository providerRepo, IFileSystem fileSystem, ILibraryManager libraryManager)
+            : base(serverConfigurationManager, logger, providerManager, providerRepo, fileSystem)
+        {
+            _libraryManager = libraryManager;
+        }
+
+        /// <summary>
+        /// Merges the specified source.
+        /// </summary>
+        /// <param name="source">The source.</param>
+        /// <param name="target">The target.</param>
+        /// <param name="lockedFields">The locked fields.</param>
+        /// <param name="replaceData">if set to <c>true</c> [replace data].</param>
+        /// <param name="mergeMetadataSettings">if set to <c>true</c> [merge metadata settings].</param>
+        protected override void MergeData(Year source, Year target, List<MetadataFields> lockedFields, bool replaceData, bool mergeMetadataSettings)
+        {
+            ProviderUtils.MergeBaseItemData(source, target, lockedFields, replaceData, mergeMetadataSettings);
+        }
+
+        protected override Task SaveItem(Year item, ItemUpdateType reason, CancellationToken cancellationToken)
+        {
+            return _libraryManager.UpdateItem(item, reason, cancellationToken);
+        }
+    }
+}

+ 2 - 15
MediaBrowser.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs

@@ -273,21 +273,8 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
             {
                 if (item.LocationType == LocationType.FileSystem)
                 {
-                    return collections.Where(i =>
-                    {
-
-                        try
-                        {
-                            return i.LocationType == LocationType.FileSystem &&
-                                   i.PhysicalLocations.Contains(item.Path);
-                        }
-                        catch (Exception ex)
-                        {
-                            _logger.ErrorException("Error getting ResolveArgs for {0}", ex, i.Path);
-                            return false;
-                        }
-
-                    }).Cast<T>();
+                    return collections.Where(i => i.LocationType == LocationType.FileSystem &&
+                                                  i.PhysicalLocations.Contains(item.Path)).Cast<T>();
                 }
             }
 

+ 1 - 12
MediaBrowser.Server.Implementations/IO/LibraryMonitor.cs

@@ -162,18 +162,7 @@ namespace MediaBrowser.Server.Implementations.IO
                 .Children
                 .OfType<Folder>()
                 .Where(i => i.LocationType != LocationType.Remote && i.LocationType != LocationType.Virtual)
-                .SelectMany(f =>
-                    {
-                        try
-                        {
-                            return f.PhysicalLocations;
-                        }
-                        catch (IOException)
-                        {
-                            return new string[] { };
-                        }
-
-                    })
+                .SelectMany(f => f.PhysicalLocations)
                 .Distinct(StringComparer.OrdinalIgnoreCase)
                 .OrderBy(i => i)
                 .ToList();

+ 4 - 14
MediaBrowser.Server.Implementations/Library/LibraryManager.cs

@@ -833,9 +833,6 @@ namespace MediaBrowser.Server.Implementations.Library
                 (item as MusicArtist).IsAccessedByName = true;
             }
 
-            // Set this now so we don't cause additional file system access during provider executions
-            item.ResetResolveArgs(fileInfo);
-
             return new Tuple<bool, T>(isNew, item);
         }
 
@@ -1113,6 +1110,7 @@ namespace MediaBrowser.Server.Implementations.Library
             cancellationToken.ThrowIfCancellationRequested();
 
             await userRootFolder.ValidateChildren(new Progress<double>(), cancellationToken, recursive: false).ConfigureAwait(false);
+            var b = true;
         }
 
         /// <summary>
@@ -1244,7 +1242,6 @@ namespace MediaBrowser.Server.Implementations.Library
 
                         if (dbItem != null)
                         {
-                            dbItem.ResetResolveArgs(video.ResolveArgs);
                             video = dbItem;
                         }
                     }
@@ -1383,6 +1380,8 @@ namespace MediaBrowser.Server.Implementations.Library
             
             item.DateLastSaved = DateTime.UtcNow;
 
+            _logger.Debug("Saving {0} to database.", item.Path ?? item.Name);
+
             await ItemRepository.SaveItem(item, cancellationToken).ConfigureAwait(false);
 
             UpdateItemInLibraryCache(item);
@@ -1479,16 +1478,7 @@ namespace MediaBrowser.Server.Implementations.Library
                         return true;
                     }
 
-                    try
-                    {
-
-                        return i.PhysicalLocations.Contains(item.Path);
-                    }
-                    catch (IOException ex)
-                    {
-                        _logger.ErrorException("Error getting resolve args for {0}", ex, i.Path);
-                        return false;
-                    }
+                    return i.PhysicalLocations.Contains(item.Path);
                 })
                 .Select(i => i.CollectionType)
                 .Where(i => !string.IsNullOrEmpty(i))

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

@@ -23,8 +23,6 @@ namespace MediaBrowser.Server.Implementations.Library
         /// <param name="fileSystem">The file system.</param>
         public static void SetInitialItemValues(BaseItem item, ItemResolveArgs args, IFileSystem fileSystem)
         {
-            item.ResetResolveArgs(args);
-
             // If the resolver didn't specify this
             if (string.IsNullOrEmpty(item.Path))
             {

+ 1 - 1
MediaBrowser.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs

@@ -184,7 +184,7 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.Movies
         private void SetProviderIdFromPath(Video item)
         {
             //we need to only look at the name of this actual item (not parents)
-            var justName = item.IsInMixedFolder ? Path.GetFileName(item.Path) : Path.GetFileName(item.MetaLocation);
+            var justName = item.IsInMixedFolder ? Path.GetFileName(item.Path) : Path.GetFileName(item.ContainingFolderPath);
 
             var id = justName.GetAttributeValue("tmdbid");
 

+ 0 - 12
MediaBrowser.Server.Implementations/Library/Resolvers/TV/SeasonResolver.cs

@@ -47,17 +47,5 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
 
             return null;
         }
-
-        /// <summary>
-        /// Sets the initial item values.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <param name="args">The args.</param>
-        protected override void SetInitialItemValues(Season item, ItemResolveArgs args)
-        {
-            base.SetInitialItemValues(item, args);
-
-            Season.AddMetadataFiles(args);
-        }
     }
 }

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

@@ -92,8 +92,6 @@ namespace MediaBrowser.Server.Implementations.Library.Resolvers.TV
         {
             base.SetInitialItemValues(item, args);
 
-            Season.AddMetadataFiles(args);
-
             SetProviderIdFromPath(item, args.Path);
         }
 

+ 3 - 9
MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs

@@ -326,13 +326,9 @@ namespace MediaBrowser.Server.Implementations.LiveTv
                 item.Name = channelInfo.Name;
             }
 
-            // Set this now so we don't cause additional file system access during provider executions
-            item.ResetResolveArgs(fileInfo);
-
             await item.RefreshMetadata(new MetadataRefreshOptions
             {
-                ForceSave = isNew,
-                ResetResolveArgs = false
+                ForceSave = isNew
 
             }, cancellationToken);
 
@@ -391,8 +387,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
             await item.RefreshMetadata(new MetadataRefreshOptions
             {
-                ForceSave = isNew,
-                ResetResolveArgs = false
+                ForceSave = isNew
 
             }, cancellationToken);
 
@@ -448,8 +443,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv
 
             await item.RefreshMetadata(new MetadataRefreshOptions
             {
-                ForceSave = isNew,
-                ResetResolveArgs = false
+                ForceSave = isNew
 
             }, cancellationToken);
 

+ 2 - 2
MediaBrowser.ServerApplication/ApplicationHost.cs

@@ -501,7 +501,7 @@ namespace MediaBrowser.ServerApplication
                                     GetExports<ILibraryPostScanTask>(),
                                     GetExports<IPeoplePrescanTask>());
 
-            ProviderManager.AddParts(GetExports<BaseMetadataProvider>(), GetExports<IImageProvider>(), GetExports<IMetadataService>(), GetExports<IMetadataProvider>(),
+            ProviderManager.AddParts(GetExports<IImageProvider>(), GetExports<IMetadataService>(), GetExports<IMetadataProvider>(),
                                     GetExports<IMetadataSaver>());
 
             ImageProcessor.AddParts(GetExports<IImageEnhancer>());
@@ -627,7 +627,7 @@ namespace MediaBrowser.ServerApplication
             list.Add(typeof(IServerApplicationHost).Assembly);
 
             // Include composable parts in the Providers assembly 
-            list.Add(typeof(ImagesByNameProvider).Assembly);
+            list.Add(typeof(ProviderUtils).Assembly);
 
             // Common implementations
             list.Add(typeof(TaskManager).Assembly);

+ 3 - 0
MediaBrowser.sln

@@ -237,4 +237,7 @@ Global
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
 	EndGlobalSection
+	GlobalSection(Performance) = preSolution
+		HasPerformanceSessions = true
+	EndGlobalSection
 EndGlobal