Browse Source

converted movie providers to new system

Luke Pulverenti 11 năm trước cách đây
mục cha
commit
821a3d29a2
94 tập tin đã thay đổi với 1671 bổ sung5363 xóa
  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