diff options
8 files changed, 285 insertions, 96 deletions
diff --git a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs index ca19e8844..23433ac75 100644 --- a/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs +++ b/MediaBrowser.Common.Implementations/HttpClientManager/HttpClientManager.cs @@ -134,11 +134,15 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager if (cachedInfo != null) { - var isCacheValid = (!cachedInfo.MustRevalidate && !string.IsNullOrEmpty(cachedInfo.Etag)) - || (cachedInfo.Expires.HasValue && cachedInfo.Expires.Value > DateTime.UtcNow); + var now = DateTime.UtcNow; + + var isCacheValid = (!cachedInfo.MustRevalidate && !string.IsNullOrEmpty(cachedInfo.Etag) && (now - cachedInfo.RequestDate).TotalDays < 14) + || (cachedInfo.Expires.HasValue && cachedInfo.Expires.Value > now); if (isCacheValid) { + _logger.Debug("Cache is still valid for {0}", options.Url); + try { return GetCachedResponse(cachedReponsePath); @@ -180,26 +184,38 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager var response = await GetHttpClient(GetHostFromUrl(options.Url)).SendAsync(message, HttpCompletionOption.ResponseHeadersRead, options.CancellationToken).ConfigureAwait(false); - EnsureSuccessStatusCode(response); - - options.CancellationToken.ThrowIfCancellationRequested(); - if (options.EnableResponseCache) { + if (response.StatusCode != HttpStatusCode.NotModified) + { + EnsureSuccessStatusCode(response); + } + + options.CancellationToken.ThrowIfCancellationRequested(); + cachedInfo = UpdateInfoCache(cachedInfo, options.Url, cachedInfoPath, response); if (response.StatusCode == HttpStatusCode.NotModified) { + _logger.Debug("Server indicates not modified for {0}. Returning cached result.", options.Url); + return GetCachedResponse(cachedReponsePath); } - if (!string.IsNullOrEmpty(cachedInfo.Etag) || cachedInfo.LastModified.HasValue || (cachedInfo.Expires.HasValue && cachedInfo.Expires.Value > DateTime.UtcNow)) + if (!string.IsNullOrEmpty(cachedInfo.Etag) || cachedInfo.LastModified.HasValue || + (cachedInfo.Expires.HasValue && cachedInfo.Expires.Value > DateTime.UtcNow)) { await UpdateResponseCache(response, cachedReponsePath).ConfigureAwait(false); return GetCachedResponse(cachedReponsePath); } } + else + { + EnsureSuccessStatusCode(response); + + options.CancellationToken.ThrowIfCancellationRequested(); + } return await response.Content.ReadAsStreamAsync().ConfigureAwait(false); } @@ -284,6 +300,7 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager } cachedInfo.Url = url; + cachedInfo.RequestDate = DateTime.UtcNow; var etag = response.Headers.ETag; if (etag != null) diff --git a/MediaBrowser.Common.Implementations/HttpClientManager/HttpResponseInfo.cs b/MediaBrowser.Common.Implementations/HttpClientManager/HttpResponseInfo.cs index 240e99d79..4a4612ffb 100644 --- a/MediaBrowser.Common.Implementations/HttpClientManager/HttpResponseInfo.cs +++ b/MediaBrowser.Common.Implementations/HttpClientManager/HttpResponseInfo.cs @@ -36,5 +36,11 @@ namespace MediaBrowser.Common.Implementations.HttpClientManager /// </summary> /// <value><c>true</c> if [must revalidate]; otherwise, <c>false</c>.</value> public bool MustRevalidate { get; set; } + + /// <summary> + /// Gets or sets the request date. + /// </summary> + /// <value>The request date.</value> + public DateTime RequestDate { get; set; } } } diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index afed9fa0b..cb7f635ad 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -117,6 +117,7 @@ <Compile Include="Providers\MediaInfo\MediaEncoderHelpers.cs" /> <Compile Include="Providers\MetadataProviderPriority.cs" /> <Compile Include="Providers\Movies\RottenTomatoesMovieProvider.cs" /> + <Compile Include="Providers\Movies\RottenTomatoesMovieReviewsProvider.cs" /> <Compile Include="Providers\Music\FanArtArtistByNameProvider.cs" /> <Compile Include="Providers\Music\LastfmAlbumProvider.cs" /> <Compile Include="Providers\Music\FanArtAlbumProvider.cs" /> diff --git a/MediaBrowser.Controller/Providers/Movies/RottenTomatoesMovieProvider.cs b/MediaBrowser.Controller/Providers/Movies/RottenTomatoesMovieProvider.cs index d306cc0a6..acf8c8da6 100644 --- a/MediaBrowser.Controller/Providers/Movies/RottenTomatoesMovieProvider.cs +++ b/MediaBrowser.Controller/Providers/Movies/RottenTomatoesMovieProvider.cs @@ -23,18 +23,17 @@ namespace MediaBrowser.Controller.Providers.Movies /// <summary> /// The API key /// </summary> - private const string ApiKey = "x9wjnvv39ntjmt9zs95nm7bg"; + internal const string ApiKey = "x9wjnvv39ntjmt9zs95nm7bg"; - private const string BasicUrl = @"http://api.rottentomatoes.com/api/public/v1.0/"; - private const string Movie = @"movies/{1}.json?apikey={0}"; + internal const string BasicUrl = @"http://api.rottentomatoes.com/api/public/v1.0/"; private const string MovieImdb = @"movie_alias.json?id={1}&type=imdb&apikey={0}"; - private const string MovieSearch = @"movies.json?q={1}&apikey={0}&page_limit=20&page={2}"; - private const string MoviesReviews = @"movies/{1}/reviews.json?review_type=top_critic&page_limit=10&page=1&country=us&apikey={0}"; + + internal static RottenTomatoesMovieProvider Current { get; private set; } /// <summary> /// The _rotten tomatoes resource pool /// </summary> - private readonly SemaphoreSlim _rottenTomatoesResourcePool = new SemaphoreSlim(1, 1); + internal readonly SemaphoreSlim RottenTomatoesResourcePool = new SemaphoreSlim(1, 1); /// <summary> /// Gets the json serializer. @@ -60,6 +59,7 @@ namespace MediaBrowser.Controller.Providers.Movies { JsonSerializer = jsonSerializer; HttpClient = httpClient; + Current = this; } /// <summary> @@ -136,7 +136,7 @@ namespace MediaBrowser.Controller.Providers.Movies get { // Run after moviedb and xml providers - return MetadataProviderPriority.Last; + return MetadataProviderPriority.Third; } } @@ -183,60 +183,23 @@ namespace MediaBrowser.Controller.Providers.Movies return true; } - RTMovieSearchResult hit = null; - // Have IMDB Id using (var stream = await HttpClient.Get(new HttpRequestOptions { Url = GetMovieImdbUrl(imdbId), - ResourcePool = _rottenTomatoesResourcePool, + ResourcePool = RottenTomatoesResourcePool, CancellationToken = cancellationToken, EnableResponseCache = true }).ConfigureAwait(false)) { - var result = JsonSerializer.DeserializeFromStream<RTMovieSearchResult>(stream); + var hit = JsonSerializer.DeserializeFromStream<RTMovieSearchResult>(stream); - if (!string.IsNullOrEmpty(result.id)) + if (!string.IsNullOrEmpty(hit.id)) { // Got a result - hit = result; - } - } - - // If we found any results, that's great! - if (hit != null) - { - item.CriticRatingSummary = hit.critics_consensus; - item.CriticRating = float.Parse(hit.ratings.critics_score); - - using (var stream = await HttpClient.Get(new HttpRequestOptions - { - Url = GetMovieReviewsUrl(hit.id), - ResourcePool = _rottenTomatoesResourcePool, - CancellationToken = cancellationToken, - EnableResponseCache = true - - }).ConfigureAwait(false)) - { - - var result = JsonSerializer.DeserializeFromStream<RTReviewList>(stream); - - item.CriticReviews = result.reviews.Select(rtReview => new ItemReview - { - ReviewerName = rtReview.critic, - Publisher = rtReview.publication, - Date = DateTime.Parse(rtReview.date).ToUniversalTime(), - Caption = rtReview.quote, - Url = rtReview.links.review, - Likes = string.Equals(rtReview.freshness, "fresh", StringComparison.OrdinalIgnoreCase) - - }).ToList(); - - if (data == null) - { - data = new BaseProviderInfo(); - } + item.CriticRatingSummary = hit.critics_consensus; + item.CriticRating = float.Parse(hit.ratings.critics_score); data.Data = GetComparisonData(hit.alternate_ids.imdb); @@ -244,13 +207,6 @@ namespace MediaBrowser.Controller.Providers.Movies item.SetProviderId(MetadataProviders.RottenTomatoes, hit.id); } } - else - { - // Nothing found on RT - Logger.Info("Nothing found on RottenTomatoes for Movie \"{0}\"", item.Name); - - // TODO: When alternative names are implemented search for those instead - } data.Data = GetComparisonData(imdbId); data.LastRefreshStatus = ProviderRefreshStatus.Success; @@ -267,11 +223,6 @@ namespace MediaBrowser.Controller.Providers.Movies return BasicUrl + string.Format(MovieImdb, ApiKey, imdbId.TrimStart('t')); } - private string GetMovieReviewsUrl(string rtId) - { - return BasicUrl + string.Format(MoviesReviews, ApiKey, rtId); - } - // Data contract classes for use with the Rotten Tomatoes API protected class RTSearchResults @@ -312,27 +263,5 @@ namespace MediaBrowser.Controller.Providers.Movies { public string imdb { get; set; } } - - protected class RTReviewList - { - public int total { get; set; } - public List<RTReview> reviews { get; set; } - } - - protected class RTReview - { - public string critic { get; set; } - public string date { get; set; } - public string freshness { get; set; } - public string publication { get; set; } - public string quote { get; set; } - public RTReviewLink links { get; set; } - public string original_score { get; set; } - } - - protected class RTReviewLink - { - public string review { get; set; } - } } }
\ No newline at end of file diff --git a/MediaBrowser.Controller/Providers/Movies/RottenTomatoesMovieReviewsProvider.cs b/MediaBrowser.Controller/Providers/Movies/RottenTomatoesMovieReviewsProvider.cs new file mode 100644 index 000000000..3e2a5b558 --- /dev/null +++ b/MediaBrowser.Controller/Providers/Movies/RottenTomatoesMovieReviewsProvider.cs @@ -0,0 +1,236 @@ +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.Serialization; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace MediaBrowser.Controller.Providers.Movies +{ + /// <summary> + /// Class RottenTomatoesMovieProvider + /// </summary> + public class RottenTomatoesMovieReviewsProvider : BaseMetadataProvider + { + // http://developer.rottentomatoes.com/iodocs + + private const string MoviesReviews = @"movies/{1}/reviews.json?review_type=top_critic&page_limit=10&page=1&country=us&apikey={0}"; + + /// <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; } + + /// <summary> + /// Initializes a new instance of the <see cref="RottenTomatoesMovieProvider"/> 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> + public RottenTomatoesMovieReviewsProvider(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 "5"; + } + } + + /// <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) + { + return false; + var trailer = item as Trailer; + + if (trailer != null) + { + return !trailer.IsLocalTrailer; + } + + // Don't support local trailers + return item is Movie; + } + + /// <summary> + /// Gets the comparison data. + /// </summary> + /// <param name="imdbId">The imdb id.</param> + /// <returns>Guid.</returns> + private Guid GetComparisonData(string imdbId) + { + return string.IsNullOrEmpty(imdbId) ? Guid.Empty : imdbId.GetMD5(); + } + + /// <summary> + /// Gets the priority. + /// </summary> + /// <value>The priority.</value> + public override MetadataProviderPriority Priority + { + get + { + // Run after moviedb and xml providers + return MetadataProviderPriority.Last; + } + } + + /// <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) + { + // Refresh if rt id has changed + if (providerInfo.Data != GetComparisonData(item.GetProviderId(MetadataProviders.RottenTomatoes))) + { + return true; + } + + return base.NeedsRefreshInternal(item, providerInfo); + } + + /// <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, CancellationToken cancellationToken) + { + BaseProviderInfo data; + + if (!item.ProviderData.TryGetValue(Id, out data)) + { + data = new BaseProviderInfo(); + item.ProviderData[Id] = data; + } + + var rottenTomatoesId = item.GetProviderId(MetadataProviders.RottenTomatoes); + + + if (string.IsNullOrEmpty(rottenTomatoesId)) + { + data.Data = GetComparisonData(rottenTomatoesId); + data.LastRefreshStatus = ProviderRefreshStatus.Success; + return true; + } + + using (var stream = await HttpClient.Get(new HttpRequestOptions + { + Url = GetMovieReviewsUrl(rottenTomatoesId), + ResourcePool = RottenTomatoesMovieProvider.Current.RottenTomatoesResourcePool, + CancellationToken = cancellationToken, + EnableResponseCache = true + + }).ConfigureAwait(false)) + { + + var result = JsonSerializer.DeserializeFromStream<RTReviewList>(stream); + + item.CriticReviews = result.reviews.Select(rtReview => new ItemReview + { + ReviewerName = rtReview.critic, + Publisher = rtReview.publication, + Date = DateTime.Parse(rtReview.date).ToUniversalTime(), + Caption = rtReview.quote, + Url = rtReview.links.review, + Likes = string.Equals(rtReview.freshness, "fresh", StringComparison.OrdinalIgnoreCase) + + }).ToList(); + } + + data.Data = GetComparisonData(rottenTomatoesId); + data.LastRefreshStatus = ProviderRefreshStatus.Success; + SetLastRefreshed(item, DateTime.UtcNow); + + return true; + } + + // Utility functions to get the URL of the API calls + + private string GetMovieReviewsUrl(string rtId) + { + return RottenTomatoesMovieProvider.BasicUrl + string.Format(MoviesReviews, RottenTomatoesMovieProvider.ApiKey, rtId); + } + + // Data contract classes for use with the Rotten Tomatoes API + + protected class RTReviewList + { + public int total { get; set; } + public List<RTReview> reviews { get; set; } + } + + protected class RTReview + { + public string critic { get; set; } + public string date { get; set; } + public string freshness { get; set; } + public string publication { get; set; } + public string quote { get; set; } + public RTReviewLink links { get; set; } + public string original_score { get; set; } + } + + protected class RTReviewLink + { + public string review { get; set; } + } + } +}
\ No newline at end of file diff --git a/Nuget/MediaBrowser.Common.Internal.nuspec b/Nuget/MediaBrowser.Common.Internal.nuspec index b15974fe6..b1688a11d 100644 --- a/Nuget/MediaBrowser.Common.Internal.nuspec +++ b/Nuget/MediaBrowser.Common.Internal.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd"> <metadata> <id>MediaBrowser.Common.Internal</id> - <version>3.0.91</version> + <version>3.0.92</version> <title>MediaBrowser.Common.Internal</title> <authors>Luke</authors> <owners>ebr,Luke,scottisafool</owners> @@ -12,9 +12,9 @@ <description>Contains common components shared by Media Browser Theatre and Media Browser Server. Not intended for plugin developer consumption.</description> <copyright>Copyright © Media Browser 2013</copyright> <dependencies> - <dependency id="MediaBrowser.Common" version="3.0.91" /> + <dependency id="MediaBrowser.Common" version="3.0.92" /> <dependency id="NLog" version="2.0.1.2" /> - <dependency id="ServiceStack.Text" version="3.9.91" /> + <dependency id="ServiceStack.Text" version="3.9.92" /> <dependency id="SimpleInjector" version="2.2.1" /> </dependencies> </metadata> diff --git a/Nuget/MediaBrowser.Common.nuspec b/Nuget/MediaBrowser.Common.nuspec index 97210cd62..acfb08736 100644 --- a/Nuget/MediaBrowser.Common.nuspec +++ b/Nuget/MediaBrowser.Common.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <metadata> <id>MediaBrowser.Common</id> - <version>3.0.91</version> + <version>3.0.92</version> <title>MediaBrowser.Common</title> <authors>Media Browser Team</authors> <owners>ebr,Luke,scottisafool</owners> diff --git a/Nuget/MediaBrowser.Server.Core.nuspec b/Nuget/MediaBrowser.Server.Core.nuspec index 0bd9c18cf..11c79207a 100644 --- a/Nuget/MediaBrowser.Server.Core.nuspec +++ b/Nuget/MediaBrowser.Server.Core.nuspec @@ -2,7 +2,7 @@ <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <metadata> <id>MediaBrowser.Server.Core</id> - <version>3.0.91</version> + <version>3.0.92</version> <title>Media Browser.Server.Core</title> <authors>Media Browser Team</authors> <owners>ebr,Luke,scottisafool</owners> @@ -12,7 +12,7 @@ <description>Contains core components required to build plugins for Media Browser Server.</description> <copyright>Copyright © Media Browser 2013</copyright> <dependencies> - <dependency id="MediaBrowser.Common" version="3.0.91" /> + <dependency id="MediaBrowser.Common" version="3.0.92" /> </dependencies> </metadata> <files> |
