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

a start to the lookup feature

Luke Pulverenti 11 жил өмнө
parent
commit
6c5cf81752

+ 252 - 0
MediaBrowser.Api/ItemLookupService.cs

@@ -0,0 +1,252 @@
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.IO;
+using MediaBrowser.Controller;
+using MediaBrowser.Controller.Dto;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Providers;
+using ServiceStack;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Api
+{
+    [Route("/Items/{Id}/ExternalIdInfos", "GET")]
+    [Api(Description = "Gets external id infos for an item")]
+    public class GetExternalIdInfos : IReturn<List<ExternalIdInfo>>
+    {
+        /// <summary>
+        /// Gets or sets the id.
+        /// </summary>
+        /// <value>The id.</value>
+        [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
+        public string Id { get; set; }
+    }
+
+    [Route("/Items/RemoteSearch/Movie", "POST")]
+    [Api(Description = "Gets external id infos for an item")]
+    public class GetMovieRemoteSearchResults : RemoteSearchQuery<MovieInfo>, IReturn<List<RemoteSearchResult>>
+    {
+    }
+
+    [Route("/Items/RemoteSearch/Trailer", "POST")]
+    [Api(Description = "Gets external id infos for an item")]
+    public class GetTrailerRemoteSearchResults : RemoteSearchQuery<TrailerInfo>, IReturn<List<RemoteSearchResult>>
+    {
+    }
+
+    [Route("/Items/RemoteSearch/AdultVideo", "POST")]
+    [Api(Description = "Gets external id infos for an item")]
+    public class GetAdultVideoRemoteSearchResults : RemoteSearchQuery<ItemLookupInfo>, IReturn<List<RemoteSearchResult>>
+    {
+    }
+
+    [Route("/Items/RemoteSearch/Series", "POST")]
+    [Api(Description = "Gets external id infos for an item")]
+    public class GetSeriesRemoteSearchResults : RemoteSearchQuery<SeriesInfo>, IReturn<List<RemoteSearchResult>>
+    {
+    }
+
+    [Route("/Items/RemoteSearch/Game", "POST")]
+    [Api(Description = "Gets external id infos for an item")]
+    public class GetGameRemoteSearchResults : RemoteSearchQuery<GameInfo>, IReturn<List<RemoteSearchResult>>
+    {
+    }
+
+    [Route("/Items/RemoteSearch/BoxSet", "POST")]
+    [Api(Description = "Gets external id infos for an item")]
+    public class GetBoxSetRemoteSearchResults : RemoteSearchQuery<BoxSetInfo>, IReturn<List<RemoteSearchResult>>
+    {
+    }
+
+    [Route("/Items/RemoteSearch/Person", "POST")]
+    [Api(Description = "Gets external id infos for an item")]
+    public class GetPersonRemoteSearchResults : RemoteSearchQuery<PersonLookupInfo>, IReturn<List<RemoteSearchResult>>
+    {
+    }
+
+    [Route("/Items/RemoteSearch/Image", "GET")]
+    [Api(Description = "Gets a remote image")]
+    public class GetRemoteSearchImage
+    {
+        [ApiMember(Name = "ImageUrl", Description = "The image url", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string ImageUrl { get; set; }
+
+        [ApiMember(Name = "ProviderName", IsRequired = true, DataType = "string", ParameterType = "query", Verb = "GET")]
+        public string ProviderName { get; set; }
+    }
+    
+    public class ItemLookupService : BaseApiService
+    {
+        private readonly IDtoService _dtoService;
+        private readonly IProviderManager _providerManager;
+        private readonly IServerApplicationPaths _appPaths;
+        private readonly IFileSystem _fileSystem;
+
+        public ItemLookupService(IDtoService dtoService, IProviderManager providerManager, IServerApplicationPaths appPaths, IFileSystem fileSystem)
+        {
+            _dtoService = dtoService;
+            _providerManager = providerManager;
+            _appPaths = appPaths;
+            _fileSystem = fileSystem;
+        }
+
+        public object Get(GetExternalIdInfos request)
+        {
+            var item = _dtoService.GetItemByDtoId(request.Id);
+
+            var infos = _providerManager.GetExternalIdInfos(item).ToList();
+
+            return ToOptimizedResult(infos);
+        }
+
+        public object Post(GetMovieRemoteSearchResults request)
+        {
+            var result = _providerManager.GetRemoteSearchResults<Movie, MovieInfo>(request, CancellationToken.None).Result;
+
+            return ToOptimizedResult(result);
+        }
+
+        public object Post(GetAdultVideoRemoteSearchResults request)
+        {
+            var result = _providerManager.GetRemoteSearchResults<AdultVideo, ItemLookupInfo>(request, CancellationToken.None).Result;
+
+            return ToOptimizedResult(result);
+        }
+
+        public object Post(GetSeriesRemoteSearchResults request)
+        {
+            var result = _providerManager.GetRemoteSearchResults<Series, SeriesInfo>(request, CancellationToken.None).Result;
+
+            return ToOptimizedResult(result);
+        }
+
+        public object Post(GetGameRemoteSearchResults request)
+        {
+            var result = _providerManager.GetRemoteSearchResults<Game, GameInfo>(request, CancellationToken.None).Result;
+
+            return ToOptimizedResult(result);
+        }
+
+        public object Post(GetBoxSetRemoteSearchResults request)
+        {
+            var result = _providerManager.GetRemoteSearchResults<BoxSet, BoxSetInfo>(request, CancellationToken.None).Result;
+
+            return ToOptimizedResult(result);
+        }
+
+        public object Post(GetPersonRemoteSearchResults request)
+        {
+            var result = _providerManager.GetRemoteSearchResults<Person, PersonLookupInfo>(request, CancellationToken.None).Result;
+
+            return ToOptimizedResult(result);
+        }
+
+        public object Post(GetTrailerRemoteSearchResults request)
+        {
+            var result = _providerManager.GetRemoteSearchResults<Trailer, TrailerInfo>(request, CancellationToken.None).Result;
+
+            return ToOptimizedResult(result);
+        }
+
+        public object Get(GetRemoteSearchImage request)
+        {
+            var result = GetRemoteImage(request).Result;
+
+            return result;
+        }
+
+        /// <summary>
+        /// Gets the remote image.
+        /// </summary>
+        /// <param name="request">The request.</param>
+        /// <returns>Task{System.Object}.</returns>
+        private async Task<object> GetRemoteImage(GetRemoteSearchImage request)
+        {
+            var urlHash = request.ImageUrl.GetMD5();
+            var pointerCachePath = GetFullCachePath(urlHash.ToString());
+
+            string contentPath;
+
+            try
+            {
+                using (var reader = new StreamReader(pointerCachePath))
+                {
+                    contentPath = await reader.ReadToEndAsync().ConfigureAwait(false);
+                }
+
+                if (File.Exists(contentPath))
+                {
+                    return ToStaticFileResult(contentPath);
+                }
+            }
+            catch (DirectoryNotFoundException)
+            {
+                // Means the file isn't cached yet
+            }
+            catch (FileNotFoundException)
+            {
+                // Means the file isn't cached yet
+            }
+
+            await DownloadImage(request.ProviderName, request.ImageUrl, urlHash, pointerCachePath).ConfigureAwait(false);
+
+            // Read the pointer file again
+            using (var reader = new StreamReader(pointerCachePath))
+            {
+                contentPath = await reader.ReadToEndAsync().ConfigureAwait(false);
+            }
+
+            return ToStaticFileResult(contentPath);
+        }
+
+        /// <summary>
+        /// Downloads the image.
+        /// </summary>
+        /// <param name="providerName">Name of the provider.</param>
+        /// <param name="url">The URL.</param>
+        /// <param name="urlHash">The URL hash.</param>
+        /// <param name="pointerCachePath">The pointer cache path.</param>
+        /// <returns>Task.</returns>
+        private async Task DownloadImage(string providerName, string url, Guid urlHash, string pointerCachePath)
+        {
+            var result = await _providerManager.GetSearchImage(providerName, url, CancellationToken.None).ConfigureAwait(false);
+
+            var ext = result.ContentType.Split('/').Last();
+
+            var fullCachePath = GetFullCachePath(urlHash + "." + ext);
+
+            Directory.CreateDirectory(Path.GetDirectoryName(fullCachePath));
+            using (var stream = result.Content)
+            {
+                using (var filestream = _fileSystem.GetFileStream(fullCachePath, FileMode.Create, FileAccess.Write, FileShare.Read, true))
+                {
+                    await stream.CopyToAsync(filestream).ConfigureAwait(false);
+                }
+            }
+
+            Directory.CreateDirectory(Path.GetDirectoryName(pointerCachePath));
+            using (var writer = new StreamWriter(pointerCachePath))
+            {
+                await writer.WriteAsync(fullCachePath).ConfigureAwait(false);
+            }
+        }
+
+        /// <summary>
+        /// Gets the full cache path.
+        /// </summary>
+        /// <param name="filename">The filename.</param>
+        /// <returns>System.String.</returns>
+        private string GetFullCachePath(string filename)
+        {
+            return Path.Combine(_appPaths.CachePath, "remote-images", filename.Substring(0, 1), filename);
+        }
+
+    }
+}

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

@@ -6,10 +6,8 @@ using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Persistence;
-using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Providers;
 using MediaBrowser.Model.Querying;
 using ServiceStack;
 using System;
@@ -50,18 +48,6 @@ namespace MediaBrowser.Api.Library
         public int Index { get; set; }
     }
 
-    [Route("/Items/{Id}/ExternalIdInfos", "GET")]
-    [Api(Description = "Gets external id infos for an item")]
-    public class GetExternalIdInfos : IReturn<List<ExternalIdInfo>>
-    {
-        /// <summary>
-        /// Gets or sets the id.
-        /// </summary>
-        /// <value>The id.</value>
-        [ApiMember(Name = "Id", Description = "Item Id", IsRequired = true, DataType = "string", ParameterType = "path", Verb = "GET")]
-        public string Id { get; set; }
-    }
-    
     /// <summary>
     /// Class GetCriticReviews
     /// </summary>
@@ -256,29 +242,18 @@ namespace MediaBrowser.Api.Library
         private readonly IUserDataManager _userDataManager;
 
         private readonly IDtoService _dtoService;
-        private readonly IProviderManager _providerManager;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="LibraryService" /> class.
         /// </summary>
         public LibraryService(IItemRepository itemRepo, ILibraryManager libraryManager, IUserManager userManager,
-                              IDtoService dtoService, IUserDataManager userDataManager, IProviderManager providerManager)
+                              IDtoService dtoService, IUserDataManager userDataManager)
         {
             _itemRepo = itemRepo;
             _libraryManager = libraryManager;
             _userManager = userManager;
             _dtoService = dtoService;
             _userDataManager = userDataManager;
-            _providerManager = providerManager;
-        }
-
-        public object Get(GetExternalIdInfos request)
-        {
-            var item = _dtoService.GetItemByDtoId(request.Id);
-
-            var infos = _providerManager.GetExternalIdInfos(item).ToList();
-
-            return ToOptimizedResult(infos);
         }
 
         public object Get(GetMediaFolders request)

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

@@ -82,6 +82,7 @@
     <Compile Include="Images\ImageService.cs" />
     <Compile Include="Images\ImageWriter.cs" />
     <Compile Include="InstantMixService.cs" />
+    <Compile Include="ItemLookupService.cs" />
     <Compile Include="ItemRefreshService.cs" />
     <Compile Include="ItemUpdateService.cs" />
     <Compile Include="Library\LibraryService.cs" />

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

@@ -1,4 +1,5 @@
-using MediaBrowser.Controller.Entities;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Model.Configuration;
 using MediaBrowser.Model.Entities;
@@ -126,5 +127,14 @@ namespace MediaBrowser.Controller.Providers
             CancellationToken cancellationToken)
             where TItemType : BaseItem, new()
             where TLookupType : ItemLookupInfo;
