static resource URLs are now case sensitive.

This commit is contained in:
kay.one 2013-07-23 23:26:10 -07:00
parent e89a35522e
commit 478caf15f8
16 changed files with 127 additions and 84 deletions

View File

@ -58,8 +58,8 @@ module.exports = function (grunt) {
expand: true, expand: true,
src : [ src : [
'UI/Content/base.less', 'UI/Content/base.less',
'UI/Content/Overrides.less', 'UI/Content/overrides.less',
'UI/Series/Series.less', 'UI/Series/series.less',
'UI/AddSeries/addSeries.less', 'UI/AddSeries/addSeries.less',
'UI/Calendar/calendar.less', 'UI/Calendar/calendar.less',
'UI/Cells/cells.less', 'UI/Cells/cells.less',

View File

@ -16,14 +16,14 @@ namespace NzbDrone.Api.Frontend
public string Map(string resourceUrl) public string Map(string resourceUrl)
{ {
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar); var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
path = path.Trim(Path.DirectorySeparatorChar).ToLower(); path = path.Trim(Path.DirectorySeparatorChar);
return Path.Combine(_appFolderInfo.GetAppDataPath(), path); return Path.Combine(_appFolderInfo.GetAppDataPath(), path);
} }
public bool CanHandle(string resourceUrl) public bool CanHandle(string resourceUrl)
{ {
return resourceUrl.StartsWith("/mediacover"); return resourceUrl.StartsWith("/MediaCover");
} }
} }
} }

View File

@ -32,10 +32,10 @@ namespace NzbDrone.Api.Frontend
public string Map(string resourceUrl) public string Map(string resourceUrl)
{ {
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar); var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
path = path.Trim(Path.DirectorySeparatorChar).ToLower(); path = path.Trim(Path.DirectorySeparatorChar);
return Path.Combine(_appFolderInfo.StartUpFolder, "ui", path); return Path.Combine(_appFolderInfo.StartUpFolder, "UI", path);
} }
public bool CanHandle(string resourceUrl) public bool CanHandle(string resourceUrl)

View File

