Kaynağa Gözat

fixes #606 - Add manual image selection for Seasons

Luke Pulverenti 11 yıl önce
ebeveyn
işleme
9ba615e649

+ 5 - 4
MediaBrowser.Controller/Entities/Folder.cs

@@ -718,7 +718,7 @@ namespace MediaBrowser.Controller.Entities
             {
                 var newChildren = validChildren.Select(c => c.Item1).ToList();
 
-                //that's all the new and changed ones - now see if there are any that are missing
+                // That's all the new and changed ones - now see if there are any that are missing
                 var itemsRemoved = currentChildren.Values.Except(newChildren).ToList();
 
                 var actualRemovals = new List<BaseItem>();
@@ -936,13 +936,14 @@ namespace MediaBrowser.Controller.Entities
         /// <returns>IEnumerable{BaseItem}.</returns>
         protected virtual IEnumerable<BaseItem> GetNonCachedChildren()
         {
+            var resolveArgs = ResolveArgs;
 
-            if (ResolveArgs == null || ResolveArgs.FileSystemDictionary == null)
+            if (resolveArgs == null || resolveArgs.FileSystemDictionary == null)
             {
-                Logger.Error("Null for {0}", Path);
+                Logger.Error("ResolveArgs null for {0}", Path);
             }
 
-            return LibraryManager.ResolvePaths<BaseItem>(ResolveArgs.FileSystemChildren, this);
+            return LibraryManager.ResolvePaths<BaseItem>(resolveArgs.FileSystemChildren, this);
         }
 
         /// <summary>

+ 5 - 0
MediaBrowser.Model/Providers/RemoteImageInfo.cs

@@ -20,6 +20,11 @@ namespace MediaBrowser.Model.Providers
         /// <value>The URL.</value>
         public string Url { get; set; }
 
+        /// <summary>
+        /// Gets a url used for previewing a smaller version
+        /// </summary>
+        public string ThumbnailUrl { get; set; }
+        
         /// <summary>
         /// Gets or sets the height.
         /// </summary>

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

@@ -113,6 +113,7 @@
     <Compile Include="TV\ManualFanartSeasonProvider.cs" />
     <Compile Include="TV\ManualFanartSeriesProvider.cs" />
     <Compile Include="TV\ManualTvdbEpisodeImageProvider.cs" />
+    <Compile Include="TV\ManualTvdbSeasonImageProvider.cs" />
     <Compile Include="TV\RemoteEpisodeProvider.cs" />
     <Compile Include="TV\RemoteSeasonProvider.cs" />
     <Compile Include="TV\RemoteSeriesProvider.cs" />

+ 4 - 2
MediaBrowser.Providers/TV/ManualTvdbEpisodeImageProvider.cs

@@ -36,9 +36,11 @@ namespace MediaBrowser.Providers.TV
             return item is Episode;
         }
 
-        public Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken)
+        public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken)
         {
-            return GetAllImages(item, cancellationToken);
+            var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false);
+
+            return images.Where(i => i.Type == imageType);
         }
 
         public Task<IEnumerable<RemoteImageInfo>> GetAllImages(BaseItem item, CancellationToken cancellationToken)

+ 313 - 0
MediaBrowser.Providers/TV/ManualTvdbSeasonImageProvider.cs