+
+        /// <summary>
+        /// Gets the search image.
+        /// </summary>
+        /// <param name="providerName">Name of the provider.</param>
+        /// <param name="url">The URL.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task{HttpResponseInfo}.</returns>
+        Task<HttpResponseInfo> GetSearchImage(string providerName, string url, CancellationToken cancellationToken);
     }
 }

+ 7 - 4
MediaBrowser.Controller/Providers/IRemoteMetadataProvider.cs

@@ -18,11 +18,8 @@ namespace MediaBrowser.Controller.Providers
         Task<MetadataResult<TItemType>> GetMetadata(TLookupInfoType info, CancellationToken cancellationToken);
     }
 
-    public interface IRemoteSearchProvider<in TLookupInfoType> : IMetadataProvider
-        where TLookupInfoType : ItemLookupInfo
+    public interface IRemoteSearchProvider : IMetadataProvider
     {
-        Task<IEnumerable<RemoteSearchResult>> GetSearchResults(TLookupInfoType searchInfo, CancellationToken cancellationToken);
-
         /// <summary>
         /// Gets the image response.
         /// </summary>
@@ -31,6 +28,12 @@ namespace MediaBrowser.Controller.Providers
         /// <returns>Task{HttpResponseInfo}.</returns>
         Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken);
     }
