diff --git a/src/NzbDrone.Api/Extensions/Pipelines/GZipPipeline.cs b/src/NzbDrone.Api/Extensions/Pipelines/GZipPipeline.cs index 29fb1a7de..a671d1f02 100644 --- a/src/NzbDrone.Api/Extensions/Pipelines/GZipPipeline.cs +++ b/src/NzbDrone.Api/Extensions/Pipelines/GZipPipeline.cs @@ -26,7 +26,9 @@ namespace NzbDrone.Api.Extensions.Pipelines { try { - if (!response.ContentType.Contains("image") + if ( + !response.ContentType.Contains("image") + && !response.ContentType.Contains("font") && request.Headers.AcceptEncoding.Any(x => x.Contains("gzip")) && (!response.Headers.ContainsKey("Content-Encoding") || response.Headers["Content-Encoding"] != "gzip")) { 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 47759b5c5..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; } @@ -51,22 +57,34 @@ namespace NzbDrone.Api.Frontend.Mappers protected override Stream GetContentStream(string filePath) { - return StringToStream(GetIndexText()); + var text = GetIndexText(); + + var stream = new MemoryStream(); + 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; + } - text = ReplaceRegex.Replace(text, match => URL_BASE + match.Value); + _generatedContent = _diskProvider.ReadAllText(_indexPath); - 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()); + var cacheBreakProvider = _cacheBreakProviderFactory(); - return text; + _generatedContent = ReplaceRegex.Replace(_generatedContent, match => cacheBreakProvider.AddCacheBreakerToPath(URL_BASE + match.Value)); + + _generatedContent = _generatedContent.Replace("API_ROOT", URL_BASE + "/api"); + _generatedContent = _generatedContent.Replace("API_KEY", API_KEY); + _generatedContent = _generatedContent.Replace("APP_VERSION", BuildInfo.Version.ToString()); + + 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 75cd56180..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); @@ -51,14 +51,5 @@ namespace NzbDrone.Api.Frontend.Mappers return File.OpenRead(filePath); } - protected static Stream StringToStream(string text) - { - var stream = new MemoryStream(); - var writer = new StreamWriter(stream); - writer.Write(text); - writer.Flush(); - stream.Position = 0; - return stream; - } } } \ No newline at end of file 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/HashProvider.cs b/src/NzbDrone.Common/Crypto/HashProvider.cs new file mode 100644 index 000000000..2d7cf86b6 --- /dev/null +++ b/src/NzbDrone.Common/Crypto/HashProvider.cs @@ -0,0 +1,31 @@ +using System.Security.Cryptography; +using NzbDrone.Common.Disk; + +namespace NzbDrone.Common.Crypto +{ + public interface IHashProvider + { + byte[] ComputeMd5(string path); + } + + public class HashProvider : IHashProvider + { + private readonly IDiskProvider _diskProvider; + + public HashProvider(IDiskProvider diskProvider) + { + _diskProvider = diskProvider; + } + + public byte[] ComputeMd5(string path) + { + using (var md5 = MD5.Create()) + { + using (var stream = _diskProvider.StreamFile(path)) + { + return md5.ComputeHash(stream); + } + } + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Common/Extensions/Base64Extentions.cs b/src/NzbDrone.Common/Extensions/Base64Extentions.cs new file mode 100644 index 000000000..3a2dbcf3f --- /dev/null +++ b/src/NzbDrone.Common/Extensions/Base64Extentions.cs @@ -0,0 +1,17 @@ +using System; + +namespace NzbDrone.Common.Extensions +{ + public static class Base64Extentions + { + public static string ToBase64(this byte[] bytes) + { + return Convert.ToBase64String(bytes); + } + + public static string ToBase64(this long input) + { + return BitConverter.GetBytes(input).ToBase64(); + } + } +} \ No newline at end of file diff --git a/src/NzbDrone.Common/HashUtil.cs b/src/NzbDrone.Common/HashUtil.cs index abeb9496d..19353f21b 100644 --- a/src/NzbDrone.Common/HashUtil.cs +++ b/src/NzbDrone.Common/HashUtil.cs @@ -1,16 +1,10 @@ using System; using System.Text; -using System.Threading; namespace NzbDrone.Common { public static class HashUtil { - //This should never be changed. very bad things will happen! - private static readonly DateTime Epoch = new DateTime(2010, 1, 1); - - private static readonly object _lock = new object(); - public static string CalculateCrc(string input) { uint mCrc = 0xffffffff; @@ -32,36 +26,5 @@ namespace NzbDrone.Common } return String.Format("{0:x8}", mCrc); } - - public static string GenerateCommandId() - { - return GenerateId("c"); - } - - private static string GenerateId(string prefix) - { - lock (_lock) - { - Thread.Sleep(1); - var tick = (DateTime.Now - Epoch).Ticks; - return prefix + "." + ToBase(tick); - } - } - - private static string ToBase(long input) - { - const string BASE_CHARS = "0123456789abcdefghijklmnopqrstuvwxyz"; - int targetBase = BASE_CHARS.Length; - - var result = new StringBuilder(); - do - { - result.Append(BASE_CHARS[(int)(input % targetBase)]); - input /= targetBase; - } while (input > 0); - - return result.ToString(); - } - } } \ No newline at end of file diff --git a/src/NzbDrone.Common/NzbDrone.Common.csproj b/src/NzbDrone.Common/NzbDrone.Common.csproj index bd3053661..e93248231 100644 --- a/src/NzbDrone.Common/NzbDrone.Common.csproj +++ b/src/NzbDrone.Common/NzbDrone.Common.csproj @@ -73,6 +73,7 @@ + @@ -110,6 +111,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 @@ - - - - - + + + + +