@@ -0,0 +1,313 @@
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Providers;
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Xml;
+
+namespace MediaBrowser.Providers.TV
+{
+    public class ManualTvdbSeasonImageProvider : IImageProvider
+    {
+        private readonly IServerConfigurationManager _config;
+        private readonly CultureInfo _usCulture = new CultureInfo("en-US");
+
+        public ManualTvdbSeasonImageProvider(IServerConfigurationManager config)
+        {
+            _config = config;
+        }
+
+        public string Name
+        {
+            get { return ProviderName; }
+        }
+
+        public static string ProviderName
+        {
+            get { return "TvDb"; }
+        }
+
+        public bool Supports(BaseItem item)
+        {
+            return item is Season;
+        }
+
+        public async Task<IEnumerable<RemoteImageInfo>> GetImages(BaseItem item, ImageType imageType, CancellationToken cancellationToken)
+        {
+            var images = await GetAllImages(item, cancellationToken).ConfigureAwait(false);
+
+            return images.Where(i => i.Type == imageType);
+        }
+
+        public Task<IEnumerable<RemoteImageInfo>> GetAllImages(BaseItem item, CancellationToken cancellationToken)
+        {
+            var season = (Season)item;
+
+            var seriesId = season.Series != null ? season.Series.GetProviderId(MetadataProviders.Tvdb) : null;
+
+            if (!string.IsNullOrEmpty(seriesId) && season.IndexNumber.HasValue)
+            {
+                // Process images
+                var seriesDataPath = RemoteSeriesProvider.GetSeriesDataPath(_config.ApplicationPaths, seriesId);
+
+                var path = Path.Combine(seriesDataPath, "banners.xml");
+
+                try
+                {
+                    var result = GetImages(path, season.IndexNumber.Value, cancellationToken);
+
+                    return Task.FromResult(result);
+                }
+                catch (FileNotFoundException)
+                {
+                    // No tvdb data yet. Don't blow up
+                }
+            }
+
+            return Task.FromResult<IEnumerable<RemoteImageInfo>>(new RemoteImageInfo[] { });
+        }
+
+        private IEnumerable<RemoteImageInfo> GetImages(string xmlPath, int seasonNumber, CancellationToken cancellationToken)
+        {
+            var settings = new XmlReaderSettings
+            {
+                CheckCharacters = false,
+                IgnoreProcessingInstructions = true,
+                IgnoreComments = true,
+                ValidationType = ValidationType.None
+            };
+
+            var list = new List<RemoteImageInfo>();
+
+            using (var streamReader = new StreamReader(xmlPath, Encoding.UTF8))
+            {
+                // Use XmlReader for best performance
+                using (var reader = XmlReader.Create(streamReader, settings))
+                {
+                    reader.MoveToContent();
+
+                    // Loop through each element
+                    while (reader.Read())
+                    {
+                        cancellationToken.ThrowIfCancellationRequested();
+
+                        if (reader.NodeType == XmlNodeType.Element)
+                        {
+                            switch (reader.Name)
+                            {
+                                case "Banner":
+                                    {
+                                        using (var subtree = reader.ReadSubtree())
+                                        {
+                                            AddImage(subtree, list, seasonNumber);
+                                        }
+                                        break;
+                                    }
+                                default:
+                                    reader.Skip();
+                                    break;
+                            }
+                        }
+                    }
+                }
+            }
+
+            var language = _config.Configuration.PreferredMetadataLanguage;
+
+            var isLanguageEn = string.Equals(language, "en", StringComparison.OrdinalIgnoreCase);
+
+            return list.OrderByDescending(i =>
+                {
+                    if (string.Equals(language, i.Language, StringComparison.OrdinalIgnoreCase))
+                    {
+                        return 3;
+                    }
+                    if (!isLanguageEn)
+                    {
+                        if (string.Equals("en", i.Language, StringComparison.OrdinalIgnoreCase))
+                        {
+                            return 2;
+                        }
+                    }
+                    if (string.IsNullOrEmpty(i.Language))
+                    {
+                        return isLanguageEn ? 3 : 2;
+                    }
+                    return 0;
+                })
+                .ThenByDescending(i => i.CommunityRating ?? 0)
+                .ToList();
+        }
+
+        private void AddImage(XmlReader reader, List<RemoteImageInfo> images, int seasonNumber)
+        {
+            reader.MoveToContent();
+
+            string bannerType = null;
+            string bannerType2 = null;
+            string url = null;
+            int? bannerSeason = null;
+            int? width = null;
+            int? height = null;
+            string language = null;
+            double? rating = null;
+            int? voteCount = null;
+            string thumbnailUrl = null;
+
+            while (reader.Read())
+            {
+                if (reader.NodeType == XmlNodeType.Element)
+                {
+                    switch (reader.Name)
+                    {
+                        case "Rating":
+                            {
+                                var val = reader.ReadElementContentAsString() ?? string.Empty;
+
+                                double rval;
+
+                                if (double.TryParse(val, NumberStyles.Any, _usCulture, out rval))
+                                {
+                                    rating = rval;
+                                }
+
+                                break;
+                            }
+
+                        case "RatingCount":
+                            {
+                                var val = reader.ReadElementContentAsString() ?? string.Empty;
+
+                                int rval;
+
+                                if (int.TryParse(val, NumberStyles.Integer, _usCulture, out rval))
+                                {
+                                    voteCount = rval;
+                                }
+
+                                break;
+                            }
+
+                        case "Language":
+                            {
+                                language = reader.ReadElementContentAsString() ?? string.Empty;
+                                break;
+                            }
+
+                        case "ThumbnailPath":
+                            {
+                                thumbnailUrl = reader.ReadElementContentAsString() ?? string.Empty;
+                                break;
+                            }
+
+                        case "BannerType":
+                            {
+                                bannerType = reader.ReadElementContentAsString() ?? string.Empty;
+                                break;
+                            }
+
+                        case "BannerType2":
+                            {
+                                bannerType2 = reader.ReadElementContentAsString() ?? string.Empty;
+
+                                // Sometimes the resolution is stuffed in here
+                                var resolutionParts = bannerType2.Split('x');
+
+                                if (resolutionParts.Length == 2)
+                                {
+                                    int rval;
+
+                                    if (int.TryParse(resolutionParts[0], NumberStyles.Integer, _usCulture, out rval))
+                                    {
+                                        width = rval;
+                                    }
+
+                                    if (int.TryParse(resolutionParts[1], NumberStyles.Integer, _usCulture, out rval))
+                                    {
+                                        height = rval;
+                                    }
+
+                                }
+
+                                break;
+                            }
+
+                        case "BannerPath":
+                            {
+                                url = reader.ReadElementContentAsString() ?? string.Empty;
+                                break;
+                            }
+
+                        case "Season":
+                            {
+                                var val = reader.ReadElementContentAsString();
+
+                                if (!string.IsNullOrWhiteSpace(val))
+                                {
+                                    bannerSeason = int.Parse(val);
+                                }
+                                break;
+                            }
+
+
+                        default:
+                            reader.Skip();
+                            break;
+                    }
+                }
+            }
+
+            if (!string.IsNullOrEmpty(url) && bannerSeason.HasValue && bannerSeason.Value == seasonNumber)
+            {
+                var imageInfo = new RemoteImageInfo
+                {
+                    RatingType = RatingType.Score,
+                    CommunityRating = rating,
+                    VoteCount = voteCount,
+                    Url = TVUtils.BannerUrl + url,
+                    ProviderName = Name,
+                    Language = language,
+                    Width = width,
+                    Height = height,
+                    ThumbnailUrl = thumbnailUrl
+                };
+
+                if (string.Equals(bannerType, "season", StringComparison.OrdinalIgnoreCase))
+                {
+                    if (string.Equals(bannerType2, "season", StringComparison.OrdinalIgnoreCase))
+                    {
+                        imageInfo.Type = ImageType.Primary;
+                        images.Add(imageInfo);
+                    }
+                    else if (string.Equals(bannerType2, "seasonwide", StringComparison.OrdinalIgnoreCase))
+                    {
+                        imageInfo.Type = ImageType.Banner;
+                        images.Add(imageInfo);
+                    }
+                }
+                else if (string.Equals(bannerType, "fanart", StringComparison.OrdinalIgnoreCase))
+                {
+                    imageInfo.Type = ImageType.Backdrop;
+                    images.Add(imageInfo);
+                }
+            }
+
+        }
+
+        public int Priority
+        {
+            get { return 0; }
+        }
+    }
+}

