diff --git a/frontend/src/History/HistoryRow.js b/frontend/src/History/HistoryRow.js index 7cc08fc1b..fca5bcb41 100644 --- a/frontend/src/History/HistoryRow.js +++ b/frontend/src/History/HistoryRow.js @@ -351,6 +351,11 @@ class HistoryRow extends Component { `${data.elapsedTime}ms` : null } + { + data.cached === '1' ? + ' (cached)' : + null + } ); } diff --git a/src/NzbDrone.Common/HashUtil.cs b/src/NzbDrone.Common/HashUtil.cs index 8817a95f8..3c9144023 100644 --- a/src/NzbDrone.Common/HashUtil.cs +++ b/src/NzbDrone.Common/HashUtil.cs @@ -29,6 +29,14 @@ namespace NzbDrone.Common return $"{mCrc:x8}"; } + public static string ComputeSha256Hash(string rawData) + { + using var sha256Hash = SHA256.Create(); + var hashBytes = sha256Hash.ComputeHash(Encoding.UTF8.GetBytes(rawData)); + + return Convert.ToHexString(hashBytes); + } + public static string CalculateMd5(string s) { // Use input string to calculate MD5 hash diff --git a/src/NzbDrone.Core/History/HistoryService.cs b/src/NzbDrone.Core/History/HistoryService.cs index 4352a1c67..87481b00b 100644 --- a/src/NzbDrone.Core/History/HistoryService.cs +++ b/src/NzbDrone.Core/History/HistoryService.cs @@ -170,7 +170,7 @@ namespace NzbDrone.Core.History history.Data.Add("Genre", ((BookSearchCriteria)message.Query).Genre ?? string.Empty); } - history.Data.Add("ElapsedTime", message.QueryResult.Response?.ElapsedTime.ToString() ?? string.Empty); + history.Data.Add("ElapsedTime", message.QueryResult.Cached ? "0" : message.QueryResult.Response?.ElapsedTime.ToString() ?? string.Empty); history.Data.Add("Query", message.Query.SearchTerm ?? string.Empty); history.Data.Add("QueryType", message.Query.SearchType ?? string.Empty); history.Data.Add("Categories", string.Join(",", message.Query.Categories) ?? string.Empty); @@ -178,6 +178,7 @@ namespace NzbDrone.Core.History history.Data.Add("Host", message.Query.Host ?? string.Empty); history.Data.Add("QueryResults", message.QueryResult.Releases?.Count.ToString() ?? string.Empty); history.Data.Add("Url", message.QueryResult.Response?.Request.Url.FullUri ?? string.Empty); + history.Data.Add("Cached", message.QueryResult.Cached ? "1" : "0"); _historyRepository.Insert(history); } diff --git a/src/NzbDrone.Core/Indexers/Definitions/AnimeBytes.cs b/src/NzbDrone.Core/Indexers/Definitions/AnimeBytes.cs index 342c50a32..633033eb1 100644 --- a/src/NzbDrone.Core/Indexers/Definitions/AnimeBytes.cs +++ b/src/NzbDrone.Core/Indexers/Definitions/AnimeBytes.cs @@ -8,9 +8,11 @@ using System.Net; using System.Text; using System.Text.Json.Serialization; using System.Text.RegularExpressions; +using System.Threading.Tasks; using FluentValidation; using NLog; using NzbDrone.Common; +using NzbDrone.Common.Cache; using NzbDrone.Common.Extensions; using NzbDrone.Common.Http; using NzbDrone.Common.Serializer; @@ -38,9 +40,12 @@ namespace NzbDrone.Core.Indexers.Definitions public override IndexerCapabilities Capabilities => SetCapabilities(); public override TimeSpan RateLimit => TimeSpan.FromSeconds(4); - public AnimeBytes(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger) + private readonly ICached _queryResultCache; + + public AnimeBytes(IIndexerHttpClient httpClient, IEventAggregator eventAggregator, IIndexerStatusService indexerStatusService, IConfigService configService, Logger logger, ICacheManager cacheManager) : base(httpClient, eventAggregator, indexerStatusService, configService, logger) { + _queryResultCache = cacheManager.GetCache(GetType(), "QueryResults"); } public override IIndexerRequestGenerator GetRequestGenerator() @@ -58,6 +63,38 @@ namespace NzbDrone.Core.Indexers.Definitions return false; } + protected string BuildQueryResultCacheKey(IndexerRequest request) + { + return $"{request.HttpRequest.Url.FullUri}.{HashUtil.ComputeSha256Hash(Settings.ToJson())}"; + } + + protected override async Task FetchPage(IndexerRequest request, IParseIndexerResponse parser) + { + var cacheKey = BuildQueryResultCacheKey(request); + var queryResult = _queryResultCache.Find(cacheKey); + + if (queryResult != null) + { + queryResult.Cached = true; + + return queryResult; + } + + _queryResultCache.ClearExpired(); + + queryResult = await base.FetchPage(request, parser); + _queryResultCache.Set(cacheKey, queryResult, TimeSpan.FromMinutes(3)); + + return queryResult; + } + + protected override IList CleanupReleases(IEnumerable releases, SearchCriteriaBase searchCriteria) + { + var cleanReleases = base.CleanupReleases(releases, searchCriteria); + + return cleanReleases.Select(r => (ReleaseInfo)r.Clone()).ToList(); + } + private IndexerCapabilities SetCapabilities() { var caps = new IndexerCapabilities diff --git a/src/NzbDrone.Core/Indexers/IndexerQueryResult.cs b/src/NzbDrone.Core/Indexers/IndexerQueryResult.cs index 4bddb8f5d..6cd6f60bc 100644 --- a/src/NzbDrone.Core/Indexers/IndexerQueryResult.cs +++ b/src/NzbDrone.Core/Indexers/IndexerQueryResult.cs @@ -13,5 +13,6 @@ namespace NzbDrone.Core.Indexers public IList Releases { get; set; } public HttpResponse Response { get; set; } + public bool Cached { get; set; } } } diff --git a/src/NzbDrone.Core/Parser/Model/ReleaseInfo.cs b/src/NzbDrone.Core/Parser/Model/ReleaseInfo.cs index d782399cf..22827f43c 100644 --- a/src/NzbDrone.Core/Parser/Model/ReleaseInfo.cs +++ b/src/NzbDrone.Core/Parser/Model/ReleaseInfo.cs @@ -1,12 +1,11 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Text; using NzbDrone.Core.Indexers; namespace NzbDrone.Core.Parser.Model { - public class ReleaseInfo + public class ReleaseInfo : ICloneable { public ReleaseInfo() { @@ -115,5 +114,10 @@ namespace NzbDrone.Core.Parser.Model return ToString(); } } + + public object Clone() + { + return MemberwiseClone(); + } } }