+
+    public interface IRemoteSearchProvider<in TLookupInfoType> : IRemoteSearchProvider
+        where TLookupInfoType : ItemLookupInfo
+    {
+        Task<IEnumerable<RemoteSearchResult>> GetSearchResults(TLookupInfoType searchInfo, CancellationToken cancellationToken);
+    }
     
     public class RemoteSearchQuery<T>
         where T : ItemLookupInfo

+ 68 - 5
MediaBrowser.Providers/BoxSets/MovieDbBoxSetProvider.cs

@@ -32,20 +32,71 @@ namespace MediaBrowser.Providers.BoxSets
         private readonly IServerConfigurationManager _config;
         private readonly IFileSystem _fileSystem;
         private readonly ILocalizationManager _localization;
+        private readonly IHttpClient _httpClient;
 
-        public MovieDbBoxSetProvider(ILogger logger, IJsonSerializer json, IServerConfigurationManager config, IFileSystem fileSystem, ILocalizationManager localization)
+        public MovieDbBoxSetProvider(ILogger logger, IJsonSerializer json, IServerConfigurationManager config, IFileSystem fileSystem, ILocalizationManager localization, IHttpClient httpClient)
         {
             _logger = logger;
             _json = json;
             _config = config;
             _fileSystem = fileSystem;
             _localization = localization;
+            _httpClient = httpClient;
             Current = this;
         }
 
