From c4e8e3ebc7b11ff851abae24c920f6d04d15469d Mon Sep 17 00:00:00 2001 From: kayone Date: Sun, 31 Aug 2014 00:40:23 -0700 Subject: [PATCH] Added cache breaking based on hash rather than version --- .../Frontend/CacheableSpecification.cs | 2 +- .../Frontend/Mappers/BackupFileMapper.cs | 2 +- .../Frontend/Mappers/CacheBreakerProvider.cs | 43 +++++++++++++++++++ .../Frontend/Mappers/FaviconMapper.cs | 2 +- .../Mappers/IMapHttpRequestsToDisk.cs | 1 + .../Frontend/Mappers/IndexHtmlMapper.cs | 37 ++++++++++------ .../Frontend/Mappers/LogFileMapper.cs | 2 +- .../Frontend/Mappers/MediaCoverMapper.cs | 2 +- .../Frontend/Mappers/StaticResourceMapper.cs | 2 +- .../Mappers/StaticResourceMapperBase.cs | 2 +- .../Frontend/Mappers/UpdateLogFileMapper.cs | 2 +- src/NzbDrone.Api/NzbDrone.Api.csproj | 1 + .../{Md5HashProvider.cs => HashProvider.cs} | 11 +++-- src/NzbDrone.Common/NzbDrone.Common.csproj | 2 +- src/UI/index.html | 10 ++--- 15 files changed, 90 insertions(+), 31 deletions(-) create mode 100644 src/NzbDrone.Api/Frontend/Mappers/CacheBreakerProvider.cs rename src/NzbDrone.Common/Crypto/{Md5HashProvider.cs => HashProvider.cs} (66%) diff --git a/src/NzbDrone.Api/Frontend/CacheableSpecification.cs b/src/NzbDrone.Api/Frontend/CacheableSpecification.cs index 52b177ce8..cc83e8607 100644 --- a/src/NzbDrone.Api/Frontend/CacheableSpecification.cs +++ b/src/NzbDrone.Api/Frontend/CacheableSpecification.cs @@ -19,7 +19,7 @@ namespace NzbDrone.Api.Frontend return false; } - if (context.Request.Query.v == BuildInfo.Version) return true; + if (((DynamicDictionary)context.Request.Query).ContainsKey("h")) return true; if (context.Request.Path.StartsWith("/api", StringComparison.CurrentCultureIgnoreCase)) { diff --git a/src/NzbDrone.Api/Frontend/Mappers/BackupFileMapper.cs b/src/NzbDrone.Api/Frontend/Mappers/BackupFileMapper.cs index a2e111430..7f7690287 100644 --- a/src/NzbDrone.Api/Frontend/Mappers/BackupFileMapper.cs +++ b/src/NzbDrone.Api/Frontend/Mappers/BackupFileMapper.cs @@ -16,7 +16,7 @@ namespace NzbDrone.Api.Frontend.Mappers _appFolderInfo = appFolderInfo; } - protected override string Map(string resourceUrl) + public override string Map(string resourceUrl) { var path = resourceUrl.Replace("/backup/", "").Replace('/', Path.DirectorySeparatorChar); diff --git a/src/NzbDrone.Api/Frontend/Mappers/CacheBreakerProvider.cs b/src/NzbDrone.Api/Frontend/Mappers/CacheBreakerProvider.cs new file mode 100644 index 000000000..78f84d0eb --- /dev/null +++ b/src/NzbDrone.Api/Frontend/Mappers/CacheBreakerProvider.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using System.Linq; +using NzbDrone.Common.Crypto; +using NzbDrone.Common.Extensions; + +namespace NzbDrone.Api.Frontend.Mappers +{ + public interface ICacheBreakerProvider + { + string AddCacheBreakerToPath(string resourceUrl); + } + + public class CacheBreakerProvider : ICacheBreakerProvider + { + private readonly IEnumerable _diskMappers; + private readonly IHashProvider _hashProvider; + + public CacheBreakerProvider(IEnumerable diskMappers, IHashProvider hashProvider) + { + _diskMappers = diskMappers; + _hashProvider = hashProvider; + } + + public string AddCacheBreakerToPath(string resourceUrl) + { + if (!ShouldBreakCache(resourceUrl)) + { + return resourceUrl; + } + + var mapper = _diskMappers.Single(m => m.CanHandle(resourceUrl)); + var pathToFile = mapper.Map(resourceUrl); + var hash = _hashProvider.ComputeMd5(pathToFile).ToBase64(); + + return resourceUrl + "?h=" + hash; + } + + private static bool ShouldBreakCache(string path) + { + return !path.EndsWith(".ics") && !path.EndsWith("main"); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Api/Frontend/Mappers/FaviconMapper.cs b/src/NzbDrone.Api/Frontend/Mappers/FaviconMapper.cs index e3c810a8d..e82fcd898 100644 --- a/src/NzbDrone.Api/Frontend/Mappers/FaviconMapper.cs +++ b/src/NzbDrone.Api/Frontend/Mappers/FaviconMapper.cs @@ -15,7 +15,7 @@ namespace NzbDrone.Api.Frontend.Mappers _appFolderInfo = appFolderInfo; } - protected override string Map(string resourceUrl) + public override string Map(string resourceUrl) { var path = Path.Combine("Content", "Images", "favicon.ico"); diff --git a/src/NzbDrone.Api/Frontend/Mappers/IMapHttpRequestsToDisk.cs b/src/NzbDrone.Api/Frontend/Mappers/IMapHttpRequestsToDisk.cs index 20216f49c..6390a2545 100644 --- a/src/NzbDrone.Api/Frontend/Mappers/IMapHttpRequestsToDisk.cs +++ b/src/NzbDrone.Api/Frontend/Mappers/IMapHttpRequestsToDisk.cs @@ -5,6 +5,7 @@ namespace NzbDrone.Api.Frontend.Mappers { public interface IMapHttpRequestsToDisk { + string Map(string resourceUrl); bool CanHandle(string resourceUrl); Response GetResponse(string resourceUrl); } diff --git a/src/NzbDrone.Api/Frontend/Mappers/IndexHtmlMapper.cs b/src/NzbDrone.Api/Frontend/Mappers/IndexHtmlMapper.cs index 0de2a2622..69aa65855 100644 --- a/src/NzbDrone.Api/Frontend/Mappers/IndexHtmlMapper.cs +++ b/src/NzbDrone.Api/Frontend/Mappers/IndexHtmlMapper.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Runtime.InteropServices; using System.Text.RegularExpressions; using Nancy; using NLog; @@ -12,26 +13,31 @@ namespace NzbDrone.Api.Frontend.Mappers public class IndexHtmlMapper : StaticResourceMapperBase { private readonly IDiskProvider _diskProvider; + private readonly Func _cacheBreakProviderFactory; private readonly string _indexPath; private static readonly Regex ReplaceRegex = new Regex("(?<=(?:href|src|data-main)=\").*?(?=\")", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static String API_KEY; private static String URL_BASE; + private string _generatedContent + ; public IndexHtmlMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, IConfigFileProvider configFileProvider, + Func cacheBreakProviderFactory, Logger logger) : base(diskProvider, logger) { _diskProvider = diskProvider; + _cacheBreakProviderFactory = cacheBreakProviderFactory; _indexPath = Path.Combine(appFolderInfo.StartUpFolder, "UI", "index.html"); API_KEY = configFileProvider.ApiKey; URL_BASE = configFileProvider.UrlBase; } - protected override string Map(string resourceUrl) + public override string Map(string resourceUrl) { return _indexPath; } @@ -54,28 +60,31 @@ namespace NzbDrone.Api.Frontend.Mappers var text = GetIndexText(); var stream = new MemoryStream(); - using (var writer = new StreamWriter(stream)) - { - writer.Write(text); - writer.Flush(); - } + var writer = new StreamWriter(stream); + writer.Write(text); + writer.Flush(); stream.Position = 0; return stream; } private string GetIndexText() { - var text = _diskProvider.ReadAllText(_indexPath); + if (RuntimeInfoBase.IsProduction && _generatedContent != null) + { + return _generatedContent; + } + + _generatedContent = _diskProvider.ReadAllText(_indexPath); + + var cacheBreakProvider = _cacheBreakProviderFactory(); - text = ReplaceRegex.Replace(text, match => URL_BASE + match.Value); + _generatedContent = ReplaceRegex.Replace(_generatedContent, match => cacheBreakProvider.AddCacheBreakerToPath(URL_BASE + match.Value)); - text = text.Replace(".css", ".css?v=" + BuildInfo.Version); - text = text.Replace(".js", ".js?v=" + BuildInfo.Version); - text = text.Replace("API_ROOT", URL_BASE + "/api"); - text = text.Replace("API_KEY", API_KEY); - text = text.Replace("APP_VERSION", BuildInfo.Version.ToString()); + _generatedContent = _generatedContent.Replace("API_ROOT", URL_BASE + "/api"); + _generatedContent = _generatedContent.Replace("API_KEY", API_KEY); + _generatedContent = _generatedContent.Replace("APP_VERSION", BuildInfo.Version.ToString()); - return text; + return _generatedContent; } } } \ No newline at end of file diff --git a/src/NzbDrone.Api/Frontend/Mappers/LogFileMapper.cs b/src/NzbDrone.Api/Frontend/Mappers/LogFileMapper.cs index 588775839..0a165d1c7 100644 --- a/src/NzbDrone.Api/Frontend/Mappers/LogFileMapper.cs +++ b/src/NzbDrone.Api/Frontend/Mappers/LogFileMapper.cs @@ -16,7 +16,7 @@ namespace NzbDrone.Api.Frontend.Mappers _appFolderInfo = appFolderInfo; } - protected override string Map(string resourceUrl) + public override string Map(string resourceUrl) { var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar); path = Path.GetFileName(path); diff --git a/src/NzbDrone.Api/Frontend/Mappers/MediaCoverMapper.cs b/src/NzbDrone.Api/Frontend/Mappers/MediaCoverMapper.cs index f6864b06a..1ff70a345 100644 --- a/src/NzbDrone.Api/Frontend/Mappers/MediaCoverMapper.cs +++ b/src/NzbDrone.Api/Frontend/Mappers/MediaCoverMapper.cs @@ -16,7 +16,7 @@ namespace NzbDrone.Api.Frontend.Mappers _appFolderInfo = appFolderInfo; } - protected override string Map(string resourceUrl) + public override string Map(string resourceUrl) { var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar); path = path.Trim(Path.DirectorySeparatorChar); diff --git a/src/NzbDrone.Api/Frontend/Mappers/StaticResourceMapper.cs b/src/NzbDrone.Api/Frontend/Mappers/StaticResourceMapper.cs index a81bb4ab7..3ef823592 100644 --- a/src/NzbDrone.Api/Frontend/Mappers/StaticResourceMapper.cs +++ b/src/NzbDrone.Api/Frontend/Mappers/StaticResourceMapper.cs @@ -15,7 +15,7 @@ namespace NzbDrone.Api.Frontend.Mappers _appFolderInfo = appFolderInfo; } - protected override string Map(string resourceUrl) + public override string Map(string resourceUrl) { var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar); path = path.Trim(Path.DirectorySeparatorChar); diff --git a/src/NzbDrone.Api/Frontend/Mappers/StaticResourceMapperBase.cs b/src/NzbDrone.Api/Frontend/Mappers/StaticResourceMapperBase.cs index f0a4acd9d..4f5e635bc 100644 --- a/src/NzbDrone.Api/Frontend/Mappers/StaticResourceMapperBase.cs +++ b/src/NzbDrone.Api/Frontend/Mappers/StaticResourceMapperBase.cs @@ -27,7 +27,7 @@ namespace NzbDrone.Api.Frontend.Mappers } } - protected abstract string Map(string resourceUrl); + public abstract string Map(string resourceUrl); public abstract bool CanHandle(string resourceUrl); diff --git a/src/NzbDrone.Api/Frontend/Mappers/UpdateLogFileMapper.cs b/src/NzbDrone.Api/Frontend/Mappers/UpdateLogFileMapper.cs index bc7124a97..80a11da65 100644 --- a/src/NzbDrone.Api/Frontend/Mappers/UpdateLogFileMapper.cs +++ b/src/NzbDrone.Api/Frontend/Mappers/UpdateLogFileMapper.cs @@ -16,7 +16,7 @@ namespace NzbDrone.Api.Frontend.Mappers _appFolderInfo = appFolderInfo; } - protected override string Map(string resourceUrl) + public override string Map(string resourceUrl) { var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar); path = Path.GetFileName(path); diff --git a/src/NzbDrone.Api/NzbDrone.Api.csproj b/src/NzbDrone.Api/NzbDrone.Api.csproj index 19594a4e9..51303d2de 100644 --- a/src/NzbDrone.Api/NzbDrone.Api.csproj +++ b/src/NzbDrone.Api/NzbDrone.Api.csproj @@ -139,6 +139,7 @@ + diff --git a/src/NzbDrone.Common/Crypto/Md5HashProvider.cs b/src/NzbDrone.Common/Crypto/HashProvider.cs similarity index 66% rename from src/NzbDrone.Common/Crypto/Md5HashProvider.cs rename to src/NzbDrone.Common/Crypto/HashProvider.cs index e6825c66a..2d7cf86b6 100644 --- a/src/NzbDrone.Common/Crypto/Md5HashProvider.cs +++ b/src/NzbDrone.Common/Crypto/HashProvider.cs @@ -3,16 +3,21 @@ using NzbDrone.Common.Disk; namespace NzbDrone.Common.Crypto { - public class Md5HashProvider + public interface IHashProvider + { + byte[] ComputeMd5(string path); + } + + public class HashProvider : IHashProvider { private readonly IDiskProvider _diskProvider; - public Md5HashProvider(IDiskProvider diskProvider) + public HashProvider(IDiskProvider diskProvider) { _diskProvider = diskProvider; } - public byte[] ComputeHash(string path) + public byte[] ComputeMd5(string path) { using (var md5 = MD5.Create()) { diff --git a/src/NzbDrone.Common/NzbDrone.Common.csproj b/src/NzbDrone.Common/NzbDrone.Common.csproj index c06a5e2a0..e93248231 100644 --- a/src/NzbDrone.Common/NzbDrone.Common.csproj +++ b/src/NzbDrone.Common/NzbDrone.Common.csproj @@ -73,7 +73,7 @@ - + diff --git a/src/UI/index.html b/src/UI/index.html index f1542fcff..376104d0f 100644 --- a/src/UI/index.html +++ b/src/UI/index.html @@ -23,11 +23,11 @@ - - - - - + + + + +