+ 20 - 192
MediaBrowser.Providers/TV/RemoteSeasonProvider.cs

@@ -6,12 +6,13 @@ using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
+using MediaBrowser.Model.Providers;
 using System;
+using System.Collections.Generic;
 using System.IO;
-using System.Text;
+using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
-using System.Xml;
 
 namespace MediaBrowser.Providers.TV
 {
@@ -144,56 +145,36 @@ namespace MediaBrowser.Providers.TV
         {
             cancellationToken.ThrowIfCancellationRequested();
 
-            var season = (Season)item;
+            var images = await _providerManager.GetAvailableRemoteImages(item, cancellationToken, ManualFanartSeriesImageProvider.ProviderName).ConfigureAwait(false);
 
-            var seriesId = season.Series != null ? season.Series.GetProviderId(MetadataProviders.Tvdb) : null;
+            const int backdropLimit = 1;
 
-            var seasonNumber = season.IndexNumber;
+            await DownloadImages(item, images.ToList(), backdropLimit, cancellationToken).ConfigureAwait(false);
 
-            if (!string.IsNullOrEmpty(seriesId) && seasonNumber.HasValue)
-            {
-                // Process images
-                var imagesXmlPath = Path.Combine(RemoteSeriesProvider.GetSeriesDataPath(ConfigurationManager.ApplicationPaths, seriesId), "banners.xml");
-
-                try
-                {
-                    var fanartData = FetchFanartXmlData(imagesXmlPath, seasonNumber.Value, cancellationToken);
-                    await DownloadImages(item, fanartData, ConfigurationManager.Configuration.MaxBackdrops, cancellationToken).ConfigureAwait(false);
-                }
-                catch (FileNotFoundException)
-                {
-                    // No biggie. Not all series have images
-                }
-
-                SetLastRefreshed(item, DateTime.UtcNow);
-                return true;
-            }
-
-            return false;
+            SetLastRefreshed(item, DateTime.UtcNow);
+            return true;
         }
 
-        private async Task DownloadImages(BaseItem item, FanartXmlData data, int backdropLimit, CancellationToken cancellationToken)
+        private async Task DownloadImages(BaseItem item, List<RemoteImageInfo> images, int backdropLimit, CancellationToken cancellationToken)
         {
             if (!item.HasImage(ImageType.Primary))
             {
-                var url = data.LanguagePoster ?? data.Poster;
-                if (!string.IsNullOrEmpty(url))
-                {
-                    url = TVUtils.BannerUrl + url;
+                var image = images.FirstOrDefault(i => i.Type == ImageType.Primary);
 
-                    await _providerManager.SaveImage(item, url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Primary, null, cancellationToken)
+                if (image != null)
+                {
+                    await _providerManager.SaveImage(item, image.Url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Primary, null, cancellationToken)
                       .ConfigureAwait(false);
                 }
             }
 
             if (ConfigurationManager.Configuration.DownloadSeasonImages.Banner && !item.HasImage(ImageType.Banner))
             {
-                var url = data.LanguageBanner ?? data.Banner;
-                if (!string.IsNullOrEmpty(url))
-                {
-                    url = TVUtils.BannerUrl + url;
+                var image = images.FirstOrDefault(i => i.Type == ImageType.Banner);
 
-                    await _providerManager.SaveImage(item, url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Banner, null, cancellationToken)
+                if (image != null)
+                {
+                    await _providerManager.SaveImage(item, image.Url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Banner, null, cancellationToken)
                       .ConfigureAwait(false);
                 }
             }
@@ -202,17 +183,16 @@ namespace MediaBrowser.Providers.TV
             {
                 var bdNo = item.BackdropImagePaths.Count;
 
-                foreach (var backdrop in data.Backdrops)
+                foreach (var backdrop in images.Where(i => i.Type == ImageType.Backdrop))
                 {
-                    var url = TVUtils.BannerUrl + backdrop.Url;
+                    var url = backdrop.Url;
 
                     if (item.ContainsImageWithSourceUrl(url))
                     {
                         continue;
                     }
 
-                    await _providerManager.SaveImage(item, url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Backdrop, bdNo, cancellationToken)
-                      .ConfigureAwait(false);
+                    await _providerManager.SaveImage(item, url, RemoteSeriesProvider.Current.TvDbResourcePool, ImageType.Backdrop, bdNo, cancellationToken).ConfigureAwait(false);
 
                     bdNo++;
 
@@ -220,157 +200,5 @@ namespace MediaBrowser.Providers.TV
                 }
             }
         }
-
-        private FanartXmlData FetchFanartXmlData(string bannersXmlPath, int seasonNumber, CancellationToken cancellationToken)
-        {
-            var settings = new XmlReaderSettings
-            {
-                CheckCharacters = false,
-                IgnoreProcessingInstructions = true,
-                IgnoreComments = true,
-                ValidationType = ValidationType.None
-            };
-
-            var data = new FanartXmlData();
-
-            using (var streamReader = new StreamReader(bannersXmlPath, Encoding.UTF8))
-            {
-                // Use XmlReader for best performance
-                using (var reader = XmlReader.Create(streamReader, settings))
-                {
-                    reader.MoveToContent();
-
-                    // Loop through each element
-                    while (reader.Read())
-                    {
-                        cancellationToken.ThrowIfCancellationRequested();
-
-                        if (reader.NodeType == XmlNodeType.Element)
-                        {
-                            switch (reader.Name)
-                            {
-                                case "Banner":
-                                    {
-                                        using (var subtree = reader.ReadSubtree())
-                                        {
-                                            FetchInfoFromBannerNode(data, subtree, seasonNumber);
-                                        }
-                                        break;
-                                    }
-                                default:
-                                    reader.Skip();
-                                    break;
-                            }
-                        }
-                    }
-                }
-            }
-
-            return data;
-        }
-
-        private void FetchInfoFromBannerNode(FanartXmlData data, XmlReader reader, int seasonNumber)
-        {
-            reader.MoveToContent();
-
-            string bannerType = null;
-            string bannerType2 = null;
-            string url = null;
-            int? bannerSeason = null;
-            string resolution = null;
-            string language = null;
-
-            while (reader.Read())
-            {
-                if (reader.NodeType == XmlNodeType.Element)
-                {
-                    switch (reader.Name)
-                    {
-                        case "Language":
-                            {
-                                language = reader.ReadElementContentAsString() ?? string.Empty;
-                                break;
-                            }
-
-                        case "BannerType":
-                            {
-                                bannerType = reader.ReadElementContentAsString() ?? string.Empty;
-                                break;
-                            }
-
-                        case "BannerType2":
-                            {
-                                bannerType2 = reader.ReadElementContentAsString() ?? string.Empty;
-                                break;
-                            }
-
-                        case "BannerPath":
-                            {
-                                url = reader.ReadElementContentAsString() ?? string.Empty;
-                                break;
-                            }
-
-                        case "Season":
-                            {
-                                var val = reader.ReadElementContentAsString();
-
-                                if (!string.IsNullOrWhiteSpace(val))
-                                {
-                                    bannerSeason = int.Parse(val);
-                                }
-                                break;
-                            }
-
-
-                        default:
-                            reader.Skip();
-                            break;
-                    }
-                }
-            }
-
-            if (!string.IsNullOrEmpty(url) && bannerSeason.HasValue && bannerSeason.Value == seasonNumber)
-            {
-                if (string.Equals(bannerType, "season", StringComparison.OrdinalIgnoreCase))
-                {
-                    if (string.Equals(bannerType2, "season", StringComparison.OrdinalIgnoreCase))
-                    {
-                        // Just grab the first
-                        if (string.IsNullOrWhiteSpace(data.Poster))
-                        {
-                            data.Poster = url;
-                        }
-                    }
-                    else if (string.Equals(bannerType2, "seasonwide", StringComparison.OrdinalIgnoreCase))
-                    {
-                        if (string.IsNullOrWhiteSpace(language) || string.Equals(language, "en", StringComparison.OrdinalIgnoreCase))
-                        {
-                            // Just grab the first
-                            if (string.IsNullOrWhiteSpace(data.Banner))
-                            {
-                                data.Banner = url;
-                            }
-                        }
-                        else if (string.Equals(language, ConfigurationManager.Configuration.PreferredMetadataLanguage, StringComparison.OrdinalIgnoreCase))
-                        {
-                            // Just grab the first
-                            if (string.IsNullOrWhiteSpace(data.LanguageBanner))
-                            {
-                                data.LanguageBanner = url;
-                            }
-                        }
-                    }
-                }
-                else if (string.Equals(bannerType, "fanart", StringComparison.OrdinalIgnoreCase))
-                {
-                    data.Backdrops.Add(new ImageInfo
-                    {
-                        Url = url,
-                        Resolution = resolution
-                    });
-                }
-            }
-        }
-
     }
 }

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

@@ -48,10 +48,7 @@ namespace MediaBrowser.Server.Implementations.Drawing
         /// The _logger
         /// </summary>
         private readonly ILogger _logger;
-        /// <summary>
-        /// The _app paths
-        /// </summary>
-        private readonly IServerApplicationPaths _appPaths;
+
         private readonly IFileSystem _fileSystem;
 
         private readonly string _imageSizeCachePath;
@@ -62,13 +59,12 @@ namespace MediaBrowser.Server.Implementations.Drawing
         public ImageProcessor(ILogger logger, IServerApplicationPaths appPaths, IFileSystem fileSystem)
         {
             _logger = logger;
-            _appPaths = appPaths;
             _fileSystem = fileSystem;
 
-            _imageSizeCachePath = Path.Combine(_appPaths.ImageCachePath, "image-sizes");
-            _croppedWhitespaceImageCachePath = Path.Combine(_appPaths.ImageCachePath, "cropped-images");
-            _enhancedImageCachePath = Path.Combine(_appPaths.ImageCachePath, "enhanced-images");
-            _resizedImageCachePath = Path.Combine(_appPaths.ImageCachePath, "resized-images");
+            _imageSizeCachePath = Path.Combine(appPaths.ImageCachePath, "image-sizes");
+            _croppedWhitespaceImageCachePath = Path.Combine(appPaths.ImageCachePath, "cropped-images");
+            _enhancedImageCachePath = Path.Combine(appPaths.ImageCachePath, "enhanced-images");
+            _resizedImageCachePath = Path.Combine(appPaths.ImageCachePath, "resized-images");
         }
 
         public void AddParts(IEnumerable<IImageEnhancer> enhancers)

+ 7 - 7
MediaBrowser.Server.Implementations/Library/LibraryManager.cs

@@ -60,25 +60,25 @@ namespace MediaBrowser.Server.Implementations.Library
         /// Gets the list of entity resolution ignore rules
         /// </summary>
         /// <value>The entity resolution ignore rules.</value>
-        private IEnumerable<IResolverIgnoreRule> EntityResolutionIgnoreRules { get; set; }
+        private IResolverIgnoreRule[] EntityResolutionIgnoreRules { get; set; }
 
         /// <summary>
         /// Gets the list of BasePluginFolders added by plugins
         /// </summary>
         /// <value>The plugin folders.</value>
-        private IEnumerable<IVirtualFolderCreator> PluginFolderCreators { get; set; }
+        private IVirtualFolderCreator[] PluginFolderCreators { get; set; }
 
         /// <summary>
         /// Gets the list of currently registered entity resolvers
         /// </summary>
         /// <value>The entity resolvers enumerable.</value>
-        private IEnumerable<IItemResolver> EntityResolvers { get; set; }
+        private IItemResolver[] EntityResolvers { get; set; }
 
         /// <summary>
         /// Gets or sets the comparers.
         /// </summary>
         /// <value>The comparers.</value>
-        private IEnumerable<IBaseItemComparer> Comparers { get; set; }
+        private IBaseItemComparer[] Comparers { get; set; }
 
         /// <summary>
         /// Gets the active item repository
@@ -218,11 +218,11 @@ namespace MediaBrowser.Server.Implementations.Library
             IEnumerable<IPeoplePrescanTask> peoplePrescanTasks,
             IEnumerable<IMetadataSaver> savers)
         {
-            EntityResolutionIgnoreRules = rules;
-            PluginFolderCreators = pluginFolders;
+            EntityResolutionIgnoreRules = rules.ToArray();
+            PluginFolderCreators = pluginFolders.ToArray();
             EntityResolvers = resolvers.OrderBy(i => i.Priority).ToArray();
             IntroProviders = introProviders;
-            Comparers = itemComparers;
+            Comparers = itemComparers.ToArray();
             PrescanTasks = prescanTasks;
             PostscanTasks = postscanTasks;
             PeoplePrescanTasks = peoplePrescanTasks;

+ 3 - 0
MediaBrowser.Tests/MediaBrowser.Tests.csproj

@@ -74,6 +74,9 @@
       <Name>MediaBrowser.Server.Implementations</Name>
     </ProjectReference>
   </ItemGroup>
+  <ItemGroup>
+    <None Include="app.config" />
+  </ItemGroup>
   <Choose>
     <When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'">
       <ItemGroup>

+ 11 - 0
MediaBrowser.Tests/app.config

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<configuration>
+  <runtime>
+    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
+      <dependentAssembly>
+        <assemblyIdentity name="System.Data.SQLite" publicKeyToken="db937bc2d44ff139" culture="neutral" />
+        <bindingRedirect oldVersion="0.0.0.0-1.0.89.0" newVersion="1.0.89.0" />
+      </dependentAssembly>
+    </assemblyBinding>
+  </runtime>
+</configuration>