+        private readonly CultureInfo _usCulture = new CultureInfo("en-US");
+        
         public async Task<IEnumerable<RemoteSearchResult>> GetSearchResults(BoxSetInfo searchInfo, CancellationToken cancellationToken)
         {
-            return new List<RemoteSearchResult>();
+            var tmdbId = searchInfo.GetProviderId(MetadataProviders.Tmdb);
+
+            var tmdbSettings = await MovieDbProvider.Current.GetTmdbSettings(cancellationToken).ConfigureAwait(false);
+
+            var tmdbImageUrl = tmdbSettings.images.base_url + "original";
+
+            if (!string.IsNullOrEmpty(tmdbId))
+            {
+                await EnsureInfo(tmdbId, searchInfo.MetadataLanguage, cancellationToken).ConfigureAwait(false);
+
+                var dataFilePath = GetDataFilePath(_config.ApplicationPaths, tmdbId, searchInfo.MetadataLanguage);
+                var info = _json.DeserializeFromFile<RootObject>(dataFilePath);
+
+                var images = (info.images ?? new Images()).posters ?? new List<Poster>();
+               
+                var result = new RemoteSearchResult
+                {
+                    Name = info.name,
+
+                    SearchProviderName = Name,
+                    
+                    ImageUrl = images.Count == 0 ? null : (tmdbImageUrl + images[0].file_path)
+                };
+
+                result.SetProviderId(MetadataProviders.Tmdb, info.id.ToString(_usCulture));
+
+                return new[] { result };
+            }
+
+            var results = await new MovieDbSearch(_logger, _json).GetSearchResults(searchInfo, cancellationToken).ConfigureAwait(false);
+
+            return results.Select(i => GetRemoteSearchResult(i, tmdbImageUrl));
+        }
+
+        private RemoteSearchResult GetRemoteSearchResult(MovieDbSearch.TmdbMovieSearchResult tmdbResult, string baseImageUrl)
+        {
+            var result = new RemoteSearchResult
+            {
+                Name = tmdbResult.name,
+
+                SearchProviderName = Name,
+                
+                ImageUrl = string.IsNullOrEmpty(tmdbResult.poster_path) ? null : (baseImageUrl + tmdbResult.poster_path)
+            };
+
+            result.SetProviderId(MetadataProviders.Tmdb, tmdbResult.id.ToString(_usCulture));
+
+            return result;
         }
 
         public async Task<MetadataResult<BoxSet>> GetMetadata(BoxSetInfo id, CancellationToken cancellationToken)