@ -5,6 +5,7 @@ using NLog;
using Nancy; using Nancy;
using Nancy.Responses; using Nancy.Responses;
using NzbDrone.Common; using NzbDrone.Common;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Api.Frontend namespace NzbDrone.Api.Frontend
{ {
@ -20,6 +21,8 @@ namespace NzbDrone.Api.Frontend
private readonly IAddCacheHeaders _addCacheHeaders; private readonly IAddCacheHeaders _addCacheHeaders;
private readonly Logger _logger; private readonly Logger _logger;
private readonly bool _caseSensitive;
public StaticResourceProvider(IDiskProvider diskProvider, public StaticResourceProvider(IDiskProvider diskProvider,
IEnumerable<IMapHttpRequestsToDisk> requestMappers, IEnumerable<IMapHttpRequestsToDisk> requestMappers,
IAddCacheHeaders addCacheHeaders, IAddCacheHeaders addCacheHeaders,
@ -29,11 +32,16 @@ namespace NzbDrone.Api.Frontend
_requestMappers = requestMappers; _requestMappers = requestMappers;
_addCacheHeaders = addCacheHeaders; _addCacheHeaders = addCacheHeaders;
_logger = logger; _logger = logger;
if (!RuntimeInfo.IsProduction)
{
_caseSensitive = true;
}
} }
public Response ProcessStaticResourceRequest(NancyContext context, string workingFolder) public Response ProcessStaticResourceRequest(NancyContext context, string workingFolder)
{ {
var path = context.Request.Url.Path.ToLower(); var path = context.Request.Url.Path;
if (string.IsNullOrWhiteSpace(path)) if (string.IsNullOrWhiteSpace(path))
{ {
@ -46,7 +54,7 @@ namespace NzbDrone.Api.Frontend
{ {
var filePath = mapper.Map(path); var filePath = mapper.Map(path);
if (_diskProvider.FileExists(filePath)) if (_diskProvider.FileExists(filePath, _caseSensitive))
{ {
var response = new StreamResponse(() => File.OpenRead(filePath), MimeTypes.GetMimeType(filePath)); var response = new StreamResponse(() => File.OpenRead(filePath), MimeTypes.GetMimeType(filePath));
_addCacheHeaders.ToResponse(context.Request, response); _addCacheHeaders.ToResponse(context.Request, response);

View File

@ -78,12 +78,12 @@ namespace NzbDrone.Common.Test.CacheTests
{ {
hitCount++; hitCount++;
return null; return null;
}, TimeSpan.FromMilliseconds(200)); }, TimeSpan.FromMilliseconds(300));
Thread.Sleep(10); Thread.Sleep(10);
} }
hitCount.Should().BeInRange(4, 6); hitCount.Should().BeInRange(3, 6);
} }
} }

View File

@ -1,4 +1,6 @@
using System; using System;
using System.Diagnostics;
using System.IO;
using FluentAssertions; using FluentAssertions;
using Moq; using Moq;
using NUnit.Framework; using NUnit.Framework;
@ -69,6 +71,40 @@ namespace NzbDrone.Common.Test
ExceptionVerification.ExpectedWarns(1); ExceptionVerification.ExpectedWarns(1);
} }
[Test]
public void get_actual_casing_for_none_existing_file_should_throw()
{
WindowsOnly();
Assert.Throws<DirectoryNotFoundException>(() => "C:\\InValidFolder\\invalidfile.exe".GetActualCasing());
}
[Test]
public void get_actual_casing_should_return_actual_casing_for_local_file()
{
var path = Process.GetCurrentProcess().MainModule.FileName;
path.ToUpper().GetActualCasing().Should().Be(path);
path.ToLower().GetActualCasing().Should().Be(path);
}
[Test]
public void get_actual_casing_should_return_actual_casing_for_local_dir()
{
var path = Directory.GetCurrentDirectory();
path.ToUpper().GetActualCasing().Should().Be(path);
path.ToLower().GetActualCasing().Should().Be(path);
}
[Test]
[Explicit]
public void get_actual_casing_should_return_original_casing_for_shares()
{
var path = @"\\server\Pool\Apps";
path.GetActualCasing().Should().Be(path);
}
[Test] [Test]
public void AppDataDirectory_path_test() public void AppDataDirectory_path_test()
@ -76,7 +112,6 @@ namespace NzbDrone.Common.Test
GetIAppDirectoryInfo().GetAppDataPath().Should().BeEquivalentTo(@"C:\NzbDrone\"); GetIAppDirectoryInfo().GetAppDataPath().Should().BeEquivalentTo(@"C:\NzbDrone\");
} }
[Test] [Test]
public void Config_path_test() public void Config_path_test()
{ {

View File

@ -14,8 +14,10 @@ namespace NzbDrone.Common
DateTime GetLastFolderWrite(string path); DateTime GetLastFolderWrite(string path);
DateTime GetLastFileWrite(string path); DateTime GetLastFileWrite(string path);
void EnsureFolder(string path); void EnsureFolder(string path);
bool FolderExists(string path, bool caseSensitive);
bool FolderExists(string path); bool FolderExists(string path);
bool FileExists(string path); bool FileExists(string path);
bool FileExists(string path, bool caseSensitive);
string[] GetDirectories(string path); string[] GetDirectories(string path);
string[] GetFiles(string path, SearchOption searchOption); string[] GetFiles(string path, SearchOption searchOption);
long GetFolderSize(string path); long GetFolderSize(string path);
@ -97,17 +99,36 @@ namespace NzbDrone.Common
public virtual bool FolderExists(string path) public virtual bool FolderExists(string path)
{ {
Ensure.That(() => path).IsValidPath(); Ensure.That(() => path).IsValidPath();
return Directory.Exists(path); return Directory.Exists(path);
} }
public virtual bool FolderExists(string path, bool caseSensitive)
{
if (caseSensitive)
{
return FolderExists(path) && path == path.GetActualCasing();
}
return FolderExists(path);
}
public virtual bool FileExists(string path) public virtual bool FileExists(string path)
{ {
Ensure.That(() => path).IsValidPath(); Ensure.That(() => path).IsValidPath();
return File.Exists(path); return File.Exists(path);
} }
public virtual bool FileExists(string path, bool caseSensitive)
{
if (caseSensitive)
{
return FileExists(path) && path == path.GetActualCasing();
}
return FileExists(path);
}
public virtual string[] GetDirectories(string path) public virtual string[] GetDirectories(string path)
{ {
Ensure.That(() => path).IsValidPath(); Ensure.That(() => path).IsValidPath();

View File

@ -41,18 +41,33 @@ namespace NzbDrone.Common
private static string GetProperCapitalization(DirectoryInfo dirInfo) private static string GetProperCapitalization(DirectoryInfo dirInfo)
{ {
var parentDirInfo = dirInfo.Parent; var parentDirInfo = dirInfo.Parent;
if (null == parentDirInfo) if (parentDirInfo == null)
return dirInfo.Name; {
return Path.Combine(GetProperCapitalization(parentDirInfo), //Drive letter
parentDirInfo.GetDirectories(dirInfo.Name)[0].Name); return dirInfo.Name.ToUpper();
}
return Path.Combine(GetProperCapitalization(parentDirInfo), parentDirInfo.GetDirectories(dirInfo.Name)[0].Name);
} }
public static string GetActualCasing(this string filename) public static string GetActualCasing(this string path)
{ {
var fileInfo = new FileInfo(filename); var attributes = File.GetAttributes(path);
if (OsInfo.IsLinux || path.StartsWith("\\"))
{
return path;
}
if ((attributes & FileAttributes.Directory) == FileAttributes.Directory)
{
return GetProperCapitalization(new DirectoryInfo(path));
}
var fileInfo = new FileInfo(path);
DirectoryInfo dirInfo = fileInfo.Directory; DirectoryInfo dirInfo = fileInfo.Directory;
return Path.Combine(GetProperCapitalization(dirInfo), return Path.Combine(GetProperCapitalization(dirInfo), dirInfo.GetFiles(fileInfo.Name)[0].Name);
dirInfo.GetFiles(fileInfo.Name)[0].Name);
} }

View File

@ -37,7 +37,7 @@ namespace NzbDrone.Core.Test.MediaCoverTests
Subject.ConvertToLocalUrls(12, covers); Subject.ConvertToLocalUrls(12, covers);
covers.Single().Url.Should().Be("/mediacover/12/banner.jpg?lastWrite=1234"); covers.Single().Url.Should().Be("/MediaCover/12/banner.jpg?lastWrite=1234");
} }
[Test] [Test]
@ -52,7 +52,7 @@ namespace NzbDrone.Core.Test.MediaCoverTests
Subject.ConvertToLocalUrls(12, covers); Subject.ConvertToLocalUrls(12, covers);
covers.Single().Url.Should().Be("/mediacover/12/banner.jpg"); covers.Single().Url.Should().Be("/MediaCover/12/banner.jpg");
} }
} }

