Merge pull request #107 from NzbDrone/hash-cache-break
Hash cache break
This commit is contained in:
commit
52da4e4423
|
@ -26,7 +26,9 @@ namespace NzbDrone.Api.Extensions.Pipelines
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (!response.ContentType.Contains("image")
|
if (
|
||||||
|
!response.ContentType.Contains("image")
|
||||||
|
&& !response.ContentType.Contains("font")
|
||||||
&& request.Headers.AcceptEncoding.Any(x => x.Contains("gzip"))
|
&& request.Headers.AcceptEncoding.Any(x => x.Contains("gzip"))
|
||||||
&& (!response.Headers.ContainsKey("Content-Encoding") || response.Headers["Content-Encoding"] != "gzip"))
|
&& (!response.Headers.ContainsKey("Content-Encoding") || response.Headers["Content-Encoding"] != "gzip"))
|
||||||
{
|
{
|
||||||
|
|
|
@ -19,7 +19,7 @@ namespace NzbDrone.Api.Frontend
|
||||||
return false;
|
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))
|
if (context.Request.Path.StartsWith("/api", StringComparison.CurrentCultureIgnoreCase))
|
||||||
{
|
{
|
||||||
|
|
|
@ -16,7 +16,7 @@ namespace NzbDrone.Api.Frontend.Mappers
|
||||||
_appFolderInfo = appFolderInfo;
|
_appFolderInfo = appFolderInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string Map(string resourceUrl)
|
public override string Map(string resourceUrl)
|
||||||
{
|
{
|
||||||
var path = resourceUrl.Replace("/backup/", "").Replace('/', Path.DirectorySeparatorChar);
|
var path = resourceUrl.Replace("/backup/", "").Replace('/', Path.DirectorySeparatorChar);
|
||||||
|
|
||||||
|
|
|
@ -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<IMapHttpRequestsToDisk> _diskMappers;
|
||||||
|
private readonly IHashProvider _hashProvider;
|
||||||
|
|
||||||
|
public CacheBreakerProvider(IEnumerable<IMapHttpRequestsToDisk> 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,7 +15,7 @@ namespace NzbDrone.Api.Frontend.Mappers
|
||||||
_appFolderInfo = appFolderInfo;
|
_appFolderInfo = appFolderInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string Map(string resourceUrl)
|
public override string Map(string resourceUrl)
|
||||||
{
|
{
|
||||||
var path = Path.Combine("Content", "Images", "favicon.ico");
|
var path = Path.Combine("Content", "Images", "favicon.ico");
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ namespace NzbDrone.Api.Frontend.Mappers
|
||||||
{
|
{
|
||||||
public interface IMapHttpRequestsToDisk
|
public interface IMapHttpRequestsToDisk
|
||||||
{
|
{
|
||||||
|
string Map(string resourceUrl);
|
||||||
bool CanHandle(string resourceUrl);
|
bool CanHandle(string resourceUrl);
|
||||||
Response GetResponse(string resourceUrl);
|
Response GetResponse(string resourceUrl);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Nancy;
|
using Nancy;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
@ -12,26 +13,31 @@ namespace NzbDrone.Api.Frontend.Mappers
|
||||||
public class IndexHtmlMapper : StaticResourceMapperBase
|
public class IndexHtmlMapper : StaticResourceMapperBase
|
||||||
{
|
{
|
||||||
private readonly IDiskProvider _diskProvider;
|
private readonly IDiskProvider _diskProvider;
|
||||||
|
private readonly Func<ICacheBreakerProvider> _cacheBreakProviderFactory;
|
||||||
private readonly string _indexPath;
|
private readonly string _indexPath;
|
||||||
private static readonly Regex ReplaceRegex = new Regex("(?<=(?:href|src|data-main)=\").*?(?=\")", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
private static readonly Regex ReplaceRegex = new Regex("(?<=(?:href|src|data-main)=\").*?(?=\")", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||||
|
|
||||||
private static String API_KEY;
|
private static String API_KEY;
|
||||||
private static String URL_BASE;
|
private static String URL_BASE;
|
||||||
|
private string _generatedContent
|
||||||
|
;
|
||||||
|
|
||||||
public IndexHtmlMapper(IAppFolderInfo appFolderInfo,
|
public IndexHtmlMapper(IAppFolderInfo appFolderInfo,
|
||||||
IDiskProvider diskProvider,
|
IDiskProvider diskProvider,
|
||||||
IConfigFileProvider configFileProvider,
|
IConfigFileProvider configFileProvider,
|
||||||
|
Func<ICacheBreakerProvider> cacheBreakProviderFactory,
|
||||||
Logger logger)
|
Logger logger)
|
||||||
: base(diskProvider, logger)
|
: base(diskProvider, logger)
|
||||||
{
|
{
|
||||||
_diskProvider = diskProvider;
|
_diskProvider = diskProvider;
|
||||||
|
_cacheBreakProviderFactory = cacheBreakProviderFactory;
|
||||||
_indexPath = Path.Combine(appFolderInfo.StartUpFolder, "UI", "index.html");
|
_indexPath = Path.Combine(appFolderInfo.StartUpFolder, "UI", "index.html");
|
||||||
|
|
||||||
API_KEY = configFileProvider.ApiKey;
|
API_KEY = configFileProvider.ApiKey;
|
||||||
URL_BASE = configFileProvider.UrlBase;
|
URL_BASE = configFileProvider.UrlBase;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string Map(string resourceUrl)
|
public override string Map(string resourceUrl)
|
||||||
{
|
{
|
||||||
return _indexPath;
|
return _indexPath;
|
||||||
}
|
}
|
||||||
|
@ -51,22 +57,34 @@ namespace NzbDrone.Api.Frontend.Mappers
|
||||||
|
|
||||||
protected override Stream GetContentStream(string filePath)
|
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()
|
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);
|
var cacheBreakProvider = _cacheBreakProviderFactory();
|
||||||
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());
|
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -16,7 +16,7 @@ namespace NzbDrone.Api.Frontend.Mappers
|
||||||
_appFolderInfo = appFolderInfo;
|
_appFolderInfo = appFolderInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string Map(string resourceUrl)
|
public override string Map(string resourceUrl)
|
||||||
{
|
{
|
||||||
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
|
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
|
||||||
path = Path.GetFileName(path);
|
path = Path.GetFileName(path);
|
||||||
|
|
|
@ -16,7 +16,7 @@ namespace NzbDrone.Api.Frontend.Mappers
|
||||||
_appFolderInfo = appFolderInfo;
|
_appFolderInfo = appFolderInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string Map(string resourceUrl)
|
public override string Map(string resourceUrl)
|
||||||
{
|
{
|
||||||
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
|
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
|
||||||
path = path.Trim(Path.DirectorySeparatorChar);
|
path = path.Trim(Path.DirectorySeparatorChar);
|
||||||
|
|
|
@ -15,7 +15,7 @@ namespace NzbDrone.Api.Frontend.Mappers
|
||||||
_appFolderInfo = appFolderInfo;
|
_appFolderInfo = appFolderInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string Map(string resourceUrl)
|
public override string Map(string resourceUrl)
|
||||||
{
|
{
|
||||||
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
|
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
|
||||||
path = path.Trim(Path.DirectorySeparatorChar);
|
path = path.Trim(Path.DirectorySeparatorChar);
|
||||||
|
|
|
@ -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);
|
public abstract bool CanHandle(string resourceUrl);
|
||||||
|
|
||||||
|
@ -51,14 +51,5 @@ namespace NzbDrone.Api.Frontend.Mappers
|
||||||
return File.OpenRead(filePath);
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -16,7 +16,7 @@ namespace NzbDrone.Api.Frontend.Mappers
|
||||||
_appFolderInfo = appFolderInfo;
|
_appFolderInfo = appFolderInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override string Map(string resourceUrl)
|
public override string Map(string resourceUrl)
|
||||||
{
|
{
|
||||||
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
|
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
|
||||||
path = Path.GetFileName(path);
|
path = Path.GetFileName(path);
|
||||||
|
|
|
@ -139,6 +139,7 @@
|
||||||
<Compile Include="Extensions\RequestExtensions.cs" />
|
<Compile Include="Extensions\RequestExtensions.cs" />
|
||||||
<Compile Include="Frontend\CacheableSpecification.cs" />
|
<Compile Include="Frontend\CacheableSpecification.cs" />
|
||||||
<Compile Include="Frontend\Mappers\BackupFileMapper.cs" />
|
<Compile Include="Frontend\Mappers\BackupFileMapper.cs" />
|
||||||
|
<Compile Include="Frontend\Mappers\CacheBreakerProvider.cs" />
|
||||||
<Compile Include="Frontend\Mappers\FaviconMapper.cs" />
|
<Compile Include="Frontend\Mappers\FaviconMapper.cs" />
|
||||||
<Compile Include="Frontend\Mappers\IMapHttpRequestsToDisk.cs" />
|
<Compile Include="Frontend\Mappers\IMapHttpRequestsToDisk.cs" />
|
||||||
<Compile Include="Frontend\Mappers\IndexHtmlMapper.cs" />
|
<Compile Include="Frontend\Mappers\IndexHtmlMapper.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,16 +1,10 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace NzbDrone.Common
|
namespace NzbDrone.Common
|
||||||
{
|
{
|
||||||
public static class HashUtil
|
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)
|
public static string CalculateCrc(string input)
|
||||||
{
|
{
|
||||||
uint mCrc = 0xffffffff;
|
uint mCrc = 0xffffffff;
|
||||||
|
@ -32,36 +26,5 @@ namespace NzbDrone.Common
|
||||||
}
|
}
|
||||||
return String.Format("{0:x8}", mCrc);
|
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -73,6 +73,7 @@
|
||||||
<Compile Include="Composition\IContainer.cs" />
|
<Compile Include="Composition\IContainer.cs" />
|
||||||
<Compile Include="ConsoleService.cs" />
|
<Compile Include="ConsoleService.cs" />
|
||||||
<Compile Include="ConvertBase32.cs" />
|
<Compile Include="ConvertBase32.cs" />
|
||||||
|
<Compile Include="Crypto\HashProvider.cs" />
|
||||||
<Compile Include="DictionaryExtensions.cs" />
|
<Compile Include="DictionaryExtensions.cs" />
|
||||||
<Compile Include="Disk\DiskProviderBase.cs" />
|
<Compile Include="Disk\DiskProviderBase.cs" />
|
||||||
<Compile Include="Disk\IDiskProvider.cs" />
|
<Compile Include="Disk\IDiskProvider.cs" />
|
||||||
|
@ -110,6 +111,7 @@
|
||||||
<Compile Include="Expansive\Tree.cs" />
|
<Compile Include="Expansive\Tree.cs" />
|
||||||
<Compile Include="Expansive\TreeNode.cs" />
|
<Compile Include="Expansive\TreeNode.cs" />
|
||||||
<Compile Include="Expansive\TreeNodeList.cs" />
|
<Compile Include="Expansive\TreeNodeList.cs" />
|
||||||
|
<Compile Include="Extensions\Base64Extentions.cs" />
|
||||||
<Compile Include="Extensions\StreamExtensions.cs" />
|
<Compile Include="Extensions\StreamExtensions.cs" />
|
||||||
<Compile Include="HashUtil.cs" />
|
<Compile Include="HashUtil.cs" />
|
||||||
<Compile Include="Http\HttpProvider.cs" />
|
<Compile Include="Http\HttpProvider.cs" />
|
||||||
|
|
|
@ -23,11 +23,11 @@
|
||||||
<link href="/Content/update.css" rel='stylesheet' type='text/css'/>
|
<link href="/Content/update.css" rel='stylesheet' type='text/css'/>
|
||||||
<link href="/Content/overrides.css" rel='stylesheet' type='text/css'/>
|
<link href="/Content/overrides.css" rel='stylesheet' type='text/css'/>
|
||||||
|
|
||||||
<link rel="apple-touch-icon" href="/Content/Images/touch/57.png?v=2"/>
|
<link rel="apple-touch-icon" href="/Content/Images/touch/57.png"/>
|
||||||
<link rel="apple-touch-icon" sizes="72x72" href="/Content/Images/touch/72.png?v=2"/>
|
<link rel="apple-touch-icon" sizes="72x72" href="/Content/Images/touch/72.png"/>
|
||||||
<link rel="apple-touch-icon" sizes="114x114" href="/Content/Images/touch/114.png?v=2"/>
|
<link rel="apple-touch-icon" sizes="114x114" href="/Content/Images/touch/114.png"/>
|
||||||
<link rel="apple-touch-icon" sizes="144x144" href="/Content/Images/touch/144.png?v=2"/>
|
<link rel="apple-touch-icon" sizes="144x144" href="/Content/Images/touch/144.png"/>
|
||||||
<link rel="icon" type="image/ico" href="/Content/Images/favicon.ico?v=2"/>
|
<link rel="icon" type="image/ico" href="/Content/Images/favicon.ico"/>
|
||||||
|
|
||||||
<link rel="alternate" type="text/calendar" title="iCalendar feed for NzbDrone" href="/feed/calendar/NzbDrone.ics" />
|
<link rel="alternate" type="text/calendar" title="iCalendar feed for NzbDrone" href="/feed/calendar/NzbDrone.ics" />
|
||||||
</head>
|
</head>
|
||||||
|
|
Loading…
Reference in New Issue