@@ -55,7 +106,9 @@ namespace MediaBrowser.Providers.BoxSets
             // We don't already have an Id, need to fetch it
             if (string.IsNullOrEmpty(tmdbId))
             {
-                var searchResult = await new MovieDbSearch(_logger, _json).FindCollectionId(id, cancellationToken).ConfigureAwait(false);
+                var searchResults = await new MovieDbSearch(_logger, _json).GetSearchResults(id, cancellationToken).ConfigureAwait(false);
+
+                var searchResult = searchResults.FirstOrDefault();
 
                 if (searchResult != null)
                 {
@@ -219,10 +272,15 @@ namespace MediaBrowser.Providers.BoxSets
 
         private static string GetDataFilePath(IApplicationPaths appPaths, string tmdbId, string preferredLanguage)
         {
+            if (string.IsNullOrWhiteSpace(preferredLanguage))
+            {
+                throw new ArgumentNullException("preferredLanguage");
+            }
+
             var path = GetDataPath(appPaths, tmdbId);
 
             var filename = string.Format("all-{0}.json",
-                preferredLanguage ?? string.Empty);
+                preferredLanguage);
 
             return Path.Combine(path, filename);
         }
@@ -291,7 +349,12 @@ namespace MediaBrowser.Providers.BoxSets
 
         public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
         {
-            throw new NotImplementedException();
+            return _httpClient.GetResponse(new HttpRequestOptions
+            {
+                CancellationToken = cancellationToken,
+                Url = url,
+                ResourcePool = MovieDbProvider.Current.MovieDbResourcePool
+            });
         }
     }
 }

+ 22 - 1
MediaBrowser.Providers/Manager/ProviderManager.cs

@@ -657,6 +657,15 @@ namespace MediaBrowser.Providers.Manager
                 providers = providers.Where(i => string.Equals(i.Name, searchInfo.SearchProviderName, StringComparison.OrdinalIgnoreCase));
             }
 
+            if (string.IsNullOrWhiteSpace(searchInfo.SearchInfo.MetadataLanguage))
+            {
+                searchInfo.SearchInfo.MetadataLanguage = ConfigurationManager.Configuration.PreferredMetadataLanguage;
+            }
+            if (string.IsNullOrWhiteSpace(searchInfo.SearchInfo.MetadataCountryCode))
+            {
+                searchInfo.SearchInfo.MetadataCountryCode = ConfigurationManager.Configuration.MetadataCountryCode;
+            }
+
             foreach (var provider in providers)
             {
                 var results = await provider.GetSearchResults(searchInfo.SearchInfo, cancellationToken).ConfigureAwait(false);
@@ -665,7 +674,7 @@ namespace MediaBrowser.Providers.Manager
 
                 if (list.Count > 0)
                 {
-                    return list;
+                    return list.Take(10);
                 }
             }
 
@@ -673,6 +682,18 @@ namespace MediaBrowser.Providers.Manager
             return new List<RemoteSearchResult>();
         }
 