View File

@ -95,7 +95,7 @@ namespace NzbDrone.Core.MediaCover
{ {
var filePath = GetCoverPath(seriesId, mediaCover.CoverType); var filePath = GetCoverPath(seriesId, mediaCover.CoverType);
mediaCover.Url = @"/mediacover/" + seriesId + "/" + mediaCover.CoverType.ToString().ToLower() + ".jpg"; mediaCover.Url = @"/MediaCover/" + seriesId + "/" + mediaCover.CoverType.ToString().ToLower() + ".jpg";
if (_diskProvider.FileExists(filePath)) if (_diskProvider.FileExists(filePath))
{ {

View File

@ -3,7 +3,7 @@ define(
[ [
'backbone', 'backbone',
'AddSeries/RootFolders/Model', 'AddSeries/RootFolders/Model',
'mixins/backbone.signalr.mixin' 'Mixins/backbone.signalr.mixin'
], function (Backbone, RootFolderModel) { ], function (Backbone, RootFolderModel) {
var RootFolderCollection = Backbone.Collection.extend({ var RootFolderCollection = Backbone.Collection.extend({

View File

@ -2,70 +2,34 @@
font-family: 'Open Sans'; font-family: 'Open Sans';
font-style: normal; font-style: normal;
font-weight: 300; font-weight: 300;
src: url('/content/fonts/opensans-light.eot'); src: url('/Content/fonts/OpenSans-Light.eot');
src: local('Open Sans Light'), src: local('Open Sans Light'),
local('OpenSans-Light'), local('OpenSans-Light'),
url('/content/fonts/opensans-light.eot?#iefix') format('embedded-opentype'), url('/Content/fonts/OpenSans-Light.eot?#iefix') format('embedded-opentype'),
url('/content/fonts/opensans-light.woff') format('woff'), url('/Content/fonts/OpenSans-Light.woff') format('woff'),
url('/content/fonts/opensans-light.ttf') format('truetype'); url('/Content/fonts/OpenSans-Light.ttf') format('truetype');
} }
@font-face { @font-face {
font-family: 'Open Sans'; font-family: 'Open Sans';
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
src: url('/content/fonts/opensans-regular.eot'); src: url('/Content/fonts/OpenSans-Regular.eot');
src: local('Open Sans'), src: local('Open Sans'),
local('OpenSans'), local('OpenSans'),
url('/content/fonts/opensans-regular.eot?#iefix') format('embedded-opentype'), url('/Content/fonts/OpenSans-Regular.eot?#iefix') format('embedded-opentype'),
url('/content/fonts/opensans-regular.woff') format('woff'), url('/Content/fonts/OpenSans-Regular.woff') format('woff'),
url('/content/fonts/opensans-regular.ttf') format('truetype') url('/Content/fonts/OpenSans-Regular.ttf') format('truetype')
} }
@font-face { @font-face {
font-family: 'Open Sans'; font-family: 'Open Sans';
font-style: normal; font-style: normal;
font-weight: 600; font-weight: 600;
src: url('/content/fonts/opensans-semibold.eot'); src: url('/Content/fonts/OpenSans-SemiBold.eot');
src: local('Open Sans Semibold'), src: local('Open Sans SemiBold'),
local('OpenSans-Semibold'), local('OpenSans-SemiBold'),
url('/content/fonts/opensans-semibold.eot?#iefix') format('embedded-opentype'), url('/Content/fonts/OpenSans-SemiBold.eot?#iefix') format('embedded-opentype'),
url('/content/fonts/opensans-semibold.woff') format('woff'), url('/Content/fonts/OpenSans-SemiBold.woff') format('woff'),
url('/content/fonts/opensans-semibold.ttf') format('truetype') url('/Content/fonts/OpenSans-SemiBold.ttf') format('truetype')
}
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 300;
src: url('/content/fonts/opensans-lightitalic.eot');
src: local('Open Sans Light Italic'),
local('OpenSansLight-Italic'),
url('/content/fonts/opensans-lightitalic.eot?#iefix') format('embedded-opentype'),
url('/content/fonts/opensans-lightitalic.woff') format('woff'),
url('/content/fonts/opensans-lightitalic.ttf') format('truetype')
}
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 400;
src: url('/content/fonts/opensans-italic.eot');
src: local('Open Sans Italic'),
local('OpenSans-Italic'),
url('/content/fonts/opensans-italic.eot?#iefix') format('embedded-opentype'),
url('/content/fonts/opensans-italic.woff') format('woff'),
url('/content/fonts/opensans-italic.woff.ttf') format('truetype')
}
@font-face {
font-family: 'Open Sans';
font-style: italic;
font-weight: 600;
src: url('/content/fonts/opensans-semibolditalic.eot');
src: local('Open Sans Semibold Italic'),
local('OpenSans-SemiboldItalic'),
url('/content/fonts/opensans-semibolditalic.eot?#iefix') format('embedded-opentype'),
url('/content/fonts/opensans-semibolditalic.woff') format('woff'),
url('/content/fonts/opensans-semibolditalic.ttf') format('truetype')
} }

View File

@ -2,7 +2,7 @@
body { body {
background-color : #1c1c1c; background-color : #1c1c1c;
background-image : url('../content/images/pattern.png'); background-image : url('../Content/Images/pattern.png');
margin-bottom : 100px; margin-bottom : 100px;
p { p {
font-size : 0.9em; font-size : 0.9em;

View File

@ -5,6 +5,6 @@ define(
'handlebars' 'handlebars'
], function (Handlebars) { ], function (Handlebars) {
Handlebars.registerHelper('defaultImg', function () { Handlebars.registerHelper('defaultImg', function () {
return new Handlebars.SafeString('onerror=this.src=\'/content/images/poster-dark.jpg\';'); return new Handlebars.SafeString('onerror=this.src=\'/Content/Images/poster-dark.jpg\';');
}); });
}); });

View File

@ -3,17 +3,17 @@
<head runat="server"> <head runat="server">
<title>NzbDrone</title> <title>NzbDrone</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/> <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<link href="/Content/Bootstrap.css" rel='stylesheet' type='text/css'/> <link href="/Content/bootstrap.css" rel='stylesheet' type='text/css'/>
<link href="/Content/bootstrap.toggle-switch.css" rel='stylesheet' type='text/css'/> <link href="/Content/bootstrap.toggle-switch.css" rel='stylesheet' type='text/css'/>
<link href="/Content/Messenger/messenger.css" rel='stylesheet' type='text/css'/> <link href="/Content/Messenger/messenger.css" rel='stylesheet' type='text/css'/>
<link href="/Content/Messenger/messenger.future.css" rel='stylesheet' type='text/css'/> <link href="/Content/Messenger/messenger.future.css" rel='stylesheet' type='text/css'/>
<link href="/Content/fullcalendar.css" rel='stylesheet' type='text/css'> <link href="/Content/fullcalendar.css" rel='stylesheet' type='text/css'>
<link href="/Content/base.css" rel='stylesheet' type='text/css'/> <link href="/Content/base.css" rel='stylesheet' type='text/css'/>
<link href="/Cells/Cells.css" rel='stylesheet' type='text/css'> <link href="/Cells/cells.css" rel='stylesheet' type='text/css'>
<link href="/Series/Series.css" rel='stylesheet' type='text/css'/> <link href="/Series/series.css" rel='stylesheet' type='text/css'/>
<link href="/Logs/logs.css" rel='stylesheet' type='text/css'/> <link href="/Logs/logs.css" rel='stylesheet' type='text/css'/>
<link href="/Settings/settings.css" rel='stylesheet' type='text/css'/> <link href="/Settings/settings.css" rel='stylesheet' type='text/css'/>
<link href="/AddSeries/Addseries.css" rel='stylesheet' type='text/css'/> <link href="/AddSeries/addSeries.css" rel='stylesheet' type='text/css'/>
<link href="/Calendar/calendar.css" rel='stylesheet' type='text/css'/> <link href="/Calendar/calendar.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'/>

View File

@ -73,7 +73,7 @@ require.config({
backbone: { backbone: {
deps : deps :
[ [
'mixins/backbone.ajax', 'Mixins/backbone.ajax',
'underscore', 'underscore',
'$' '$'
], ],
@ -87,7 +87,7 @@ require.config({
'backbone.deepmodel': { 'backbone.deepmodel': {
deps: deps:
[ [
'mixins/underscore.mixin.deepExtend' 'Mixins/underscore.mixin.deepExtend'
] ]
}, },
@ -96,7 +96,7 @@ require.config({
[ [
'backbone', 'backbone',
'Handlebars/backbone.marionette.templates', 'Handlebars/backbone.marionette.templates',
'mixins/AsNamedView' 'Mixins/AsNamedView'
], ],
exports: 'Marionette', exports: 'Marionette',