From 478caf15f853a0c2968a3fed83aa6f4c3cf5ec9a Mon Sep 17 00:00:00 2001 From: "kay.one" Date: Tue, 23 Jul 2013 23:26:10 -0700 Subject: [PATCH] static resource URLs are now case sensitive. --- Gruntfile.js | 4 +- NzbDrone.Api/Frontend/MediaCoverMapper.cs | 4 +- NzbDrone.Api/Frontend/StaticResourceMapper.cs | 4 +- .../Frontend/StaticResourceProvider.cs | 12 +++- .../CacheTests/CachedFixture.cs | 4 +- NzbDrone.Common.Test/PathExtensionFixture.cs | 37 ++++++++++- NzbDrone.Common/DiskProvider.cs | 25 +++++++- NzbDrone.Common/PathExtensions.cs | 31 ++++++--- .../MediaCoverServiceFixture.cs | 4 +- NzbDrone.Core/MediaCover/MediaCoverService.cs | 2 +- UI/AddSeries/RootFolders/Collection.js | 2 +- UI/Content/font.less | 64 ++++--------------- UI/Content/theme.less | 2 +- UI/Handlebars/Helpers/Html.js | 2 +- UI/Index.html | 8 +-- UI/app.js | 6 +- 16 files changed, 127 insertions(+), 84 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 0647eb59e..00c3753fc 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -58,8 +58,8 @@ module.exports = function (grunt) { expand: true, src : [ 'UI/Content/base.less', - 'UI/Content/Overrides.less', - 'UI/Series/Series.less', + 'UI/Content/overrides.less', + 'UI/Series/series.less', 'UI/AddSeries/addSeries.less', 'UI/Calendar/calendar.less', 'UI/Cells/cells.less', diff --git a/NzbDrone.Api/Frontend/MediaCoverMapper.cs b/NzbDrone.Api/Frontend/MediaCoverMapper.cs index 0a626ead1..fd5626d4d 100644 --- a/NzbDrone.Api/Frontend/MediaCoverMapper.cs +++ b/NzbDrone.Api/Frontend/MediaCoverMapper.cs @@ -16,14 +16,14 @@ public MediaCoverMapper(IAppFolderInfo appFolderInfo) public string Map(string resourceUrl) { var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar); - path = path.Trim(Path.DirectorySeparatorChar).ToLower(); + path = path.Trim(Path.DirectorySeparatorChar); return Path.Combine(_appFolderInfo.GetAppDataPath(), path); } public bool CanHandle(string resourceUrl) { - return resourceUrl.StartsWith("/mediacover"); + return resourceUrl.StartsWith("/MediaCover"); } } } \ No newline at end of file diff --git a/NzbDrone.Api/Frontend/StaticResourceMapper.cs b/NzbDrone.Api/Frontend/StaticResourceMapper.cs index 86353f447..17872b18e 100644 --- a/NzbDrone.Api/Frontend/StaticResourceMapper.cs +++ b/NzbDrone.Api/Frontend/StaticResourceMapper.cs @@ -32,10 +32,10 @@ public StaticResourceMapper(IAppFolderInfo appFolderInfo) public string Map(string resourceUrl) { 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) diff --git a/NzbDrone.Api/Frontend/StaticResourceProvider.cs b/NzbDrone.Api/Frontend/StaticResourceProvider.cs index 33e8a02f7..698bd77d7 100644 --- a/NzbDrone.Api/Frontend/StaticResourceProvider.cs +++ b/NzbDrone.Api/Frontend/StaticResourceProvider.cs @@ -5,6 +5,7 @@ using Nancy; using Nancy.Responses; using NzbDrone.Common; +using NzbDrone.Common.EnvironmentInfo; namespace NzbDrone.Api.Frontend { @@ -20,6 +21,8 @@ public class StaticResourceProvider : IProcessStaticResource private readonly IAddCacheHeaders _addCacheHeaders; private readonly Logger _logger; + private readonly bool _caseSensitive; + public StaticResourceProvider(IDiskProvider diskProvider, IEnumerable requestMappers, IAddCacheHeaders addCacheHeaders, @@ -29,11 +32,16 @@ public StaticResourceProvider(IDiskProvider diskProvider, _requestMappers = requestMappers; _addCacheHeaders = addCacheHeaders; _logger = logger; + + if (!RuntimeInfo.IsProduction) + { + _caseSensitive = true; + } } public Response ProcessStaticResourceRequest(NancyContext context, string workingFolder) { - var path = context.Request.Url.Path.ToLower(); + var path = context.Request.Url.Path; if (string.IsNullOrWhiteSpace(path)) { @@ -46,7 +54,7 @@ public Response ProcessStaticResourceRequest(NancyContext context, string workin { var filePath = mapper.Map(path); - if (_diskProvider.FileExists(filePath)) + if (_diskProvider.FileExists(filePath, _caseSensitive)) { var response = new StreamResponse(() => File.OpenRead(filePath), MimeTypes.GetMimeType(filePath)); _addCacheHeaders.ToResponse(context.Request, response); diff --git a/NzbDrone.Common.Test/CacheTests/CachedFixture.cs b/NzbDrone.Common.Test/CacheTests/CachedFixture.cs index 647e4fb96..4a1cf9021 100644 --- a/NzbDrone.Common.Test/CacheTests/CachedFixture.cs +++ b/NzbDrone.Common.Test/CacheTests/CachedFixture.cs @@ -78,12 +78,12 @@ public void should_honor_ttl() { hitCount++; return null; - }, TimeSpan.FromMilliseconds(200)); + }, TimeSpan.FromMilliseconds(300)); Thread.Sleep(10); } - hitCount.Should().BeInRange(4, 6); + hitCount.Should().BeInRange(3, 6); } } diff --git a/NzbDrone.Common.Test/PathExtensionFixture.cs b/NzbDrone.Common.Test/PathExtensionFixture.cs index 463a4d2cc..e20d15b20 100644 --- a/NzbDrone.Common.Test/PathExtensionFixture.cs +++ b/NzbDrone.Common.Test/PathExtensionFixture.cs @@ -1,4 +1,6 @@ using System; +using System.Diagnostics; +using System.IO; using FluentAssertions; using Moq; using NUnit.Framework; @@ -69,6 +71,40 @@ public void normalize_path_exception_null() ExceptionVerification.ExpectedWarns(1); } + [Test] + public void get_actual_casing_for_none_existing_file_should_throw() + { + WindowsOnly(); + Assert.Throws(() => "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] public void AppDataDirectory_path_test() @@ -76,7 +112,6 @@ public void AppDataDirectory_path_test() GetIAppDirectoryInfo().GetAppDataPath().Should().BeEquivalentTo(@"C:\NzbDrone\"); } - [Test] public void Config_path_test() { diff --git a/NzbDrone.Common/DiskProvider.cs b/NzbDrone.Common/DiskProvider.cs index ef8b1d33c..8de8bd79e 100644 --- a/NzbDrone.Common/DiskProvider.cs +++ b/NzbDrone.Common/DiskProvider.cs @@ -14,8 +14,10 @@ public interface IDiskProvider DateTime GetLastFolderWrite(string path); DateTime GetLastFileWrite(string path); void EnsureFolder(string path); + bool FolderExists(string path, bool caseSensitive); bool FolderExists(string path); bool FileExists(string path); + bool FileExists(string path, bool caseSensitive); string[] GetDirectories(string path); string[] GetFiles(string path, SearchOption searchOption); long GetFolderSize(string path); @@ -97,17 +99,36 @@ public virtual void EnsureFolder(string path) public virtual bool FolderExists(string path) { Ensure.That(() => path).IsValidPath(); - 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) { Ensure.That(() => path).IsValidPath(); - 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) { Ensure.That(() => path).IsValidPath(); diff --git a/NzbDrone.Common/PathExtensions.cs b/NzbDrone.Common/PathExtensions.cs index 2949111db..bd5ae4805 100644 --- a/NzbDrone.Common/PathExtensions.cs +++ b/NzbDrone.Common/PathExtensions.cs @@ -41,18 +41,33 @@ public static bool ContainsInvalidPathChars(this string text) private static string GetProperCapitalization(DirectoryInfo dirInfo) { var parentDirInfo = dirInfo.Parent; - if (null == parentDirInfo) - return dirInfo.Name; - return Path.Combine(GetProperCapitalization(parentDirInfo), - parentDirInfo.GetDirectories(dirInfo.Name)[0].Name); + if (parentDirInfo == null) + { + //Drive letter + 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; - return Path.Combine(GetProperCapitalization(dirInfo), - dirInfo.GetFiles(fileInfo.Name)[0].Name); + return Path.Combine(GetProperCapitalization(dirInfo), dirInfo.GetFiles(fileInfo.Name)[0].Name); } diff --git a/NzbDrone.Core.Test/MediaCoverTests/MediaCoverServiceFixture.cs b/NzbDrone.Core.Test/MediaCoverTests/MediaCoverServiceFixture.cs index 08e7645c3..e2e59b2ba 100644 --- a/NzbDrone.Core.Test/MediaCoverTests/MediaCoverServiceFixture.cs +++ b/NzbDrone.Core.Test/MediaCoverTests/MediaCoverServiceFixture.cs @@ -37,7 +37,7 @@ public void should_convert_trakts_urls_to_local() 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] @@ -52,7 +52,7 @@ public void should_convert_trakts_urls_to_local_without_time_if_file_doesnt_exis Subject.ConvertToLocalUrls(12, covers); - covers.Single().Url.Should().Be("/mediacover/12/banner.jpg"); + covers.Single().Url.Should().Be("/MediaCover/12/banner.jpg"); } } diff --git a/NzbDrone.Core/MediaCover/MediaCoverService.cs b/NzbDrone.Core/MediaCover/MediaCoverService.cs index ccfa8dc89..4bb237aa4 100644 --- a/NzbDrone.Core/MediaCover/MediaCoverService.cs +++ b/NzbDrone.Core/MediaCover/MediaCoverService.cs @@ -95,7 +95,7 @@ public void ConvertToLocalUrls(int seriesId, IEnumerable covers) { 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)) { diff --git a/UI/AddSeries/RootFolders/Collection.js b/UI/AddSeries/RootFolders/Collection.js index c03a30753..fdac81123 100644 --- a/UI/AddSeries/RootFolders/Collection.js +++ b/UI/AddSeries/RootFolders/Collection.js @@ -3,7 +3,7 @@ define( [ 'backbone', 'AddSeries/RootFolders/Model', - 'mixins/backbone.signalr.mixin' + 'Mixins/backbone.signalr.mixin' ], function (Backbone, RootFolderModel) { var RootFolderCollection = Backbone.Collection.extend({ diff --git a/UI/Content/font.less b/UI/Content/font.less index 7e8e65afc..a4657ee47 100644 --- a/UI/Content/font.less +++ b/UI/Content/font.less @@ -2,70 +2,34 @@ font-family: 'Open Sans'; font-style: normal; font-weight: 300; - src: url('/content/fonts/opensans-light.eot'); + src: url('/Content/fonts/OpenSans-Light.eot'); src: local('Open Sans Light'), local('OpenSans-Light'), - url('/content/fonts/opensans-light.eot?#iefix') format('embedded-opentype'), - url('/content/fonts/opensans-light.woff') format('woff'), - url('/content/fonts/opensans-light.ttf') format('truetype'); + url('/Content/fonts/OpenSans-Light.eot?#iefix') format('embedded-opentype'), + url('/Content/fonts/OpenSans-Light.woff') format('woff'), + url('/Content/fonts/OpenSans-Light.ttf') format('truetype'); } @font-face { font-family: 'Open Sans'; font-style: normal; font-weight: 400; - src: url('/content/fonts/opensans-regular.eot'); + src: url('/Content/fonts/OpenSans-Regular.eot'); src: local('Open Sans'), local('OpenSans'), - url('/content/fonts/opensans-regular.eot?#iefix') format('embedded-opentype'), - url('/content/fonts/opensans-regular.woff') format('woff'), - url('/content/fonts/opensans-regular.ttf') format('truetype') + url('/Content/fonts/OpenSans-Regular.eot?#iefix') format('embedded-opentype'), + url('/Content/fonts/OpenSans-Regular.woff') format('woff'), + url('/Content/fonts/OpenSans-Regular.ttf') format('truetype') } @font-face { font-family: 'Open Sans'; font-style: normal; font-weight: 600; - src: url('/content/fonts/opensans-semibold.eot'); - src: local('Open Sans Semibold'), - local('OpenSans-Semibold'), - url('/content/fonts/opensans-semibold.eot?#iefix') format('embedded-opentype'), - url('/content/fonts/opensans-semibold.woff') format('woff'), - 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') + src: url('/Content/fonts/OpenSans-SemiBold.eot'); + src: local('Open Sans SemiBold'), + local('OpenSans-SemiBold'), + url('/Content/fonts/OpenSans-SemiBold.eot?#iefix') format('embedded-opentype'), + url('/Content/fonts/OpenSans-SemiBold.woff') format('woff'), + url('/Content/fonts/OpenSans-SemiBold.ttf') format('truetype') } diff --git a/UI/Content/theme.less b/UI/Content/theme.less index 6e199c47c..2f3dfe92e 100644 --- a/UI/Content/theme.less +++ b/UI/Content/theme.less @@ -2,7 +2,7 @@ body { background-color : #1c1c1c; - background-image : url('../content/images/pattern.png'); + background-image : url('../Content/Images/pattern.png'); margin-bottom : 100px; p { font-size : 0.9em; diff --git a/UI/Handlebars/Helpers/Html.js b/UI/Handlebars/Helpers/Html.js index e2c0572fa..21534b9ac 100644 --- a/UI/Handlebars/Helpers/Html.js +++ b/UI/Handlebars/Helpers/Html.js @@ -5,6 +5,6 @@ define( 'handlebars' ], function (Handlebars) { 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\';'); }); }); diff --git a/UI/Index.html b/UI/Index.html index 64a77d0c8..9bf024492 100644 --- a/UI/Index.html +++ b/UI/Index.html @@ -3,17 +3,17 @@ NzbDrone - + - - + + - + diff --git a/UI/app.js b/UI/app.js index e33f7cd7c..26f7a129b 100644 --- a/UI/app.js +++ b/UI/app.js @@ -73,7 +73,7 @@ require.config({ backbone: { deps : [ - 'mixins/backbone.ajax', + 'Mixins/backbone.ajax', 'underscore', '$' ], @@ -87,7 +87,7 @@ require.config({ 'backbone.deepmodel': { deps: [ - 'mixins/underscore.mixin.deepExtend' + 'Mixins/underscore.mixin.deepExtend' ] }, @@ -96,7 +96,7 @@ require.config({ [ 'backbone', 'Handlebars/backbone.marionette.templates', - 'mixins/AsNamedView' + 'Mixins/AsNamedView' ], exports: 'Marionette',