+        public Task<HttpResponseInfo> GetSearchImage(string providerName, string url, CancellationToken cancellationToken)
+        {
+            var provider = _metadataProviders.OfType<IRemoteSearchProvider>().FirstOrDefault(i => string.Equals(i.Name, providerName, StringComparison.OrdinalIgnoreCase));
+
+            if (provider == null)
+            {
+                throw new ArgumentException("Search provider not found.");
+            }
+
+            return provider.GetImageResponse(url, cancellationToken);
+        }
+
         public IEnumerable<IExternalId> GetExternalIds(IHasProviderIds item)
         {
             return _externalIds.Where(i =>

+ 3 - 1
MediaBrowser.Providers/Movies/GenericMovieDbInfo.cs

@@ -39,7 +39,9 @@ namespace MediaBrowser.Providers.Movies
             // 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))
             {
-                var searchResult = await new MovieDbSearch(_logger, _jsonSerializer).FindMovieId(itemId, cancellationToken).ConfigureAwait(false);
+                var searchResults = await new MovieDbSearch(_logger, _jsonSerializer).GetMovieSearchResults(itemId, cancellationToken).ConfigureAwait(false);
+
+                var searchResult = searchResults.FirstOrDefault();
 
                 if (searchResult != null)
                 {

+ 6 - 1
MediaBrowser.Providers/Movies/MovieDbProvider.cs

@@ -558,7 +558,12 @@ namespace MediaBrowser.Providers.Movies
 
         public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
         {
-            throw new NotImplementedException();
+            return _httpClient.GetResponse(new HttpRequestOptions
+            {
+                CancellationToken = cancellationToken,
+                Url = url,
+                ResourcePool = MovieDbResourcePool
+            });
         }
     }
 }

+ 61 - 35
MediaBrowser.Providers/Movies/MovieDbSearch.cs

@@ -29,22 +29,22 @@ namespace MediaBrowser.Providers.Movies
             _json = json;
         }
 
-        public Task<TmdbMovieSearchResult> FindSeriesId(ItemLookupInfo idInfo, CancellationToken cancellationToken)
+        public Task<IEnumerable<TmdbMovieSearchResult>> GetSearchResults(SeriesInfo idInfo, CancellationToken cancellationToken)
         {
-            return FindId(idInfo, "tv", cancellationToken);
+            return GetSearchResults(idInfo, "tv", cancellationToken);
         }
 
-        public Task<TmdbMovieSearchResult> FindMovieId(ItemLookupInfo idInfo, CancellationToken cancellationToken)
+        public Task<IEnumerable<TmdbMovieSearchResult>> GetMovieSearchResults(ItemLookupInfo idInfo, CancellationToken cancellationToken)
         {
-            return FindId(idInfo, "movie", cancellationToken);
+            return GetSearchResults(idInfo, "movie", cancellationToken);
         }
 
-        public Task<TmdbMovieSearchResult> FindCollectionId(ItemLookupInfo idInfo, CancellationToken cancellationToken)
+        public Task<IEnumerable<TmdbMovieSearchResult>> GetSearchResults(BoxSetInfo idInfo, CancellationToken cancellationToken)
         {
-            return FindId(idInfo, "collection", cancellationToken);
+            return GetSearchResults(idInfo, "collection", cancellationToken);
         }
 
-        private async Task<TmdbMovieSearchResult> FindId(ItemLookupInfo idInfo, string searchType, CancellationToken cancellationToken)
+        private async Task<IEnumerable<TmdbMovieSearchResult>> GetSearchResults(ItemLookupInfo idInfo, string searchType, CancellationToken cancellationToken)
         {
             var name = idInfo.Name;
             var year = idInfo.Year;
@@ -60,48 +60,49 @@ namespace MediaBrowser.Providers.Movies
             //nope - search for it
             //var searchType = item is BoxSet ? "collection" : "movie";
 
-            var id = await AttemptFindId(name, searchType, year, language, cancellationToken).ConfigureAwait(false);
+            var results = await GetSearchResults(name, searchType, year, language, cancellationToken).ConfigureAwait(false);
             
-            if (id == null)
+            if (results.Count == 0)
             {
                 //try in english if wasn't before
-                if (language != "en")
+                if (!string.Equals(language, "en", StringComparison.OrdinalIgnoreCase))
                 {
-                    id = await AttemptFindId(name, searchType, year, "en", cancellationToken).ConfigureAwait(false);
+                    results = await GetSearchResults(name, searchType, year, "en", cancellationToken).ConfigureAwait(false);
                 }
-                else
-                {
-                    // try with dot and _ turned to space
-                    var originalName = name;
+            }
 
-                    name = name.Replace(",", " ");
-                    name = name.Replace(".", " ");
-                    name = name.Replace("_", " ");
-                    name = name.Replace("-", " ");
-                    name = name.Replace("!", " ");
-                    name = name.Replace("?", " ");
+            if (results.Count == 0)
+            {
+                // try with dot and _ turned to space
+                var originalName = name;
 
-                    name = name.Trim();
+                name = name.Replace(",", " ");
+                name = name.Replace(".", " ");
+                name = name.Replace("_", " ");
+                name = name.Replace("-", " ");
+                name = name.Replace("!", " ");
+                name = name.Replace("?", " ");
 
-                    // Search again if the new name is different
-                    if (!string.Equals(name, originalName))
-                    {
-                        id = await AttemptFindId(name, searchType, year, language, cancellationToken).ConfigureAwait(false);
+                name = name.Trim();
 
-                        if (id == null && language != "en")
-                        {
-                            //one more time, in english
-                            id = await AttemptFindId(name, searchType, year, "en", cancellationToken).ConfigureAwait(false);
+                // Search again if the new name is different
+                if (!string.Equals(name, originalName))
+                {
+                    results = await GetSearchResults(name, searchType, year, language, cancellationToken).ConfigureAwait(false);
+
+                    if (results.Count == 0 && !string.Equals(language, "en", StringComparison.OrdinalIgnoreCase))
+                    {
+                        //one more time, in english
+                        results = await GetSearchResults(name, searchType, year, "en", cancellationToken).ConfigureAwait(false);
 
-                        }
                     }
                 }
             }
 
-            return id;
+            return results;
         }
 
-        private async Task<TmdbMovieSearchResult> AttemptFindId(string name, string type, int? year, string language, CancellationToken cancellationToken)
+        private async Task<List<TmdbMovieSearchResult>> GetSearchResults(string name, string type, int? year, string language, CancellationToken cancellationToken)
         {
             var url3 = string.Format(Search3, WebUtility.UrlEncode(name), ApiKey, language, type);
 
@@ -113,9 +114,34 @@ namespace MediaBrowser.Providers.Movies
 
             }).ConfigureAwait(false))
             {
-                var searchResult = _json.DeserializeFromStream<TmdbMovieSearchResults>(json);
-                return FindBestResult(searchResult.results, name, year);
+                var searchResults = _json.DeserializeFromStream<TmdbMovieSearchResults>(json);
+
+                var results = searchResults.results ?? new List<TmdbMovieSearchResult>();
+
+                var index = 0;
+                var resultTuples = results.Select(result => new Tuple<TmdbMovieSearchResult, int>(result, index++)).ToList();
+
+                return resultTuples.OrderBy(i => GetSearchResultOrder(i.Item1, year))
+                    .ThenBy(i => i.Item2)
+                    .Select(i => i.Item1)
+                    .ToList();
+            }
+        }
+
+        private int GetSearchResultOrder(TmdbMovieSearchResult result, int? year)
+        {
+            if (year.HasValue)
+            {
+                DateTime r;
+
+                // These dates are always in this exact format
+                if (DateTime.TryParseExact(result.release_date, "yyyy-MM-dd", EnUs, DateTimeStyles.None, out r))
+                {
+                    return Math.Abs(r.Year - year.Value);
+                }
             }
+
+            return 0;
         }
 
         private TmdbMovieSearchResult FindBestResult(List<TmdbMovieSearchResult> results, string name, int? year)

+ 13 - 1
MediaBrowser.Providers/Movies/MovieDbTrailerProvider.cs

@@ -11,6 +11,13 @@ namespace MediaBrowser.Providers.Movies
 {
     public class MovieDbTrailerProvider : IRemoteMetadataProvider<Trailer, TrailerInfo>, IHasOrder
     {
+        private readonly IHttpClient _httpClient;
+
+        public MovieDbTrailerProvider(IHttpClient httpClient)
+        {
+            _httpClient = httpClient;
+        }
+
         public Task<MetadataResult<Trailer>> GetMetadata(TrailerInfo info, CancellationToken cancellationToken)
         {
             return MovieDbProvider.Current.GetItemMetadata<Trailer>(info, cancellationToken);
@@ -42,7 +49,12 @@ namespace MediaBrowser.Providers.Movies
 
         public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
         {
-            throw new NotImplementedException();
+            return _httpClient.GetResponse(new HttpRequestOptions
+            {
+                CancellationToken = cancellationToken,
+                Url = url,
+                ResourcePool = MovieDbProvider.Current.MovieDbResourcePool
+            });
         }
     }
 }

+ 13 - 2
MediaBrowser.Providers/People/MovieDbPersonProvider.cs

@@ -29,12 +29,14 @@ namespace MediaBrowser.Providers.People
         private readonly IJsonSerializer _jsonSerializer;
         private readonly IFileSystem _fileSystem;
         private readonly IServerConfigurationManager _configurationManager;
+        private readonly IHttpClient _httpClient;
 
-        public MovieDbPersonProvider(IFileSystem fileSystem, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer)
+        public MovieDbPersonProvider(IFileSystem fileSystem, IServerConfigurationManager configurationManager, IJsonSerializer jsonSerializer, IHttpClient httpClient)
         {
             _fileSystem = fileSystem;
             _configurationManager = configurationManager;
             _jsonSerializer = jsonSerializer;
+            _httpClient = httpClient;
             Current = this;
         }
 
@@ -64,6 +66,8 @@ namespace MediaBrowser.Providers.People
                 {
                     Name = info.name,
 
+                    SearchProviderName = Name,
+                    
                     ImageUrl = images.Count == 0 ? null : (tmdbImageUrl + images[0].file_path)
                 };
 
@@ -94,6 +98,8 @@ namespace MediaBrowser.Providers.People
         {
             var result = new RemoteSearchResult
             {
+                SearchProviderName = Name,
+                
                 Name = i.Name,
 
                 ImageUrl = string.IsNullOrEmpty(i.Profile_Path) ? null : (baseImageUrl + i.Profile_Path)
@@ -349,7 +355,12 @@ namespace MediaBrowser.Providers.People
 
         public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
         {
-            throw new NotImplementedException();
+            return _httpClient.GetResponse(new HttpRequestOptions
+            {
+                CancellationToken = cancellationToken,
+                Url = url,
+                ResourcePool = MovieDbProvider.Current.MovieDbResourcePool
+            });
         }
     }
 }

+ 12 - 3
MediaBrowser.Providers/TV/MovieDbSeriesProvider.cs

@@ -33,14 +33,16 @@ namespace MediaBrowser.Providers.TV
         private readonly IServerConfigurationManager _configurationManager;
         private readonly ILogger _logger;
         private readonly ILocalizationManager _localization;
+        private readonly IHttpClient _httpClient;
 
-        public MovieDbSeriesProvider(IJsonSerializer jsonSerializer, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILogger logger, ILocalizationManager localization)
+        public MovieDbSeriesProvider(IJsonSerializer jsonSerializer, IFileSystem fileSystem, IServerConfigurationManager configurationManager, ILogger logger, ILocalizationManager localization, IHttpClient httpClient)
         {
             _jsonSerializer = jsonSerializer;
             _fileSystem = fileSystem;
             _configurationManager = configurationManager;
             _logger = logger;
             _localization = localization;
+            _httpClient = httpClient;
             Current = this;
         }
 
@@ -82,7 +84,9 @@ namespace MediaBrowser.Providers.TV
 
             if (string.IsNullOrEmpty(tmdbId))
             {
-                var searchResult = await new MovieDbSearch(_logger, _jsonSerializer).FindSeriesId(info, cancellationToken).ConfigureAwait(false);
+                var searchResults = await new MovieDbSearch(_logger, _jsonSerializer).GetSearchResults(info, cancellationToken).ConfigureAwait(false);
+
+                var searchResult = searchResults.FirstOrDefault();
 
                 if (searchResult != null)
                 {
@@ -462,7 +466,12 @@ namespace MediaBrowser.Providers.TV
 
         public Task<HttpResponseInfo> GetImageResponse(string url, CancellationToken cancellationToken)
         {
-            throw new NotImplementedException();
+            return _httpClient.GetResponse(new HttpRequestOptions
+            {
+                CancellationToken = cancellationToken,
+                Url = url,
+                ResourcePool = MovieDbProvider.Current.MovieDbResourcePool
+            });
         }
     }
 }

+ 14 - 14
MediaBrowser.Server.Implementations/EntryPoints/ExternalPortForwarding.cs

@@ -42,7 +42,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
 
         public void Run()
         {
-            NatUtility.Logger = new LogWriter(_logger);
+            //NatUtility.Logger = new LogWriter(_logger);
             
             Reload();
         }
@@ -64,17 +64,17 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
 
         void NatUtility_UnhandledException(object sender, UnhandledExceptionEventArgs e)
         {
-            var ex = e.ExceptionObject as Exception;
-
-            if (ex == null)
-            {
-                _logger.Error("Unidentified error reported by Mono.Nat");
-            }
-            else
-            {
-                // Seeing some blank exceptions coming through here
-                _logger.ErrorException("Error reported by Mono.Nat: ", ex);
-            }
+            //var ex = e.ExceptionObject as Exception;
+
+            //if (ex == null)
+            //{
+            //    _logger.Error("Unidentified error reported by Mono.Nat");
+            //}
+            //else
+            //{
+            //    // Seeing some blank exceptions coming through here
+            //    _logger.ErrorException("Error reported by Mono.Nat: ", ex);
+            //}
         }
 
         void NatUtility_DeviceFound(object sender, DeviceEventArgs e)
@@ -88,7 +88,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
             }
             catch (Exception ex)
             {
-                _logger.ErrorException("Error creating port forwarding rules", ex);
+                //_logger.ErrorException("Error creating port forwarding rules", ex);
             }
         }
 
@@ -106,7 +106,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints
 
         private void CreatePortMap(INatDevice device, int port)
         {
-            _logger.Info("Creating port map on port {0}", port);
+            _logger.Debug("Creating port map on port {0}", port);
 
             device.CreatePortMap(new Mapping(Protocol.Tcp, port, port)
             {