diff --git a/debian/copyright b/debian/copyright index 466d37fce7..666d9bf2d0 100755 --- a/debian/copyright +++ b/debian/copyright @@ -1,24 +1,24 @@ -Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: nzbdrone -Source: https://github.com/Sonarr/Sonarr - -Files: * -Copyright: 2010-2016 Sonarr - -License: GPL-3.0+ - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - . - This package is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - . - You should have received a copy of the GNU General Public License - along with this program. If not, see . - . - On Debian systems, the complete text of the GNU General - Public License version 3 can be found in "/usr/share/common-licenses/GPL-3". +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: nzbdrone +Source: https://github.com/Sonarr/Sonarr + +Files: * +Copyright: 2010-2016 Sonarr + +License: GPL-3.0+ + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + . + This package is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + . + You should have received a copy of the GNU General Public License + along with this program. If not, see . + . + On Debian systems, the complete text of the GNU General + Public License version 3 can be found in "/usr/share/common-licenses/GPL-3". diff --git a/debian/install b/debian/install index 106b06a9b5..1810b91856 100755 --- a/debian/install +++ b/debian/install @@ -1 +1 @@ -nzbdrone_bin/* opt/NzbDrone +nzbdrone_bin/* opt/NzbDrone diff --git a/src/NzbDrone.Api/Extensions/Pipelines/RequestLoggingPipeline.cs b/src/NzbDrone.Api/Extensions/Pipelines/RequestLoggingPipeline.cs index 918d8db5e0..de5d707cd1 100644 --- a/src/NzbDrone.Api/Extensions/Pipelines/RequestLoggingPipeline.cs +++ b/src/NzbDrone.Api/Extensions/Pipelines/RequestLoggingPipeline.cs @@ -1,84 +1,84 @@ -using System; -using System.Threading; -using Nancy; -using Nancy.Bootstrapper; -using NLog; -using NzbDrone.Api.ErrorManagement; -using NzbDrone.Common.Extensions; - -namespace NzbDrone.Api.Extensions.Pipelines -{ - public class RequestLoggingPipeline : IRegisterNancyPipeline - { - private static readonly Logger _loggerHttp = LogManager.GetLogger("Http"); - private static readonly Logger _loggerApi = LogManager.GetLogger("Api"); - - private static int _requestSequenceID; - - private readonly NzbDroneErrorPipeline _errorPipeline; - - public RequestLoggingPipeline(NzbDroneErrorPipeline errorPipeline) - { - _errorPipeline = errorPipeline; - } - - public int Order => 100; - - public void Register(IPipelines pipelines) - { - pipelines.BeforeRequest.AddItemToStartOfPipeline(LogStart); - pipelines.AfterRequest.AddItemToEndOfPipeline(LogEnd); - pipelines.OnError.AddItemToEndOfPipeline(LogError); - } - - private Response LogStart(NancyContext context) - { - var id = Interlocked.Increment(ref _requestSequenceID); - - context.Items["ApiRequestSequenceID"] = id; - context.Items["ApiRequestStartTime"] = DateTime.UtcNow; - - var reqPath = GetRequestPathAndQuery(context.Request); - - _loggerHttp.Trace("Req: {0} [{1}] {2}", id, context.Request.Method, reqPath); - - return null; - } - - private void LogEnd(NancyContext context) - { - var id = (int)context.Items["ApiRequestSequenceID"]; - var startTime = (DateTime)context.Items["ApiRequestStartTime"]; - - var endTime = DateTime.UtcNow; - var duration = endTime - startTime; - - var reqPath = GetRequestPathAndQuery(context.Request); - - _loggerHttp.Trace("Res: {0} [{1}] {2}: {3}.{4} ({5} ms)", id, context.Request.Method, reqPath, (int)context.Response.StatusCode, context.Response.StatusCode, (int)duration.TotalMilliseconds); - - if (context.Request.IsApiRequest()) - { - _loggerApi.Debug("[{0}] {1}: {2}.{3} ({4} ms)", context.Request.Method, reqPath, (int)context.Response.StatusCode, context.Response.StatusCode, (int)duration.TotalMilliseconds); - } - } - - private Response LogError(NancyContext context, Exception exception) - { - var response = _errorPipeline.HandleException(context, exception); - context.Response = response; - LogEnd(context); - context.Response = null; - return response; - } - - private static string GetRequestPathAndQuery(Request request) - { - if (request.Url.Query.IsNotNullOrWhiteSpace()) - { - return string.Concat(request.Url.Path, request.Url.Query); - } - return request.Url.Path; - } - } +using System; +using System.Threading; +using Nancy; +using Nancy.Bootstrapper; +using NLog; +using NzbDrone.Api.ErrorManagement; +using NzbDrone.Common.Extensions; + +namespace NzbDrone.Api.Extensions.Pipelines +{ + public class RequestLoggingPipeline : IRegisterNancyPipeline + { + private static readonly Logger _loggerHttp = LogManager.GetLogger("Http"); + private static readonly Logger _loggerApi = LogManager.GetLogger("Api"); + + private static int _requestSequenceID; + + private readonly NzbDroneErrorPipeline _errorPipeline; + + public RequestLoggingPipeline(NzbDroneErrorPipeline errorPipeline) + { + _errorPipeline = errorPipeline; + } + + public int Order => 100; + + public void Register(IPipelines pipelines) + { + pipelines.BeforeRequest.AddItemToStartOfPipeline(LogStart); + pipelines.AfterRequest.AddItemToEndOfPipeline(LogEnd); + pipelines.OnError.AddItemToEndOfPipeline(LogError); + } + + private Response LogStart(NancyContext context) + { + var id = Interlocked.Increment(ref _requestSequenceID); + + context.Items["ApiRequestSequenceID"] = id; + context.Items["ApiRequestStartTime"] = DateTime.UtcNow; + + var reqPath = GetRequestPathAndQuery(context.Request); + + _loggerHttp.Trace("Req: {0} [{1}] {2}", id, context.Request.Method, reqPath); + + return null; + } + + private void LogEnd(NancyContext context) + { + var id = (int)context.Items["ApiRequestSequenceID"]; + var startTime = (DateTime)context.Items["ApiRequestStartTime"]; + + var endTime = DateTime.UtcNow; + var duration = endTime - startTime; + + var reqPath = GetRequestPathAndQuery(context.Request); + + _loggerHttp.Trace("Res: {0} [{1}] {2}: {3}.{4} ({5} ms)", id, context.Request.Method, reqPath, (int)context.Response.StatusCode, context.Response.StatusCode, (int)duration.TotalMilliseconds); + + if (context.Request.IsApiRequest()) + { + _loggerApi.Debug("[{0}] {1}: {2}.{3} ({4} ms)", context.Request.Method, reqPath, (int)context.Response.StatusCode, context.Response.StatusCode, (int)duration.TotalMilliseconds); + } + } + + private Response LogError(NancyContext context, Exception exception) + { + var response = _errorPipeline.HandleException(context, exception); + context.Response = response; + LogEnd(context); + context.Response = null; + return response; + } + + private static string GetRequestPathAndQuery(Request request) + { + if (request.Url.Query.IsNotNullOrWhiteSpace()) + { + return string.Concat(request.Url.Path, request.Url.Query); + } + return request.Url.Path; + } + } } \ No newline at end of file diff --git a/src/NzbDrone.Api/NetImport/ImportExclusionsModule.cs b/src/NzbDrone.Api/NetImport/ImportExclusionsModule.cs index 4615c65f72..9dfc03e552 100644 --- a/src/NzbDrone.Api/NetImport/ImportExclusionsModule.cs +++ b/src/NzbDrone.Api/NetImport/ImportExclusionsModule.cs @@ -1,45 +1,45 @@ -using System.Collections.Generic; -using FluentValidation; -using NzbDrone.Api.ClientSchema; -using NzbDrone.Core.NetImport; -using NzbDrone.Core.NetImport.ImportExclusions; -using NzbDrone.Core.Validation.Paths; - -namespace NzbDrone.Api.NetImport -{ - public class ImportExclusionsModule : NzbDroneRestModule - { - private readonly IImportExclusionsService _exclusionService; - - public ImportExclusionsModule(NetImportFactory netImportFactory, IImportExclusionsService exclusionService) : base("exclusions") - { - _exclusionService = exclusionService; - GetResourceAll = GetAll; - CreateResource = AddExclusion; - DeleteResource = RemoveExclusion; - GetResourceById = GetById; - } - - public List GetAll() - { - return _exclusionService.GetAllExclusions().ToResource(); - } - - public ImportExclusionsResource GetById(int id) - { - return _exclusionService.GetById(id).ToResource(); - } - - public int AddExclusion(ImportExclusionsResource exclusionResource) - { - var model = exclusionResource.ToModel(); - - return _exclusionService.AddExclusion(model).Id; - } - - public void RemoveExclusion (int id) - { - _exclusionService.RemoveExclusion(new ImportExclusion { Id = id }); - } - } -} +using System.Collections.Generic; +using FluentValidation; +using NzbDrone.Api.ClientSchema; +using NzbDrone.Core.NetImport; +using NzbDrone.Core.NetImport.ImportExclusions; +using NzbDrone.Core.Validation.Paths; + +namespace NzbDrone.Api.NetImport +{ + public class ImportExclusionsModule : NzbDroneRestModule + { + private readonly IImportExclusionsService _exclusionService; + + public ImportExclusionsModule(NetImportFactory netImportFactory, IImportExclusionsService exclusionService) : base("exclusions") + { + _exclusionService = exclusionService; + GetResourceAll = GetAll; + CreateResource = AddExclusion; + DeleteResource = RemoveExclusion; + GetResourceById = GetById; + } + + public List GetAll() + { + return _exclusionService.GetAllExclusions().ToResource(); + } + + public ImportExclusionsResource GetById(int id) + { + return _exclusionService.GetById(id).ToResource(); + } + + public int AddExclusion(ImportExclusionsResource exclusionResource) + { + var model = exclusionResource.ToModel(); + + return _exclusionService.AddExclusion(model).Id; + } + + public void RemoveExclusion (int id) + { + _exclusionService.RemoveExclusion(new ImportExclusion { Id = id }); + } + } +} diff --git a/src/NzbDrone.Api/NetImport/ImportExclusionsResource.cs b/src/NzbDrone.Api/NetImport/ImportExclusionsResource.cs index 0e5f266787..e3c598e0ad 100644 --- a/src/NzbDrone.Api/NetImport/ImportExclusionsResource.cs +++ b/src/NzbDrone.Api/NetImport/ImportExclusionsResource.cs @@ -1,46 +1,46 @@ -using System.Collections.Generic; -using System.Linq; -using NzbDrone.Core.NetImport; -using NzbDrone.Core.Movies; - -namespace NzbDrone.Api.NetImport -{ - public class ImportExclusionsResource : ProviderResource - { - //public int Id { get; set; } - public int TmdbId { get; set; } - public string MovieTitle { get; set; } - public int MovieYear { get; set; } - } - - public static class ImportExclusionsResourceMapper - { - public static ImportExclusionsResource ToResource(this Core.NetImport.ImportExclusions.ImportExclusion model) - { - if (model == null) return null; - - return new ImportExclusionsResource - { - Id = model.Id, - TmdbId = model.TmdbId, - MovieTitle = model.MovieTitle, - MovieYear = model.MovieYear - }; - } - - public static List ToResource(this IEnumerable exclusions) - { - return exclusions.Select(ToResource).ToList(); - } - - public static Core.NetImport.ImportExclusions.ImportExclusion ToModel(this ImportExclusionsResource resource) - { - return new Core.NetImport.ImportExclusions.ImportExclusion - { - TmdbId = resource.TmdbId, - MovieTitle = resource.MovieTitle, - MovieYear = resource.MovieYear - }; - } - } -} +using System.Collections.Generic; +using System.Linq; +using NzbDrone.Core.NetImport; +using NzbDrone.Core.Movies; + +namespace NzbDrone.Api.NetImport +{ + public class ImportExclusionsResource : ProviderResource + { + //public int Id { get; set; } + public int TmdbId { get; set; } + public string MovieTitle { get; set; } + public int MovieYear { get; set; } + } + + public static class ImportExclusionsResourceMapper + { + public static ImportExclusionsResource ToResource(this Core.NetImport.ImportExclusions.ImportExclusion model) + { + if (model == null) return null; + + return new ImportExclusionsResource + { + Id = model.Id, + TmdbId = model.TmdbId, + MovieTitle = model.MovieTitle, + MovieYear = model.MovieYear + }; + } + + public static List ToResource(this IEnumerable exclusions) + { + return exclusions.Select(ToResource).ToList(); + } + + public static Core.NetImport.ImportExclusions.ImportExclusion ToModel(this ImportExclusionsResource resource) + { + return new Core.NetImport.ImportExclusions.ImportExclusion + { + TmdbId = resource.TmdbId, + MovieTitle = resource.MovieTitle, + MovieYear = resource.MovieYear + }; + } + } +} diff --git a/src/NzbDrone.Api/packages.config b/src/NzbDrone.Api/packages.config index 1fe9f88c12..f86e3777dc 100644 --- a/src/NzbDrone.Api/packages.config +++ b/src/NzbDrone.Api/packages.config @@ -1,10 +1,10 @@ - - - - - - - - - + + + + + + + + + \ No newline at end of file diff --git a/src/NzbDrone.App.Test/NzbDrone.Host.Test.csproj b/src/NzbDrone.App.Test/NzbDrone.Host.Test.csproj index 9d1ba4206b..c352e9c808 100644 --- a/src/NzbDrone.App.Test/NzbDrone.Host.Test.csproj +++ b/src/NzbDrone.App.Test/NzbDrone.Host.Test.csproj @@ -1,134 +1,134 @@ - - - - Debug - x86 - 8.0.30703 - 2.0 - {C0EA1A40-91AD-4EEB-BD16-2DDDEBD20AE5} - Library - Properties - NzbDrone.App.Test - NzbDrone.App.Test - v4.0 - 512 - ..\ - true - - - true - bin\x86\Debug\ - DEBUG;TRACE - full - x86 - prompt - MinimumRecommendedRules.ruleset - 4 - false - - - bin\x86\Release\ - TRACE - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset - 4 - - - - ..\packages\NBuilder.4.0.0\lib\net40\FizzWare.NBuilder.dll - True - - - ..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.dll - True - - - ..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.Core.dll - True - - - - ..\packages\NLog.4.5.0-rc06\lib\net40-client\NLog.dll - - - ..\packages\NUnit.3.5.0\lib\net40\nunit.framework.dll - True - - - - - - - - - - - - - ..\packages\Moq.4.0.10827\lib\NET40\Moq.dll - - - - - - - - - - - App.config - - - - - - {F2BE0FDF-6E47-4827-A420-DD4EF82407F8} - NzbDrone.Common - - - {FF5EE3B6-913B-47CE-9CEB-11C51B4E1205} - NzbDrone.Core - - - {95C11A9E-56ED-456A-8447-2C89C1139266} - NzbDrone.Host - - - {CADDFCE0-7509-4430-8364-2074E1EEFCA2} - NzbDrone.Test.Common - - - - - sqlite3.dll - Always - - - - - - - - - - + + + + Debug + x86 + 8.0.30703 + 2.0 + {C0EA1A40-91AD-4EEB-BD16-2DDDEBD20AE5} + Library + Properties + NzbDrone.App.Test + NzbDrone.App.Test + v4.0 + 512 + ..\ + true + + + true + bin\x86\Debug\ + DEBUG;TRACE + full + x86 + prompt + MinimumRecommendedRules.ruleset + 4 + false + + + bin\x86\Release\ + TRACE + true + pdbonly + x86 + prompt + MinimumRecommendedRules.ruleset + 4 + + + + ..\packages\NBuilder.4.0.0\lib\net40\FizzWare.NBuilder.dll + True + + + ..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.dll + True + + + ..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.Core.dll + True + + + + ..\packages\NLog.4.5.0-rc06\lib\net40-client\NLog.dll + + + ..\packages\NUnit.3.5.0\lib\net40\nunit.framework.dll + True + + + + + + + + + + + + + ..\packages\Moq.4.0.10827\lib\NET40\Moq.dll + + + + + + + + + + + App.config + + + + + + {F2BE0FDF-6E47-4827-A420-DD4EF82407F8} + NzbDrone.Common + + + {FF5EE3B6-913B-47CE-9CEB-11C51B4E1205} + NzbDrone.Core + + + {95C11A9E-56ED-456A-8447-2C89C1139266} + NzbDrone.Host + + + {CADDFCE0-7509-4430-8364-2074E1EEFCA2} + NzbDrone.Test.Common + + + + + sqlite3.dll + Always + + + + + + + + + + xcopy /s /y "$(SolutionDir)\..\_output\NzbDrone.Mono.*" "$(TargetDir)" xcopy /s /y "$(SolutionDir)\..\_output\NzbDrone.Windows.*" "$(TargetDir)" - + cp -rv $(SolutionDir)\..\_output\NzbDrone.Mono.* $(TargetDir) cp -rv $(SolutionDir)\..\_output\NzbDrone.Windows.* $(TargetDir) - - + + - + --> + diff --git a/src/NzbDrone.App.Test/packages.config b/src/NzbDrone.App.Test/packages.config index 36c0e6d75e..6f97f16cea 100644 --- a/src/NzbDrone.App.Test/packages.config +++ b/src/NzbDrone.App.Test/packages.config @@ -1,8 +1,8 @@ - - - - - - - + + + + + + + \ No newline at end of file diff --git a/src/NzbDrone.Automation.Test/NzbDrone.Automation.Test.csproj b/src/NzbDrone.Automation.Test/NzbDrone.Automation.Test.csproj index 7ef094c284..b0981fdd19 100644 --- a/src/NzbDrone.Automation.Test/NzbDrone.Automation.Test.csproj +++ b/src/NzbDrone.Automation.Test/NzbDrone.Automation.Test.csproj @@ -1,109 +1,109 @@ - - - - - Debug - x86 - {CC26800D-F67E-464B-88DE-8EB1A0C227A3} - Library - Properties - NzbDrone.Automation.Test - NzbDrone.Automation.Test - v4.0 - 512 - ..\ - true - 12.0.0 - 2.0 - - - true - bin\x86\Debug\ - DEBUG;TRACE - full - x86 - prompt - MinimumRecommendedRules.ruleset - 4 - false - - - bin\x86\Release\ - TRACE - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset - 4 - - - - ..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.dll - True - - - ..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.Core.dll - True - - - ..\packages\NLog.4.5.0-rc06\lib\net40-client\NLog.dll - - - ..\packages\NUnit.3.5.0\lib\net40\nunit.framework.dll - True - - - - - - - - - - - - - - - ..\packages\Selenium.WebDriver.3.0.1\lib\net40\WebDriver.dll - True - - - ..\packages\Selenium.Support.3.0.1\lib\net40\WebDriver.Support.dll - True - - - - - - - - - - - - {F2BE0FDF-6E47-4827-A420-DD4EF82407F8} - NzbDrone.Common - - - {CADDFCE0-7509-4430-8364-2074E1EEFCA2} - NzbDrone.Test.Common - - - - - - - - - - + + + + + Debug + x86 + {CC26800D-F67E-464B-88DE-8EB1A0C227A3} + Library + Properties + NzbDrone.Automation.Test + NzbDrone.Automation.Test + v4.0 + 512 + ..\ + true + 12.0.0 + 2.0 + + + true + bin\x86\Debug\ + DEBUG;TRACE + full + x86 + prompt + MinimumRecommendedRules.ruleset + 4 + false + + + bin\x86\Release\ + TRACE + true + pdbonly + x86 + prompt + MinimumRecommendedRules.ruleset + 4 + + + + ..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.dll + True + + + ..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.Core.dll + True + + + ..\packages\NLog.4.5.0-rc06\lib\net40-client\NLog.dll + + + ..\packages\NUnit.3.5.0\lib\net40\nunit.framework.dll + True + + + + + + + + + + + + + + + ..\packages\Selenium.WebDriver.3.0.1\lib\net40\WebDriver.dll + True + + + ..\packages\Selenium.Support.3.0.1\lib\net40\WebDriver.Support.dll + True + + + + + + + + + + + + {F2BE0FDF-6E47-4827-A420-DD4EF82407F8} + NzbDrone.Common + + + {CADDFCE0-7509-4430-8364-2074E1EEFCA2} + NzbDrone.Test.Common + + + + + + + + + + + --> \ No newline at end of file diff --git a/src/NzbDrone.Automation.Test/packages.config b/src/NzbDrone.Automation.Test/packages.config index 77169e153b..b0b2198406 100644 --- a/src/NzbDrone.Automation.Test/packages.config +++ b/src/NzbDrone.Automation.Test/packages.config @@ -1,8 +1,8 @@ - - - - - - - + + + + + + + \ No newline at end of file diff --git a/src/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj b/src/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj index 90cd11e29b..44b8172d35 100644 --- a/src/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj +++ b/src/NzbDrone.Common.Test/NzbDrone.Common.Test.csproj @@ -1,165 +1,165 @@ - - - - Debug - x86 - 8.0.30703 - 2.0 - {BEC74619-DDBB-4FBA-B517-D3E20AFC9997} - Library - Properties - NzbDrone.Common.Test - NzbDrone.Common.Test - v4.0 - 512 - ..\ - true - - - true - bin\x86\Debug\ - DEBUG;TRACE - full - x86 - prompt - MinimumRecommendedRules.ruleset - 4 - false - - - bin\x86\Release\ - TRACE - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset - 4 - - - - ..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.dll - True - - - ..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.Core.dll - True - - - ..\packages\NLog.4.5.0-rc06\lib\net40-client\NLog.dll - - - ..\packages\NUnit.3.5.0\lib\net40\nunit.framework.dll - True - - - - - - - - - - - - - - - ..\packages\Moq.4.0.10827\lib\NET40\Moq.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - App.config - - - - - - {F2BE0FDF-6E47-4827-A420-DD4EF82407F8} - NzbDrone.Common - - - {FF5EE3B6-913B-47CE-9CEB-11C51B4E1205} - NzbDrone.Core - - - {95C11A9E-56ED-456A-8447-2C89C1139266} - NzbDrone.Host - - - {15ad7579-a314-4626-b556-663f51d97cd1} - NzbDrone.Mono - - - {911284d3-f130-459e-836c-2430b6fbf21d} - NzbDrone.Windows - - - {D12F7F2F-8A3C-415F-88FA-6DD061A84869} - NzbDrone - - - {CADDFCE0-7509-4430-8364-2074E1EEFCA2} - NzbDrone.Test.Common - - - {FAFB5948-A222-4CF6-AD14-026BE7564802} - NzbDrone.Test.Dummy - - - - - - - - - - - xcopy /s /y "$(SolutionDir)\ExternalModules\CurlSharp\libs\i386\*" "$(TargetDir)" - + + + + Debug + x86 + 8.0.30703 + 2.0 + {BEC74619-DDBB-4FBA-B517-D3E20AFC9997} + Library + Properties + NzbDrone.Common.Test + NzbDrone.Common.Test + v4.0 + 512 + ..\ + true + + + true + bin\x86\Debug\ + DEBUG;TRACE + full + x86 + prompt + MinimumRecommendedRules.ruleset + 4 + false + + + bin\x86\Release\ + TRACE + true + pdbonly + x86 + prompt + MinimumRecommendedRules.ruleset + 4 + + + + ..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.dll + True + + + ..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.Core.dll + True + + + ..\packages\NLog.4.5.0-rc06\lib\net40-client\NLog.dll + + + ..\packages\NUnit.3.5.0\lib\net40\nunit.framework.dll + True + + + + + + + + + + + + + + + ..\packages\Moq.4.0.10827\lib\NET40\Moq.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + App.config + + + + + + {F2BE0FDF-6E47-4827-A420-DD4EF82407F8} + NzbDrone.Common + + + {FF5EE3B6-913B-47CE-9CEB-11C51B4E1205} + NzbDrone.Core + + + {95C11A9E-56ED-456A-8447-2C89C1139266} + NzbDrone.Host + + + {15ad7579-a314-4626-b556-663f51d97cd1} + NzbDrone.Mono + + + {911284d3-f130-459e-836c-2430b6fbf21d} + NzbDrone.Windows + + + {D12F7F2F-8A3C-415F-88FA-6DD061A84869} + NzbDrone + + + {CADDFCE0-7509-4430-8364-2074E1EEFCA2} + NzbDrone.Test.Common + + + {FAFB5948-A222-4CF6-AD14-026BE7564802} + NzbDrone.Test.Dummy + + + + + + + + + + + xcopy /s /y "$(SolutionDir)\ExternalModules\CurlSharp\libs\i386\*" "$(TargetDir)" + + --> \ No newline at end of file diff --git a/src/NzbDrone.Common.Test/packages.config b/src/NzbDrone.Common.Test/packages.config index 4103044455..7fd9e8863e 100644 --- a/src/NzbDrone.Common.Test/packages.config +++ b/src/NzbDrone.Common.Test/packages.config @@ -1,7 +1,7 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/src/NzbDrone.Common/NzbDrone.Common.csproj b/src/NzbDrone.Common/NzbDrone.Common.csproj index 7331515efc..09fe501f71 100644 --- a/src/NzbDrone.Common/NzbDrone.Common.csproj +++ b/src/NzbDrone.Common/NzbDrone.Common.csproj @@ -1,282 +1,282 @@ - - - - Debug - x86 - 8.0.30703 - 2.0 - {F2BE0FDF-6E47-4827-A420-DD4EF82407F8} - Library - Properties - NzbDrone.Common - NzbDrone.Common - v4.0 - 512 - ..\ - true - - - - - true - ..\..\_output\ - DEBUG;TRACE - full - x86 - prompt - MinimumRecommendedRules.ruleset - 4 - false - - - ..\..\_output\ - TRACE - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset - 4 - - - - ..\packages\Newtonsoft.Json.9.0.1\lib\net40\Newtonsoft.Json.dll - - - ..\packages\NLog.4.5.0-rc06\lib\net40-client\NLog.dll - - - ..\packages\DotNet4.SocksProxy.1.3.2.0\lib\net40\Org.Mentalis.dll - True - - - ..\packages\SharpRaven.2.4.0\lib\net40\SharpRaven.dll - - - ..\packages\DotNet4.SocksProxy.1.3.2.0\lib\net40\SocksWebProxy.dll - True - - - - - - ..\Libraries\Sqlite\System.Data.SQLite.dll - - - - - - - - - - ..\packages\ICSharpCode.SharpZipLib.Patched.0.86.5\lib\net20\ICSharpCode.SharpZipLib.dll - - - - ..\packages\System.ValueTuple.4.5.0\lib\portable-net40+sl4+win8+wp8\System.ValueTuple.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Component - - - - - - - - - - - - - - - - - - Component - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Always - - - Designer - - - - - - - - - - - - {74420a79-cc16-442c-8b1e-7c1b913844f0} - CurlSharp - - - - + + + + Debug + x86 + 8.0.30703 + 2.0 + {F2BE0FDF-6E47-4827-A420-DD4EF82407F8} + Library + Properties + NzbDrone.Common + NzbDrone.Common + v4.0 + 512 + ..\ + true + + + + + true + ..\..\_output\ + DEBUG;TRACE + full + x86 + prompt + MinimumRecommendedRules.ruleset + 4 + false + + + ..\..\_output\ + TRACE + true + pdbonly + x86 + prompt + MinimumRecommendedRules.ruleset + 4 + + + + ..\packages\Newtonsoft.Json.9.0.1\lib\net40\Newtonsoft.Json.dll + + + ..\packages\NLog.4.5.0-rc06\lib\net40-client\NLog.dll + + + ..\packages\DotNet4.SocksProxy.1.3.2.0\lib\net40\Org.Mentalis.dll + True + + + ..\packages\SharpRaven.2.4.0\lib\net40\SharpRaven.dll + + + ..\packages\DotNet4.SocksProxy.1.3.2.0\lib\net40\SocksWebProxy.dll + True + + + + + + ..\Libraries\Sqlite\System.Data.SQLite.dll + + + + + + + + + + ..\packages\ICSharpCode.SharpZipLib.Patched.0.86.5\lib\net20\ICSharpCode.SharpZipLib.dll + + + + ..\packages\System.ValueTuple.4.5.0\lib\portable-net40+sl4+win8+wp8\System.ValueTuple.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Component + + + + + + + + + + + + + + + + + + Component + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Always + + + Designer + + + + + + + + + + + + {74420a79-cc16-442c-8b1e-7c1b913844f0} + CurlSharp + + + + \ No newline at end of file diff --git a/src/NzbDrone.Common/packages.config b/src/NzbDrone.Common/packages.config index a999e5d85a..ed66b1486f 100644 --- a/src/NzbDrone.Common/packages.config +++ b/src/NzbDrone.Common/packages.config @@ -1,9 +1,9 @@ - - - - - - - - + + + + + + + + \ No newline at end of file diff --git a/src/NzbDrone.Console/NzbDrone.Console.csproj b/src/NzbDrone.Console/NzbDrone.Console.csproj index a0f023d3ed..b6ac2e52cc 100644 --- a/src/NzbDrone.Console/NzbDrone.Console.csproj +++ b/src/NzbDrone.Console/NzbDrone.Console.csproj @@ -1,162 +1,162 @@ - - - - Debug - x86 - 8.0.30703 - 2.0 - {3DCA7B58-B8B3-49AC-9D9E-56F4A0460976} - Exe - Properties - NzbDrone.Console - Radarr.Console - v4.0 - 512 - - - false - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - true - ..\ - true - - - x86 - true - full - false - ..\..\_output\ - DEBUG;TRACE - prompt - 4 - true - BasicCorrectnessRules.ruleset - - - x86 - pdbonly - true - ..\..\_output\ - TRACE - prompt - 4 - - - Radarr.ico - - - NzbDrone.Console.ConsoleApp - - - OnBuildSuccess - - - - - False - ..\packages\Microsoft.Owin.2.1.0\lib\net40\Microsoft.Owin.dll - - - False - ..\packages\Microsoft.Owin.Hosting.2.1.0\lib\net40\Microsoft.Owin.Hosting.dll - - - ..\packages\Newtonsoft.Json.9.0.1\lib\net40\Newtonsoft.Json.dll - - - ..\packages\NLog.4.5.0-rc06\lib\net40-client\NLog.dll - - - - - - ..\packages\Owin.1.0\lib\net40\Owin.dll - - - - - - - - - - Properties\SharedAssemblyInfo.cs - - - - - - - - False - Microsoft .NET Framework 4 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - false - - - False - Windows Installer 3.1 - true - - - - - {1B9A82C4-BCA1-4834-A33E-226F17BE070B} - Microsoft.AspNet.SignalR.Core - - - {2B8C6DAD-4D85-41B1-83FD-248D9F347522} - Microsoft.AspNet.SignalR.Owin - - - {F2BE0FDF-6E47-4827-A420-DD4EF82407F8} - NzbDrone.Common - - - {95C11A9E-56ED-456A-8447-2C89C1139266} - NzbDrone.Host - - - - - app.config - - - - - - - - - - - + + + + Debug + x86 + 8.0.30703 + 2.0 + {3DCA7B58-B8B3-49AC-9D9E-56F4A0460976} + Exe + Properties + NzbDrone.Console + Radarr.Console + v4.0 + 512 + + + false + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + true + ..\ + true + + + x86 + true + full + false + ..\..\_output\ + DEBUG;TRACE + prompt + 4 + true + BasicCorrectnessRules.ruleset + + + x86 + pdbonly + true + ..\..\_output\ + TRACE + prompt + 4 + + + Radarr.ico + + + NzbDrone.Console.ConsoleApp + + + OnBuildSuccess + + + + + False + ..\packages\Microsoft.Owin.2.1.0\lib\net40\Microsoft.Owin.dll + + + False + ..\packages\Microsoft.Owin.Hosting.2.1.0\lib\net40\Microsoft.Owin.Hosting.dll + + + ..\packages\Newtonsoft.Json.9.0.1\lib\net40\Newtonsoft.Json.dll + + + ..\packages\NLog.4.5.0-rc06\lib\net40-client\NLog.dll + + + + + + ..\packages\Owin.1.0\lib\net40\Owin.dll + + + + + + + + + + Properties\SharedAssemblyInfo.cs + + + + + + + + False + Microsoft .NET Framework 4 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + false + + + False + Windows Installer 3.1 + true + + + + + {1B9A82C4-BCA1-4834-A33E-226F17BE070B} + Microsoft.AspNet.SignalR.Core + + + {2B8C6DAD-4D85-41B1-83FD-248D9F347522} + Microsoft.AspNet.SignalR.Owin + + + {F2BE0FDF-6E47-4827-A420-DD4EF82407F8} + NzbDrone.Common + + + {95C11A9E-56ED-456A-8447-2C89C1139266} + NzbDrone.Host + + + + + app.config + + + + + + + + + + + + --> \ No newline at end of file diff --git a/src/NzbDrone.Console/packages.config b/src/NzbDrone.Console/packages.config index 7001c53f24..537aa75d1b 100644 --- a/src/NzbDrone.Console/packages.config +++ b/src/NzbDrone.Console/packages.config @@ -1,8 +1,8 @@ - - - - - - - + + + + + + + \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/BulkImport/AddMultiMoviesFixture.cs b/src/NzbDrone.Core.Test/BulkImport/AddMultiMoviesFixture.cs index f54d802ec0..fcad2a48e4 100644 --- a/src/NzbDrone.Core.Test/BulkImport/AddMultiMoviesFixture.cs +++ b/src/NzbDrone.Core.Test/BulkImport/AddMultiMoviesFixture.cs @@ -1,28 +1,28 @@ -using FizzWare.NBuilder; -using FluentAssertions; -using NUnit.Framework; -using Moq; -using NzbDrone.Core.Organizer; -using NzbDrone.Core.Movies; -using NzbDrone.Core.Test.Framework; -using NzbDrone.Core.Movies.Events; +using FizzWare.NBuilder; +using FluentAssertions; +using NUnit.Framework; +using Moq; +using NzbDrone.Core.Organizer; +using NzbDrone.Core.Movies; +using NzbDrone.Core.Test.Framework; +using NzbDrone.Core.Movies.Events; using System.Collections.Generic; -namespace NzbDrone.Core.Test.BulkImport +namespace NzbDrone.Core.Test.BulkImport { [TestFixture] public class AddMultiMoviesFixture : CoreTest - { + { private List fakeMovies; [SetUp] public void Setup() - { - fakeMovies = Builder.CreateListOfSize(3).BuildList(); + { + fakeMovies = Builder.CreateListOfSize(3).BuildList(); fakeMovies.ForEach(m => - { - m.Path = null; - m.RootFolderPath = @"C:\Test\TV"; + { + m.Path = null; + m.RootFolderPath = @"C:\Test\TV"; }); } @@ -35,41 +35,41 @@ public void movies_added_event_should_have_proper_path() var movies = Subject.AddMovies(fakeMovies); - foreach (Movie movie in movies) - { - movie.Path.Should().NotBeNullOrEmpty(); + foreach (Movie movie in movies) + { + movie.Path.Should().NotBeNullOrEmpty(); } //Subject.GetAllMovies().Should().HaveCount(3); - } - - [Test] - public void movies_added_should_ignore_already_added() - { - Mocker.GetMock() - .Setup(s => s.GetMovieFolder(It.IsAny(), null)) - .Returns((Movie m, NamingConfig n) => m.Title); - - Mocker.GetMock().Setup(s => s.All()).Returns(new List { fakeMovies[0] }); - - var movies = Subject.AddMovies(fakeMovies); - - Mocker.GetMock().Verify(v => v.InsertMany(It.Is>(l => l.Count == 2))); - } - - [Test] - public void movies_added_should_ignore_duplicates() - { - Mocker.GetMock() - .Setup(s => s.GetMovieFolder(It.IsAny(), null)) - .Returns((Movie m, NamingConfig n) => m.Title); - - fakeMovies[2].TmdbId = fakeMovies[0].TmdbId; - - var movies = Subject.AddMovies(fakeMovies); - - Mocker.GetMock().Verify(v => v.InsertMany(It.Is>(l => l.Count == 2))); } - } + [Test] + public void movies_added_should_ignore_already_added() + { + Mocker.GetMock() + .Setup(s => s.GetMovieFolder(It.IsAny(), null)) + .Returns((Movie m, NamingConfig n) => m.Title); + + Mocker.GetMock().Setup(s => s.All()).Returns(new List { fakeMovies[0] }); + + var movies = Subject.AddMovies(fakeMovies); + + Mocker.GetMock().Verify(v => v.InsertMany(It.Is>(l => l.Count == 2))); + } + + [Test] + public void movies_added_should_ignore_duplicates() + { + Mocker.GetMock() + .Setup(s => s.GetMovieFolder(It.IsAny(), null)) + .Returns((Movie m, NamingConfig n) => m.Title); + + fakeMovies[2].TmdbId = fakeMovies[0].TmdbId; + + var movies = Subject.AddMovies(fakeMovies); + + Mocker.GetMock().Verify(v => v.InsertMany(It.Is>(l => l.Count == 2))); + } + + } } \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/Files/Indexers/TorrentRss/DanishBits.xml b/src/NzbDrone.Core.Test/Files/Indexers/TorrentRss/DanishBits.xml index 5b551e7324..615802afec 100644 --- a/src/NzbDrone.Core.Test/Files/Indexers/TorrentRss/DanishBits.xml +++ b/src/NzbDrone.Core.Test/Files/Indexers/TorrentRss/DanishBits.xml @@ -1,312 +1,312 @@ - - - - HD Film (RAPiDCOWS) :: Danishbits.org - http://danishbits.org/ - RSS feed for all new HD Film (RAPiDCOWS). - en-us - Thu, 03 May 2018 11:12:03 +0200 - - http://blogs.law.harvard.edu/tech/rss - Gazelle Feed Class - <![CDATA[Marfa.Girl.2012.NORDiC.720p.WEB-DL.H.264-RAPiDCOWS]]> - - Thu, 03 May 2018 10:56:03 +0200 - https://danishbits.org/torrents.php/Marfa.Girl.2012.NORDiC.720p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=960749 - - https://danishbits.org/torrents.php/Marfa.Girl.2012.NORDiC.720p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=960749 - http://danishbits.org/torrents.php?id=961807 - - N/A - - <![CDATA[Cowgirls.n.Angels.2012.NORDiC.1080p.BluRay.x264-RAPiDCOWS]]> - - Thu, 03 May 2018 10:14:09 +0200 - https://danishbits.org/torrents.php/Cowgirls.n.Angels.2012.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=960732 - - https://danishbits.org/torrents.php/Cowgirls.n.Angels.2012.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=960732 - http://danishbits.org/torrents.php?id=961790 - - N/A - - <![CDATA[Cowgirls.n.Angels.2012.NORDiC.720p.BluRay.x264-RAPiDCOWS]]> - - Thu, 03 May 2018 10:14:07 +0200 - https://danishbits.org/torrents.php/Cowgirls.n.Angels.2012.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=960731 - - https://danishbits.org/torrents.php/Cowgirls.n.Angels.2012.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=960731 - http://danishbits.org/torrents.php?id=961789 - - N/A - - <![CDATA[The.Big.Hit.1998.NORDiC.1080p.BluRay.x264-RAPiDCOWS]]> - - Wed, 02 May 2018 20:54:41 +0200 - https://danishbits.org/torrents.php/The.Big.Hit.1998.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=960314 - - https://danishbits.org/torrents.php/The.Big.Hit.1998.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=960314 - http://danishbits.org/torrents.php?id=961372 - - N/A - - <![CDATA[Phantom.Thread.2017.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.7.1-CDB]]> - - Wed, 02 May 2018 12:18:51 +0200 - https://danishbits.org/torrents.php/Phantom.Thread.2017.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.7.1-CDB.torrent/?action=download&REPLACED&REPLACED&id=960040 - - https://danishbits.org/torrents.php/Phantom.Thread.2017.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.7.1-CDB.torrent/?action=download&REPLACED&REPLACED;id=960040 - http://danishbits.org/torrents.php?id=961098 - - N/A - - <![CDATA[Phantom.Thread.2017.NORDiC.720p.BluRay.x264-RAPiDCOWS]]> - - Wed, 02 May 2018 11:04:19 +0200 - https://danishbits.org/torrents.php/Phantom.Thread.2017.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959995 - - https://danishbits.org/torrents.php/Phantom.Thread.2017.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959995 - http://danishbits.org/torrents.php?id=961053 - - N/A - - <![CDATA[Phantom.Thread.2017.NORDiC.1080p.BluRay.x264-RAPiDCOWS]]> - - Wed, 02 May 2018 10:21:45 +0200 - https://danishbits.org/torrents.php/Phantom.Thread.2017.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959975 - - https://danishbits.org/torrents.php/Phantom.Thread.2017.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959975 - http://danishbits.org/torrents.php?id=961033 - - N/A - - <![CDATA[The.15.17.to.Paris.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS]]> - - Wed, 02 May 2018 01:10:26 +0200 - https://danishbits.org/torrents.php/The.15.17.to.Paris.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959619 - - https://danishbits.org/torrents.php/The.15.17.to.Paris.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959619 - http://danishbits.org/torrents.php?id=960677 - - N/A - - <![CDATA[Walking.with.the.Enemy.2013.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS]]> - - Tue, 01 May 2018 20:10:57 +0200 - https://danishbits.org/torrents.php/Walking.with.the.Enemy.2013.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959519 - - https://danishbits.org/torrents.php/Walking.with.the.Enemy.2013.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959519 - http://danishbits.org/torrents.php?id=960577 - - N/A - - <![CDATA[The.Broken.Circle.Breakdown.2012.NORDiC.720p.BluRay.x264-RAPiDCOWS]]> - - Tue, 01 May 2018 20:00:46 +0200 - https://danishbits.org/torrents.php/The.Broken.Circle.Breakdown.2012.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959512 - - https://danishbits.org/torrents.php/The.Broken.Circle.Breakdown.2012.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959512 - http://danishbits.org/torrents.php?id=960570 - - N/A - - <![CDATA[The.Broken.Circle.Breakdown.2012.NORDiC.1080p.BluRay.x264-RAPiDCOWS]]> - - Tue, 01 May 2018 19:59:30 +0200 - https://danishbits.org/torrents.php/The.Broken.Circle.Breakdown.2012.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959510 - - https://danishbits.org/torrents.php/The.Broken.Circle.Breakdown.2012.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959510 - http://danishbits.org/torrents.php?id=960568 - - N/A - - <![CDATA[The.Boy.in.the.Striped.Pyjamas.2008.NORDiC.720p.BluRay.x264-RAPiDCOWS]]> - - Tue, 01 May 2018 18:44:23 +0200 - https://danishbits.org/torrents.php/The.Boy.in.the.Striped.Pyjamas.2008.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959491 - - https://danishbits.org/torrents.php/The.Boy.in.the.Striped.Pyjamas.2008.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959491 - http://danishbits.org/torrents.php?id=960549 - - N/A - - <![CDATA[The.Boy.in.the.Striped.Pyjamas.2008.NORDiC.1080p.BluRay.x264-RAPiDCOWS]]> - - Tue, 01 May 2018 18:44:04 +0200 - https://danishbits.org/torrents.php/The.Boy.in.the.Striped.Pyjamas.2008.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959490 - - https://danishbits.org/torrents.php/The.Boy.in.the.Striped.Pyjamas.2008.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959490 - http://danishbits.org/torrents.php?id=960548 - - N/A - - <![CDATA[27.Gone.Too.Soon.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS]]> - - Tue, 01 May 2018 17:58:14 +0200 - https://danishbits.org/torrents.php/27.Gone.Too.Soon.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959484 - - https://danishbits.org/torrents.php/27.Gone.Too.Soon.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959484 - http://danishbits.org/torrents.php?id=960542 - - N/A - - <![CDATA[Most.Likely.to.Murder.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS]]> - - Tue, 01 May 2018 14:18:26 +0200 - https://danishbits.org/torrents.php/Most.Likely.to.Murder.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959367 - - https://danishbits.org/torrents.php/Most.Likely.to.Murder.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959367 - http://danishbits.org/torrents.php?id=960425 - - N/A - - <![CDATA[Kriegerin.2011.Retail.DK.SEsubs.1080p.BluRay.x264-RAPiDCOWS]]> - - Tue, 01 May 2018 11:41:52 +0200 - https://danishbits.org/torrents.php/Kriegerin.2011.Retail.DK.SEsubs.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959254 - - https://danishbits.org/torrents.php/Kriegerin.2011.Retail.DK.SEsubs.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959254 - http://danishbits.org/torrents.php?id=960312 - - N/A - - <![CDATA[90.Minutes.in.Heaven.2015.NORDiC.1080p.BluRay.x264-RAPiDCOWS]]> - - Tue, 01 May 2018 03:21:36 +0200 - https://danishbits.org/torrents.php/90.Minutes.in.Heaven.2015.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=958960 - - https://danishbits.org/torrents.php/90.Minutes.in.Heaven.2015.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=958960 - http://danishbits.org/torrents.php?id=960018 - - N/A - - <![CDATA[90.Minutes.in.Heaven.2015.NORDiC.720p.BluRay.x264-RAPiDCOWS]]> - - Tue, 01 May 2018 03:19:25 +0200 - https://danishbits.org/torrents.php/90.Minutes.in.Heaven.2015.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=958958 - - https://danishbits.org/torrents.php/90.Minutes.in.Heaven.2015.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=958958 - http://danishbits.org/torrents.php?id=960016 - - N/A - - <![CDATA[50.to.1.2014.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS]]> - - Tue, 01 May 2018 02:30:54 +0200 - https://danishbits.org/torrents.php/50.to.1.2014.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=958929 - - https://danishbits.org/torrents.php/50.to.1.2014.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=958929 - http://danishbits.org/torrents.php?id=959987 - - N/A - - <![CDATA[Killer.Klowns.from.Outer.Space.1988.NORDiC.REMASTERED.720p.BluRay.x264-RAPiDCOWS]]> - - Mon, 30 Apr 2018 21:31:23 +0200 - https://danishbits.org/torrents.php/Killer.Klowns.from.Outer.Space.1988.NORDiC.REMASTERED.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=958702 - - https://danishbits.org/torrents.php/Killer.Klowns.from.Outer.Space.1988.NORDiC.REMASTERED.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=958702 - http://danishbits.org/torrents.php?id=959760 - - N/A - - <![CDATA[Killer.Klowns.from.Outer.Space.1988.NORDiC.REMASTERED.1080p.BluRay.x264-RAPiDCOWS]]> - - Mon, 30 Apr 2018 21:30:31 +0200 - https://danishbits.org/torrents.php/Killer.Klowns.from.Outer.Space.1988.NORDiC.REMASTERED.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=958701 - - https://danishbits.org/torrents.php/Killer.Klowns.from.Outer.Space.1988.NORDiC.REMASTERED.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=958701 - http://danishbits.org/torrents.php?id=959759 - - N/A - - <![CDATA[Monky.2017.NORDiC.720p.BluRay.x264-RAPiDCOWS]]> - - Mon, 30 Apr 2018 18:09:08 +0200 - https://danishbits.org/torrents.php/Monky.2017.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=958582 - - https://danishbits.org/torrents.php/Monky.2017.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=958582 - http://danishbits.org/torrents.php?id=959640 - - N/A - - <![CDATA[Monky.2017.NORDiC.1080p.BluRay.x264-RAPiDCOWS]]> - - Mon, 30 Apr 2018 18:08:59 +0200 - https://danishbits.org/torrents.php/Monky.2017.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=958581 - - https://danishbits.org/torrents.php/Monky.2017.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=958581 - http://danishbits.org/torrents.php?id=959639 - - N/A - - <![CDATA[Candy.Jar.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS]]> - - Mon, 30 Apr 2018 13:08:13 +0200 - https://danishbits.org/torrents.php/Candy.Jar.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=958467 - - https://danishbits.org/torrents.php/Candy.Jar.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=958467 - http://danishbits.org/torrents.php?id=959525 - - N/A - - <![CDATA[Sugar.Mountain.2016.NORDiC.720p.BluRay.x264-RAPiDCOWS]]> - - Sun, 29 Apr 2018 19:29:45 +0200 - https://danishbits.org/torrents.php/Sugar.Mountain.2016.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=957889 - - https://danishbits.org/torrents.php/Sugar.Mountain.2016.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=957889 - http://danishbits.org/torrents.php?id=958947 - - N/A - - <![CDATA[Sugar.Mountain.2016.NORDiC.1080p.BluRay.x264-RAPiDCOWS]]> - - Sun, 29 Apr 2018 19:29:39 +0200 - https://danishbits.org/torrents.php/Sugar.Mountain.2016.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=957888 - - https://danishbits.org/torrents.php/Sugar.Mountain.2016.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=957888 - http://danishbits.org/torrents.php?id=958946 - - N/A - - <![CDATA[The.Rachel.Divide.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS]]> - - Sun, 29 Apr 2018 18:04:55 +0200 - https://danishbits.org/torrents.php/The.Rachel.Divide.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=957808 - - https://danishbits.org/torrents.php/The.Rachel.Divide.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=957808 - http://danishbits.org/torrents.php?id=958866 - - N/A - - <![CDATA[Deep.Blue.Sea.2.2018.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.5.1-CDB]]> - - Sun, 29 Apr 2018 14:53:03 +0200 - https://danishbits.org/torrents.php/Deep.Blue.Sea.2.2018.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.5.1-CDB.torrent/?action=download&REPLACED&REPLACED&id=957725 - - https://danishbits.org/torrents.php/Deep.Blue.Sea.2.2018.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.5.1-CDB.torrent/?action=download&REPLACED&REPLACED;id=957725 - http://danishbits.org/torrents.php?id=958783 - - N/A - - <![CDATA[Backstabbing.for.Beginners.2018.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.5.1-CDB]]> - - Sun, 29 Apr 2018 10:28:56 +0200 - https://danishbits.org/torrents.php/Backstabbing.for.Beginners.2018.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.5.1-CDB.torrent/?action=download&REPLACED&REPLACED&id=957570 - - https://danishbits.org/torrents.php/Backstabbing.for.Beginners.2018.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.5.1-CDB.torrent/?action=download&REPLACED&REPLACED;id=957570 - http://danishbits.org/torrents.php?id=958628 - - N/A - - <![CDATA[Killing.Gunther.2017.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.5.1-CDB]]> - - Sun, 29 Apr 2018 08:22:42 +0200 - https://danishbits.org/torrents.php/Killing.Gunther.2017.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.5.1-CDB.torrent/?action=download&REPLACED&REPLACED&id=957508 - - https://danishbits.org/torrents.php/Killing.Gunther.2017.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.5.1-CDB.torrent/?action=download&REPLACED&REPLACED;id=957508 - http://danishbits.org/torrents.php?id=958566 - - N/A - + + + + HD Film (RAPiDCOWS) :: Danishbits.org + http://danishbits.org/ + RSS feed for all new HD Film (RAPiDCOWS). + en-us + Thu, 03 May 2018 11:12:03 +0200 + + http://blogs.law.harvard.edu/tech/rss + Gazelle Feed Class + <![CDATA[Marfa.Girl.2012.NORDiC.720p.WEB-DL.H.264-RAPiDCOWS]]> + + Thu, 03 May 2018 10:56:03 +0200 + https://danishbits.org/torrents.php/Marfa.Girl.2012.NORDiC.720p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=960749 + + https://danishbits.org/torrents.php/Marfa.Girl.2012.NORDiC.720p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=960749 + http://danishbits.org/torrents.php?id=961807 + + N/A + + <![CDATA[Cowgirls.n.Angels.2012.NORDiC.1080p.BluRay.x264-RAPiDCOWS]]> + + Thu, 03 May 2018 10:14:09 +0200 + https://danishbits.org/torrents.php/Cowgirls.n.Angels.2012.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=960732 + + https://danishbits.org/torrents.php/Cowgirls.n.Angels.2012.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=960732 + http://danishbits.org/torrents.php?id=961790 + + N/A + + <![CDATA[Cowgirls.n.Angels.2012.NORDiC.720p.BluRay.x264-RAPiDCOWS]]> + + Thu, 03 May 2018 10:14:07 +0200 + https://danishbits.org/torrents.php/Cowgirls.n.Angels.2012.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=960731 + + https://danishbits.org/torrents.php/Cowgirls.n.Angels.2012.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=960731 + http://danishbits.org/torrents.php?id=961789 + + N/A + + <![CDATA[The.Big.Hit.1998.NORDiC.1080p.BluRay.x264-RAPiDCOWS]]> + + Wed, 02 May 2018 20:54:41 +0200 + https://danishbits.org/torrents.php/The.Big.Hit.1998.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=960314 + + https://danishbits.org/torrents.php/The.Big.Hit.1998.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=960314 + http://danishbits.org/torrents.php?id=961372 + + N/A + + <![CDATA[Phantom.Thread.2017.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.7.1-CDB]]> + + Wed, 02 May 2018 12:18:51 +0200 + https://danishbits.org/torrents.php/Phantom.Thread.2017.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.7.1-CDB.torrent/?action=download&REPLACED&REPLACED&id=960040 + + https://danishbits.org/torrents.php/Phantom.Thread.2017.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.7.1-CDB.torrent/?action=download&REPLACED&REPLACED;id=960040 + http://danishbits.org/torrents.php?id=961098 + + N/A + + <![CDATA[Phantom.Thread.2017.NORDiC.720p.BluRay.x264-RAPiDCOWS]]> + + Wed, 02 May 2018 11:04:19 +0200 + https://danishbits.org/torrents.php/Phantom.Thread.2017.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959995 + + https://danishbits.org/torrents.php/Phantom.Thread.2017.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959995 + http://danishbits.org/torrents.php?id=961053 + + N/A + + <![CDATA[Phantom.Thread.2017.NORDiC.1080p.BluRay.x264-RAPiDCOWS]]> + + Wed, 02 May 2018 10:21:45 +0200 + https://danishbits.org/torrents.php/Phantom.Thread.2017.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959975 + + https://danishbits.org/torrents.php/Phantom.Thread.2017.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959975 + http://danishbits.org/torrents.php?id=961033 + + N/A + + <![CDATA[The.15.17.to.Paris.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS]]> + + Wed, 02 May 2018 01:10:26 +0200 + https://danishbits.org/torrents.php/The.15.17.to.Paris.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959619 + + https://danishbits.org/torrents.php/The.15.17.to.Paris.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959619 + http://danishbits.org/torrents.php?id=960677 + + N/A + + <![CDATA[Walking.with.the.Enemy.2013.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS]]> + + Tue, 01 May 2018 20:10:57 +0200 + https://danishbits.org/torrents.php/Walking.with.the.Enemy.2013.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959519 + + https://danishbits.org/torrents.php/Walking.with.the.Enemy.2013.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959519 + http://danishbits.org/torrents.php?id=960577 + + N/A + + <![CDATA[The.Broken.Circle.Breakdown.2012.NORDiC.720p.BluRay.x264-RAPiDCOWS]]> + + Tue, 01 May 2018 20:00:46 +0200 + https://danishbits.org/torrents.php/The.Broken.Circle.Breakdown.2012.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959512 + + https://danishbits.org/torrents.php/The.Broken.Circle.Breakdown.2012.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959512 + http://danishbits.org/torrents.php?id=960570 + + N/A + + <![CDATA[The.Broken.Circle.Breakdown.2012.NORDiC.1080p.BluRay.x264-RAPiDCOWS]]> + + Tue, 01 May 2018 19:59:30 +0200 + https://danishbits.org/torrents.php/The.Broken.Circle.Breakdown.2012.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959510 + + https://danishbits.org/torrents.php/The.Broken.Circle.Breakdown.2012.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959510 + http://danishbits.org/torrents.php?id=960568 + + N/A + + <![CDATA[The.Boy.in.the.Striped.Pyjamas.2008.NORDiC.720p.BluRay.x264-RAPiDCOWS]]> + + Tue, 01 May 2018 18:44:23 +0200 + https://danishbits.org/torrents.php/The.Boy.in.the.Striped.Pyjamas.2008.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959491 + + https://danishbits.org/torrents.php/The.Boy.in.the.Striped.Pyjamas.2008.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959491 + http://danishbits.org/torrents.php?id=960549 + + N/A + + <![CDATA[The.Boy.in.the.Striped.Pyjamas.2008.NORDiC.1080p.BluRay.x264-RAPiDCOWS]]> + + Tue, 01 May 2018 18:44:04 +0200 + https://danishbits.org/torrents.php/The.Boy.in.the.Striped.Pyjamas.2008.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959490 + + https://danishbits.org/torrents.php/The.Boy.in.the.Striped.Pyjamas.2008.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959490 + http://danishbits.org/torrents.php?id=960548 + + N/A + + <![CDATA[27.Gone.Too.Soon.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS]]> + + Tue, 01 May 2018 17:58:14 +0200 + https://danishbits.org/torrents.php/27.Gone.Too.Soon.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959484 + + https://danishbits.org/torrents.php/27.Gone.Too.Soon.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959484 + http://danishbits.org/torrents.php?id=960542 + + N/A + + <![CDATA[Most.Likely.to.Murder.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS]]> + + Tue, 01 May 2018 14:18:26 +0200 + https://danishbits.org/torrents.php/Most.Likely.to.Murder.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959367 + + https://danishbits.org/torrents.php/Most.Likely.to.Murder.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959367 + http://danishbits.org/torrents.php?id=960425 + + N/A + + <![CDATA[Kriegerin.2011.Retail.DK.SEsubs.1080p.BluRay.x264-RAPiDCOWS]]> + + Tue, 01 May 2018 11:41:52 +0200 + https://danishbits.org/torrents.php/Kriegerin.2011.Retail.DK.SEsubs.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=959254 + + https://danishbits.org/torrents.php/Kriegerin.2011.Retail.DK.SEsubs.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=959254 + http://danishbits.org/torrents.php?id=960312 + + N/A + + <![CDATA[90.Minutes.in.Heaven.2015.NORDiC.1080p.BluRay.x264-RAPiDCOWS]]> + + Tue, 01 May 2018 03:21:36 +0200 + https://danishbits.org/torrents.php/90.Minutes.in.Heaven.2015.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=958960 + + https://danishbits.org/torrents.php/90.Minutes.in.Heaven.2015.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=958960 + http://danishbits.org/torrents.php?id=960018 + + N/A + + <![CDATA[90.Minutes.in.Heaven.2015.NORDiC.720p.BluRay.x264-RAPiDCOWS]]> + + Tue, 01 May 2018 03:19:25 +0200 + https://danishbits.org/torrents.php/90.Minutes.in.Heaven.2015.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=958958 + + https://danishbits.org/torrents.php/90.Minutes.in.Heaven.2015.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=958958 + http://danishbits.org/torrents.php?id=960016 + + N/A + + <![CDATA[50.to.1.2014.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS]]> + + Tue, 01 May 2018 02:30:54 +0200 + https://danishbits.org/torrents.php/50.to.1.2014.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=958929 + + https://danishbits.org/torrents.php/50.to.1.2014.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=958929 + http://danishbits.org/torrents.php?id=959987 + + N/A + + <![CDATA[Killer.Klowns.from.Outer.Space.1988.NORDiC.REMASTERED.720p.BluRay.x264-RAPiDCOWS]]> + + Mon, 30 Apr 2018 21:31:23 +0200 + https://danishbits.org/torrents.php/Killer.Klowns.from.Outer.Space.1988.NORDiC.REMASTERED.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=958702 + + https://danishbits.org/torrents.php/Killer.Klowns.from.Outer.Space.1988.NORDiC.REMASTERED.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=958702 + http://danishbits.org/torrents.php?id=959760 + + N/A + + <![CDATA[Killer.Klowns.from.Outer.Space.1988.NORDiC.REMASTERED.1080p.BluRay.x264-RAPiDCOWS]]> + + Mon, 30 Apr 2018 21:30:31 +0200 + https://danishbits.org/torrents.php/Killer.Klowns.from.Outer.Space.1988.NORDiC.REMASTERED.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=958701 + + https://danishbits.org/torrents.php/Killer.Klowns.from.Outer.Space.1988.NORDiC.REMASTERED.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=958701 + http://danishbits.org/torrents.php?id=959759 + + N/A + + <![CDATA[Monky.2017.NORDiC.720p.BluRay.x264-RAPiDCOWS]]> + + Mon, 30 Apr 2018 18:09:08 +0200 + https://danishbits.org/torrents.php/Monky.2017.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=958582 + + https://danishbits.org/torrents.php/Monky.2017.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=958582 + http://danishbits.org/torrents.php?id=959640 + + N/A + + <![CDATA[Monky.2017.NORDiC.1080p.BluRay.x264-RAPiDCOWS]]> + + Mon, 30 Apr 2018 18:08:59 +0200 + https://danishbits.org/torrents.php/Monky.2017.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=958581 + + https://danishbits.org/torrents.php/Monky.2017.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=958581 + http://danishbits.org/torrents.php?id=959639 + + N/A + + <![CDATA[Candy.Jar.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS]]> + + Mon, 30 Apr 2018 13:08:13 +0200 + https://danishbits.org/torrents.php/Candy.Jar.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=958467 + + https://danishbits.org/torrents.php/Candy.Jar.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=958467 + http://danishbits.org/torrents.php?id=959525 + + N/A + + <![CDATA[Sugar.Mountain.2016.NORDiC.720p.BluRay.x264-RAPiDCOWS]]> + + Sun, 29 Apr 2018 19:29:45 +0200 + https://danishbits.org/torrents.php/Sugar.Mountain.2016.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=957889 + + https://danishbits.org/torrents.php/Sugar.Mountain.2016.NORDiC.720p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=957889 + http://danishbits.org/torrents.php?id=958947 + + N/A + + <![CDATA[Sugar.Mountain.2016.NORDiC.1080p.BluRay.x264-RAPiDCOWS]]> + + Sun, 29 Apr 2018 19:29:39 +0200 + https://danishbits.org/torrents.php/Sugar.Mountain.2016.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=957888 + + https://danishbits.org/torrents.php/Sugar.Mountain.2016.NORDiC.1080p.BluRay.x264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=957888 + http://danishbits.org/torrents.php?id=958946 + + N/A + + <![CDATA[The.Rachel.Divide.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS]]> + + Sun, 29 Apr 2018 18:04:55 +0200 + https://danishbits.org/torrents.php/The.Rachel.Divide.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED&id=957808 + + https://danishbits.org/torrents.php/The.Rachel.Divide.2018.NORDiC.1080p.WEB-DL.H.264-RAPiDCOWS.torrent/?action=download&REPLACED&REPLACED;id=957808 + http://danishbits.org/torrents.php?id=958866 + + N/A + + <![CDATA[Deep.Blue.Sea.2.2018.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.5.1-CDB]]> + + Sun, 29 Apr 2018 14:53:03 +0200 + https://danishbits.org/torrents.php/Deep.Blue.Sea.2.2018.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.5.1-CDB.torrent/?action=download&REPLACED&REPLACED&id=957725 + + https://danishbits.org/torrents.php/Deep.Blue.Sea.2.2018.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.5.1-CDB.torrent/?action=download&REPLACED&REPLACED;id=957725 + http://danishbits.org/torrents.php?id=958783 + + N/A + + <![CDATA[Backstabbing.for.Beginners.2018.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.5.1-CDB]]> + + Sun, 29 Apr 2018 10:28:56 +0200 + https://danishbits.org/torrents.php/Backstabbing.for.Beginners.2018.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.5.1-CDB.torrent/?action=download&REPLACED&REPLACED&id=957570 + + https://danishbits.org/torrents.php/Backstabbing.for.Beginners.2018.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.5.1-CDB.torrent/?action=download&REPLACED&REPLACED;id=957570 + http://danishbits.org/torrents.php?id=958628 + + N/A + + <![CDATA[Killing.Gunther.2017.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.5.1-CDB]]> + + Sun, 29 Apr 2018 08:22:42 +0200 + https://danishbits.org/torrents.php/Killing.Gunther.2017.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.5.1-CDB.torrent/?action=download&REPLACED&REPLACED&id=957508 + + https://danishbits.org/torrents.php/Killing.Gunther.2017.NORDiC.REMUX.1080p.BluRay.AVC.DTS-HD.MA.5.1-CDB.torrent/?action=download&REPLACED&REPLACED;id=957508 + http://danishbits.org/torrents.php?id=958566 + + N/A + \ No newline at end of file diff --git a/src/NzbDrone.Core.Test/packages.config b/src/NzbDrone.Core.Test/packages.config index 1a8597e28b..dd7239fe77 100644 --- a/src/NzbDrone.Core.Test/packages.config +++ b/src/NzbDrone.Core.Test/packages.config @@ -1,18 +1,18 @@ - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Responses/SabnzbdFullStatusResponse.cs b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Responses/SabnzbdFullStatusResponse.cs index 48c9710c51..4ba37f66d5 100644 --- a/src/NzbDrone.Core/Download/Clients/Sabnzbd/Responses/SabnzbdFullStatusResponse.cs +++ b/src/NzbDrone.Core/Download/Clients/Sabnzbd/Responses/SabnzbdFullStatusResponse.cs @@ -1,7 +1,7 @@ -namespace NzbDrone.Core.Download.Clients.Sabnzbd.Responses -{ - public class SabnzbdFullStatusResponse - { - public SabnzbdFullStatus Status { get; set; } - } -} +namespace NzbDrone.Core.Download.Clients.Sabnzbd.Responses +{ + public class SabnzbdFullStatusResponse + { + public SabnzbdFullStatus Status { get; set; } + } +} diff --git a/src/NzbDrone.Core/IndexerSearch/MoviesSearchService.cs b/src/NzbDrone.Core/IndexerSearch/MoviesSearchService.cs index 1552706898..3f2c04169f 100644 --- a/src/NzbDrone.Core/IndexerSearch/MoviesSearchService.cs +++ b/src/NzbDrone.Core/IndexerSearch/MoviesSearchService.cs @@ -1,128 +1,128 @@ -using System; -using System.Linq; -using System.Collections.Generic; -using NLog; -using NzbDrone.Common.Instrumentation.Extensions; -using NzbDrone.Core.Download; -using NzbDrone.Core.Messaging.Commands; -using NzbDrone.Core.Movies; -using NzbDrone.Core.Datastore; -using NzbDrone.Core.Queue; -using NzbDrone.Core.DecisionEngine; - -namespace NzbDrone.Core.IndexerSearch -{ - public class MovieSearchService : IExecute, IExecute, IExecute - { - private readonly IMovieService _movieService; - private readonly IMovieCutoffService _movieCutoffService; - private readonly ISearchForNzb _nzbSearchService; - private readonly IProcessDownloadDecisions _processDownloadDecisions; - private readonly IQueueService _queueService; - private readonly Logger _logger; - - public MovieSearchService(IMovieService movieService, - IMovieCutoffService movieCutoffService, - ISearchForNzb nzbSearchService, - IProcessDownloadDecisions processDownloadDecisions, - IQueueService queueService, - Logger logger) - { - _movieService = movieService; - _movieCutoffService = movieCutoffService; - _nzbSearchService = nzbSearchService; - _processDownloadDecisions = processDownloadDecisions; - _queueService = queueService; - _logger = logger; - } - - public void Execute(MoviesSearchCommand message) - { - var downloadedCount = 0; - foreach (var movieId in message.MovieIds) - { - var movies = _movieService.GetMovie(movieId); - - if (!movies.Monitored) - { - _logger.Debug("Movie {0} is not monitored, skipping search", movies.Title); - continue; - } - - var decisions = _nzbSearchService.MovieSearch(movieId, false);//_nzbSearchService.SeasonSearch(message.MovieId, season.SeasonNumber, false, message.Trigger == CommandTrigger.Manual); - downloadedCount += _processDownloadDecisions.ProcessDecisions(decisions).Grabbed.Count; - - } - _logger.ProgressInfo("Movie search completed. {0} reports downloaded.", downloadedCount); - } - - public void Execute(MissingMoviesSearchCommand message) - { - List movies = _movieService.MoviesWithoutFiles(new PagingSpec - { - Page = 1, - PageSize = 100000, - SortDirection = SortDirection.Ascending, - SortKey = "Id", - FilterExpression = _movieService.ConstructFilterExpression(message.FilterKey, message.FilterValue) - }).Records.ToList(); - - - var queue = _queueService.GetQueue().Select(q => q.Movie.Id); - var missing = movies.Where(e => !queue.Contains(e.Id)).ToList(); - - SearchForMissingMovies(missing, message.Trigger == CommandTrigger.Manual); - - } - - public void Execute(CutoffUnmetMoviesSearchCommand message) - { - List movies = _movieCutoffService.MoviesWhereCutoffUnmet(new PagingSpec - { - Page = 1, - PageSize = 100000, - SortDirection = SortDirection.Ascending, - SortKey = "Id", - FilterExpression = _movieService.ConstructFilterExpression(message.FilterKey, message.FilterValue) - }).Records.ToList(); - - - var queue = _queueService.GetQueue().Select(q => q.Movie.Id); - var missing = movies.Where(e => !queue.Contains(e.Id)).ToList(); - - SearchForMissingMovies(missing, message.Trigger == CommandTrigger.Manual); - - } - - private void SearchForMissingMovies(List movies, bool userInvokedSearch) - { - _logger.ProgressInfo("Performing missing search for {0} movies", movies.Count); - var downloadedCount = 0; - - foreach (var movieId in movies.GroupBy(e => e.Id)) - { - List decisions; - - try - { - decisions = _nzbSearchService.MovieSearch(movieId.Key, userInvokedSearch); - } - catch (Exception ex) - { - var message = String.Format("Unable to search for missing movie {0}", movieId.Key); - _logger.Error(ex, message); - continue; - } - - var processed = _processDownloadDecisions.ProcessDecisions(decisions); - - downloadedCount += processed.Grabbed.Count; - } - - _logger.ProgressInfo("Completed missing search for {0} movies. {1} reports downloaded.", movies.Count, downloadedCount); - } - - - - } -} +using System; +using System.Linq; +using System.Collections.Generic; +using NLog; +using NzbDrone.Common.Instrumentation.Extensions; +using NzbDrone.Core.Download; +using NzbDrone.Core.Messaging.Commands; +using NzbDrone.Core.Movies; +using NzbDrone.Core.Datastore; +using NzbDrone.Core.Queue; +using NzbDrone.Core.DecisionEngine; + +namespace NzbDrone.Core.IndexerSearch +{ + public class MovieSearchService : IExecute, IExecute, IExecute + { + private readonly IMovieService _movieService; + private readonly IMovieCutoffService _movieCutoffService; + private readonly ISearchForNzb _nzbSearchService; + private readonly IProcessDownloadDecisions _processDownloadDecisions; + private readonly IQueueService _queueService; + private readonly Logger _logger; + + public MovieSearchService(IMovieService movieService, + IMovieCutoffService movieCutoffService, + ISearchForNzb nzbSearchService, + IProcessDownloadDecisions processDownloadDecisions, + IQueueService queueService, + Logger logger) + { + _movieService = movieService; + _movieCutoffService = movieCutoffService; + _nzbSearchService = nzbSearchService; + _processDownloadDecisions = processDownloadDecisions; + _queueService = queueService; + _logger = logger; + } + + public void Execute(MoviesSearchCommand message) + { + var downloadedCount = 0; + foreach (var movieId in message.MovieIds) + { + var movies = _movieService.GetMovie(movieId); + + if (!movies.Monitored) + { + _logger.Debug("Movie {0} is not monitored, skipping search", movies.Title); + continue; + } + + var decisions = _nzbSearchService.MovieSearch(movieId, false);//_nzbSearchService.SeasonSearch(message.MovieId, season.SeasonNumber, false, message.Trigger == CommandTrigger.Manual); + downloadedCount += _processDownloadDecisions.ProcessDecisions(decisions).Grabbed.Count; + + } + _logger.ProgressInfo("Movie search completed. {0} reports downloaded.", downloadedCount); + } + + public void Execute(MissingMoviesSearchCommand message) + { + List movies = _movieService.MoviesWithoutFiles(new PagingSpec + { + Page = 1, + PageSize = 100000, + SortDirection = SortDirection.Ascending, + SortKey = "Id", + FilterExpression = _movieService.ConstructFilterExpression(message.FilterKey, message.FilterValue) + }).Records.ToList(); + + + var queue = _queueService.GetQueue().Select(q => q.Movie.Id); + var missing = movies.Where(e => !queue.Contains(e.Id)).ToList(); + + SearchForMissingMovies(missing, message.Trigger == CommandTrigger.Manual); + + } + + public void Execute(CutoffUnmetMoviesSearchCommand message) + { + List movies = _movieCutoffService.MoviesWhereCutoffUnmet(new PagingSpec + { + Page = 1, + PageSize = 100000, + SortDirection = SortDirection.Ascending, + SortKey = "Id", + FilterExpression = _movieService.ConstructFilterExpression(message.FilterKey, message.FilterValue) + }).Records.ToList(); + + + var queue = _queueService.GetQueue().Select(q => q.Movie.Id); + var missing = movies.Where(e => !queue.Contains(e.Id)).ToList(); + + SearchForMissingMovies(missing, message.Trigger == CommandTrigger.Manual); + + } + + private void SearchForMissingMovies(List movies, bool userInvokedSearch) + { + _logger.ProgressInfo("Performing missing search for {0} movies", movies.Count); + var downloadedCount = 0; + + foreach (var movieId in movies.GroupBy(e => e.Id)) + { + List decisions; + + try + { + decisions = _nzbSearchService.MovieSearch(movieId.Key, userInvokedSearch); + } + catch (Exception ex) + { + var message = String.Format("Unable to search for missing movie {0}", movieId.Key); + _logger.Error(ex, message); + continue; + } + + var processed = _processDownloadDecisions.ProcessDecisions(decisions); + + downloadedCount += processed.Grabbed.Count; + } + + _logger.ProgressInfo("Completed missing search for {0} movies. {1} reports downloaded.", movies.Count, downloadedCount); + } + + + + } +} diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs index e31c573716..aac3055c90 100644 --- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs +++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs @@ -1,608 +1,608 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; -using NLog; -using NzbDrone.Common.Cache; -using NzbDrone.Common.EnsureThat; -using NzbDrone.Common.Extensions; -using NzbDrone.Core.MediaFiles; -using NzbDrone.Core.MediaFiles.MediaInfo; -using NzbDrone.Core.Qualities; -using NzbDrone.Core.Movies; - -namespace NzbDrone.Core.Organizer -{ - public interface IBuildFileNames - { - string BuildFileName(Movie movie, MovieFile movieFile, NamingConfig namingConfig = null); - string BuildFilePath(Movie movie, string fileName, string extension); - string BuildMoviePath(Movie movie, NamingConfig namingConfig = null); - BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec); - string GetMovieFolder(Movie movie, NamingConfig namingConfig = null); - } - - public class FileNameBuilder : IBuildFileNames - { - private readonly INamingConfigService _namingConfigService; - private readonly IQualityDefinitionService _qualityDefinitionService; - private readonly ICached _episodeFormatCache; - private readonly ICached _absoluteEpisodeFormatCache; - private readonly Logger _logger; - - private static readonly Regex TitleRegex = new Regex(@"\{(?[- ._\[(]*)(?(?:[a-z0-9]+)(?:(?[- ._]+)(?:[a-z0-9]+))?)(?::(?[a-z0-9]+))?(?[- ._)\]]*)\}", - RegexOptions.Compiled | RegexOptions.IgnoreCase); - - private static readonly Regex EpisodeRegex = new Regex(@"(?\{episode(?:\:0+)?})", - RegexOptions.Compiled | RegexOptions.IgnoreCase); - - private static readonly Regex TagsRegex = new Regex(@"(?\{tags(?:\:0+)?})", - RegexOptions.Compiled | RegexOptions.IgnoreCase); - - private static readonly Regex SeasonRegex = new Regex(@"(?\{season(?:\:0+)?})", - RegexOptions.Compiled | RegexOptions.IgnoreCase); - - private static readonly Regex AbsoluteEpisodeRegex = new Regex(@"(?\{absolute(?:\:0+)?})", - RegexOptions.Compiled | RegexOptions.IgnoreCase); - - public static readonly Regex SeasonEpisodePatternRegex = new Regex(@"(?(?<=})[- ._]+?)?(?s?{season(?:\:0+)?}(?[- ._]?[ex])(?{episode(?:\:0+)?}))(?[- ._]+?(?={))?", - RegexOptions.Compiled | RegexOptions.IgnoreCase); - - public static readonly Regex AbsoluteEpisodePatternRegex = new Regex(@"(?(?<=})[- ._]+?)?(?{absolute(?:\:0+)?})(?[- ._]+?(?={))?", - RegexOptions.Compiled | RegexOptions.IgnoreCase); - - public static readonly Regex AirDateRegex = new Regex(@"\{Air(\s|\W|_)Date\}", RegexOptions.Compiled | RegexOptions.IgnoreCase); - - public static readonly Regex SeriesTitleRegex = new Regex(@"(?\{(?:Series)(?[- ._])(Clean)?Title\})", - RegexOptions.Compiled | RegexOptions.IgnoreCase); - - public static readonly Regex MovieTitleRegex = new Regex(@"(?\{((?:(Movie|Original))(?[- ._])(Clean)?(Title|Filename)(The)?)\})", - RegexOptions.Compiled | RegexOptions.IgnoreCase); - - private static readonly Regex FileNameCleanupRegex = new Regex(@"([- ._])(\1)+", RegexOptions.Compiled); - private static readonly Regex TrimSeparatorsRegex = new Regex(@"[- ._]$", RegexOptions.Compiled); - - private static readonly Regex ScenifyRemoveChars = new Regex(@"(?<=\s)(,|<|>|\/|\\|;|:|'|""|\||`|~|!|\?|@|$|%|^|\*|-|_|=){1}(?=\s)|('|:|\?|,)(?=(?:(?:s|m)\s)|\s|$)|(\(|\)|\[|\]|\{|\})", RegexOptions.Compiled | RegexOptions.IgnoreCase); - private static readonly Regex ScenifyReplaceChars = new Regex(@"[\/]", RegexOptions.Compiled | RegexOptions.IgnoreCase); - - //TODO: Support Written numbers (One, Two, etc) and Roman Numerals (I, II, III etc) - private static readonly Regex MultiPartCleanupRegex = new Regex(@"(?:\(\d+\)|(Part|Pt\.?)\s?\d+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase); - - private static readonly char[] EpisodeTitleTrimCharacters = new[] { ' ', '.', '?' }; - - public FileNameBuilder(INamingConfigService namingConfigService, - IQualityDefinitionService qualityDefinitionService, - ICacheManager cacheManager, - Logger logger) - { - _namingConfigService = namingConfigService; - _qualityDefinitionService = qualityDefinitionService; - //_movieFormatCache = cacheManager.GetCache(GetType(), "movieFormat"); - _episodeFormatCache = cacheManager.GetCache(GetType(), "episodeFormat"); - _absoluteEpisodeFormatCache = cacheManager.GetCache(GetType(), "absoluteEpisodeFormat"); - _logger = logger; - } - - public string BuildFileName(Movie movie, MovieFile movieFile, NamingConfig namingConfig = null) - { - if (namingConfig == null) - { - namingConfig = _namingConfigService.GetConfig(); - } - - if (!namingConfig.RenameEpisodes) - { - return GetOriginalTitle(movieFile); - } - - var pattern = namingConfig.StandardMovieFormat; - var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); - - AddMovieTokens(tokenHandlers, movie); - AddReleaseDateTokens(tokenHandlers, movie.Year); - AddImdbIdTokens(tokenHandlers, movie.ImdbId); - AddQualityTokens(tokenHandlers, movie, movieFile); - AddMediaInfoTokens(tokenHandlers, movieFile); - AddMovieFileTokens(tokenHandlers, movieFile); - AddTagsTokens(tokenHandlers, movieFile); - - var fileName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim(); - fileName = FileNameCleanupRegex.Replace(fileName, match => match.Captures[0].Value[0].ToString()); - fileName = TrimSeparatorsRegex.Replace(fileName, string.Empty); - - return fileName; - } - - public string BuildFilePath(Movie movie, string fileName, string extension) - { - Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace(); - - var path = ""; - - if (movie.PathState > 0) - { - path = movie.Path; - } - else - { - path = BuildMoviePath(movie); - } - - return Path.Combine(path, fileName + extension); - } - - public string BuildMoviePath(Movie movie, NamingConfig namingConfig = null) - { - if (namingConfig == null) - { - namingConfig = _namingConfigService.GetConfig(); - } - - var path = movie.Path; - var directory = new DirectoryInfo(path).Name; - var parentDirectoryPath = new DirectoryInfo(path).Parent.FullName; - - var movieFile = movie.MovieFile; - - var pattern = namingConfig.MovieFolderFormat; - var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); - - AddMovieTokens(tokenHandlers, movie); - AddReleaseDateTokens(tokenHandlers, movie.Year); - AddImdbIdTokens(tokenHandlers, movie.ImdbId); - - if(movie.MovieFile != null) - { - - AddQualityTokens(tokenHandlers, movie, movieFile); - AddMediaInfoTokens(tokenHandlers, movieFile); - AddMovieFileTokens(tokenHandlers, movieFile); - AddTagsTokens(tokenHandlers, movieFile); - } - else - { - AddMovieFileTokens(tokenHandlers, new MovieFile { SceneName = $"{movie.Title} {movie.Year}", RelativePath = $"{movie.Title} {movie.Year}" }); - } - - - var directoryName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim(); - directoryName = FileNameCleanupRegex.Replace(directoryName, match => match.Captures[0].Value[0].ToString()); - directoryName = TrimSeparatorsRegex.Replace(directoryName, string.Empty); - - return Path.Combine(parentDirectoryPath, directoryName); - } - - public BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec) - { - return new BasicNamingConfig(); //For now let's be lazy - - //var episodeFormat = GetEpisodeFormat(nameSpec.StandardMovieFormat).LastOrDefault(); - - //if (episodeFormat == null) - //{ - // return new BasicNamingConfig(); - //} - - //var basicNamingConfig = new BasicNamingConfig - //{ - // Separator = episodeFormat.Separator, - // NumberStyle = episodeFormat.SeasonEpisodePattern - //}; - - //var titleTokens = TitleRegex.Matches(nameSpec.StandardMovieFormat); - - //foreach (Match match in titleTokens) - //{ - // var separator = match.Groups["separator"].Value; - // var token = match.Groups["token"].Value; - - // if (!separator.Equals(" ")) - // { - // basicNamingConfig.ReplaceSpaces = true; - // } - - // if (token.StartsWith("{Series", StringComparison.InvariantCultureIgnoreCase)) - // { - // basicNamingConfig.IncludeSeriesTitle = true; - // } - - // if (token.StartsWith("{Episode", StringComparison.InvariantCultureIgnoreCase)) - // { - // basicNamingConfig.IncludeEpisodeTitle = true; - // } - - // if (token.StartsWith("{Quality", StringComparison.InvariantCultureIgnoreCase)) - // { - // basicNamingConfig.IncludeQuality = true; - // } - //} - - //return basicNamingConfig; - } - - public string GetMovieFolder(Movie movie, NamingConfig namingConfig = null) - { - if (namingConfig == null) - { - namingConfig = _namingConfigService.GetConfig(); - } - - var movieFile = movie.MovieFile; - - var pattern = namingConfig.MovieFolderFormat; - var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); - - AddMovieTokens(tokenHandlers, movie); - AddReleaseDateTokens(tokenHandlers, movie.Year); - AddImdbIdTokens(tokenHandlers, movie.ImdbId); - - if (movie.MovieFile != null) - { - AddQualityTokens(tokenHandlers, movie, movieFile); - AddMediaInfoTokens(tokenHandlers, movieFile); - AddMovieFileTokens(tokenHandlers, movieFile); - AddTagsTokens(tokenHandlers, movieFile); - } - else - { - AddMovieFileTokens(tokenHandlers, new MovieFile { SceneName = $"{movie.Title} {movie.Year}", RelativePath = $"{movie.Title} {movie.Year}"}); - } - - string name = ReplaceTokens(namingConfig.MovieFolderFormat, tokenHandlers, namingConfig); - return CleanFolderName(name, namingConfig.ReplaceIllegalCharacters, namingConfig.ColonReplacementFormat); - } - - public static string CleanTitle(string title) - { - title = title.Replace("&", "and"); - title = ScenifyReplaceChars.Replace(title, " "); - title = ScenifyRemoveChars.Replace(title, string.Empty); - - return title; - } - - public static string TitleThe(string title) - { - string[] prefixes = { "The ", "An ", "A " }; - - if (title.Length < 5) - { - return title; - } - - foreach (string prefix in prefixes) - { - int prefix_length = prefix.Length; - if (prefix.ToLower() == title.Substring(0, prefix_length).ToLower()) - { - title = title.Substring(prefix_length) + ", " + prefix.Trim(); - break; - } - } - - return title.Trim(); - } - - public static string CleanFileName(string name, bool replace = true, ColonReplacementFormat colonReplacement = ColonReplacementFormat.Delete) - { - var colonReplacementFormat = colonReplacement.GetFormatString(); - - string result = name; - string[] badCharacters = { "\\", "/", "<", ">", "?", "*", ":", "|", "\"" }; - string[] goodCharacters = { "+", "+", "", "", "!", "-", colonReplacementFormat, "", "" }; - - for (int i = 0; i < badCharacters.Length; i++) - { - result = result.Replace(badCharacters[i], replace ? goodCharacters[i] : string.Empty); - } - - return result.Trim(); - } - - public static string CleanFolderName(string name, bool replace = true, ColonReplacementFormat colonReplacement = ColonReplacementFormat.Delete) - { - name = FileNameCleanupRegex.Replace(name, match => match.Captures[0].Value[0].ToString()); - name = name.Trim(' ', '.'); - - return CleanFileName(name, replace, colonReplacement); - } - - private void AddMovieTokens(Dictionary> tokenHandlers, Movie movie) - { - tokenHandlers["{Movie Title}"] = m => movie.Title; - tokenHandlers["{Movie CleanTitle}"] = m => CleanTitle(movie.Title); - tokenHandlers["{Movie Title The}"] = m => TitleThe(movie.Title); - } - - private void AddTagsTokens(Dictionary> tokenHandlers, MovieFile movieFile) - { - if (movieFile.Edition.IsNotNullOrWhiteSpace()) - { - tokenHandlers["{Edition Tags}"] = m => CultureInfo.CurrentCulture.TextInfo.ToTitleCase(movieFile.Edition.ToLower()); - } - } - - private void AddReleaseDateTokens(Dictionary> tokenHandlers, int releaseYear) - { - tokenHandlers["{Release Year}"] = m => string.Format("{0}", releaseYear.ToString()); //Do I need m.CustomFormat? - } - - private void AddImdbIdTokens(Dictionary> tokenHandlers, string imdbId) - { - tokenHandlers["{IMDb Id}"] = m => $"{imdbId}"; - } - - private void AddMovieFileTokens(Dictionary> tokenHandlers, MovieFile movieFile) - { - tokenHandlers["{Original Title}"] = m => GetOriginalTitle(movieFile); - tokenHandlers["{Original Filename}"] = m => GetOriginalFileName(movieFile); - //tokenHandlers["{IMDb Id}"] = m => - tokenHandlers["{Release Group}"] = m => movieFile.ReleaseGroup ?? m.DefaultValue("Radarr"); - } - - private void AddQualityTokens(Dictionary> tokenHandlers, Movie movie, MovieFile movieFile) - { - if (movieFile?.Quality?.Quality == null) - { - tokenHandlers["{Quality Full}"] = m => ""; - tokenHandlers["{Quality Title}"] = m => ""; - tokenHandlers["{Quality Proper}"] = m => ""; - tokenHandlers["{Quality Real}"] = m => ""; - return; - } - - var qualityTitle = _qualityDefinitionService.Get(movieFile.Quality.Quality).Title; - var qualityProper = GetQualityProper(movie, movieFile.Quality); - var qualityReal = GetQualityReal(movie, movieFile.Quality); - - tokenHandlers["{Quality Full}"] = m => String.Format("{0} {1} {2}", qualityTitle, qualityProper, qualityReal); - tokenHandlers["{Quality Title}"] = m => qualityTitle; - tokenHandlers["{Quality Proper}"] = m => qualityProper; - tokenHandlers["{Quality Real}"] = m => qualityReal; - } - - private void AddMediaInfoTokens(Dictionary> tokenHandlers, MovieFile movieFile) - { - if (movieFile.MediaInfo == null) return; - - var sceneName = movieFile.GetSceneOrFileName(); - - var videoCodec = MediaInfoFormatter.FormatVideoCodec(movieFile.MediaInfo, sceneName); - var audioCodec = MediaInfoFormatter.FormatAudioCodec(movieFile.MediaInfo, sceneName); - var audioChannels = MediaInfoFormatter.FormatAudioChannels(movieFile.MediaInfo); - - // Workaround until https://github.com/MediaArea/MediaInfo/issues/299 is fixed and release - if (audioCodec.EqualsIgnoreCase("DTS-X")) - { - audioChannels = audioChannels - 1 + 0.1m; - } - - var mediaInfoAudioLanguages = GetLanguagesToken(movieFile.MediaInfo.AudioLanguages); - if (!mediaInfoAudioLanguages.IsNullOrWhiteSpace()) - { - mediaInfoAudioLanguages = $"[{mediaInfoAudioLanguages}]"; - } - var mediaInfoAudioLanguagesAll = mediaInfoAudioLanguages; - if (mediaInfoAudioLanguages == "[EN]") - { - mediaInfoAudioLanguages = string.Empty; - } - - var mediaInfoSubtitleLanguages = GetLanguagesToken(movieFile.MediaInfo.Subtitles); - if (!mediaInfoSubtitleLanguages.IsNullOrWhiteSpace()) - { - mediaInfoSubtitleLanguages = $"[{mediaInfoSubtitleLanguages}]"; - } - - var videoBitDepth = movieFile.MediaInfo.VideoBitDepth > 0 ? movieFile.MediaInfo.VideoBitDepth.ToString() : string.Empty; - var audioChannelsFormatted = audioChannels > 0 ? - audioChannels.ToString("F1", CultureInfo.InvariantCulture) : - string.Empty; - - var mediaInfo3D = movieFile.MediaInfo.VideoMultiViewCount > 1 ? "3D" : string.Empty; - - var videoColourPrimaries = movieFile.MediaInfo.VideoColourPrimaries ?? string.Empty; - var videoTransferCharacteristics = movieFile.MediaInfo.VideoTransferCharacteristics ?? string.Empty; - var mediaInfoHDR = string.Empty; - - if (movieFile.MediaInfo.VideoBitDepth >= 10 && !videoColourPrimaries.IsNullOrWhiteSpace() && !videoTransferCharacteristics.IsNullOrWhiteSpace()) - { - string[] validTransferFunctions = new string[] { "PQ", "HLG" }; - - if (videoColourPrimaries.EqualsIgnoreCase("BT.2020") && validTransferFunctions.Any(videoTransferCharacteristics.Contains)) - { - mediaInfoHDR = "HDR"; - } - } - - tokenHandlers["{MediaInfo Video}"] = m => videoCodec; - tokenHandlers["{MediaInfo VideoCodec}"] = m => videoCodec; - tokenHandlers["{MediaInfo VideoBitDepth}"] = m => videoBitDepth; - - tokenHandlers["{MediaInfo Audio}"] = m => audioCodec; - tokenHandlers["{MediaInfo AudioCodec}"] = m => audioCodec; - tokenHandlers["{MediaInfo AudioChannels}"] = m => audioChannelsFormatted; - tokenHandlers["{MediaInfo AudioLanguages}"] = m => mediaInfoAudioLanguages; - tokenHandlers["{MediaInfo AudioLanguagesAll}"] = m => mediaInfoAudioLanguagesAll; - - tokenHandlers["{MediaInfo SubtitleLanguages}"] = m => mediaInfoSubtitleLanguages; - - tokenHandlers["{MediaInfo 3D}"] = m => mediaInfo3D; - tokenHandlers["{MediaInfo HDR}"] = m => mediaInfoHDR; - - tokenHandlers["{MediaInfo Simple}"] = m => $"{videoCodec} {audioCodec}"; - tokenHandlers["{MediaInfo Full}"] = m => $"{videoCodec} {audioCodec}{mediaInfoAudioLanguages} {mediaInfoSubtitleLanguages}"; - } - - private string GetLanguagesToken(string mediaInfoLanguages) - { - List tokens = new List(); - foreach (var item in mediaInfoLanguages.Split('/')) - { - if (!string.IsNullOrWhiteSpace(item)) - tokens.Add(item.Trim()); - } - - var cultures = System.Globalization.CultureInfo.GetCultures(System.Globalization.CultureTypes.NeutralCultures); - for (int i = 0; i < tokens.Count; i++) - { - try - { - var cultureInfo = cultures.FirstOrDefault(p => p.EnglishName == tokens[i]); - - if (cultureInfo != null) - tokens[i] = cultureInfo.TwoLetterISOLanguageName.ToUpper(); - } - catch - { - } - } - - return string.Join("+", tokens.Distinct()); - } - - private string ReplaceTokens(string pattern, Dictionary> tokenHandlers, NamingConfig namingConfig) - { - return TitleRegex.Replace(pattern, match => ReplaceToken(match, tokenHandlers, namingConfig)); - } - - private string ReplaceToken(Match match, Dictionary> tokenHandlers, NamingConfig namingConfig) - { - var tokenMatch = new TokenMatch - { - RegexMatch = match, - Prefix = match.Groups["prefix"].Value, - Separator = match.Groups["separator"].Value, - Suffix = match.Groups["suffix"].Value, - Token = match.Groups["token"].Value, - CustomFormat = match.Groups["customFormat"].Value - }; - - if (tokenMatch.CustomFormat.IsNullOrWhiteSpace()) - { - tokenMatch.CustomFormat = null; - } - - var tokenHandler = tokenHandlers.GetValueOrDefault(tokenMatch.Token, m => string.Empty); - - var replacementText = tokenHandler(tokenMatch).Trim(); - - if (tokenMatch.Token.All(t => !char.IsLetter(t) || char.IsLower(t))) - { - replacementText = replacementText.ToLower(); - } - else if (tokenMatch.Token.All(t => !char.IsLetter(t) || char.IsUpper(t))) - { - replacementText = replacementText.ToUpper(); - } - - if (!tokenMatch.Separator.IsNullOrWhiteSpace()) - { - replacementText = replacementText.Replace(" ", tokenMatch.Separator); - } - - replacementText = CleanFileName(replacementText, namingConfig.ReplaceIllegalCharacters, namingConfig.ColonReplacementFormat); - - if (!replacementText.IsNullOrWhiteSpace()) - { - replacementText = tokenMatch.Prefix + replacementText + tokenMatch.Suffix; - } - - return replacementText; - } - - private string ReplaceNumberToken(string token, int value) - { - var split = token.Trim('{', '}').Split(':'); - if (split.Length == 1) return value.ToString("0"); - - return value.ToString(split[1]); - } - - private EpisodeFormat[] GetEpisodeFormat(string pattern) - { - return _episodeFormatCache.Get(pattern, () => SeasonEpisodePatternRegex.Matches(pattern).OfType() - .Select(match => new EpisodeFormat - { - EpisodeSeparator = match.Groups["episodeSeparator"].Value, - Separator = match.Groups["separator"].Value, - EpisodePattern = match.Groups["episode"].Value, - SeasonEpisodePattern = match.Groups["seasonEpisode"].Value, - }).ToArray()); - } - - private string GetQualityProper(Movie movie, QualityModel quality) - { - if (quality.Revision.Version > 1) - { - return "Proper"; - } - - return String.Empty; - } - - private string GetQualityReal(Movie movie, QualityModel quality) - { - if (quality.Revision.Real > 0) - { - return "REAL"; - } - - return string.Empty; - } - - private string GetOriginalTitle(MovieFile movieFile) - { - if (movieFile.SceneName.IsNullOrWhiteSpace()) - { - return GetOriginalFileName(movieFile); - } - - return movieFile.SceneName; - } - - private string GetOriginalFileName(MovieFile movieFile) - { - if (movieFile.RelativePath.IsNullOrWhiteSpace()) - { - return Path.GetFileNameWithoutExtension(movieFile.Path); - } - - return Path.GetFileNameWithoutExtension(movieFile.RelativePath); - } - } - - internal sealed class TokenMatch - { - public Match RegexMatch { get; set; } - public string Prefix { get; set; } - public string Separator { get; set; } - public string Suffix { get; set; } - public string Token { get; set; } - public string CustomFormat { get; set; } - - public string DefaultValue(string defaultValue) - { - if (string.IsNullOrEmpty(Prefix) && string.IsNullOrEmpty(Suffix)) - { - return defaultValue; - } - else - { - return string.Empty; - } - } - } - - public enum MultiEpisodeStyle - { - Extend = 0, - Duplicate = 1, - Repeat = 2, - Scene = 3, - Range = 4, - PrefixedRange = 5 - } -} +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using NLog; +using NzbDrone.Common.Cache; +using NzbDrone.Common.EnsureThat; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.MediaFiles; +using NzbDrone.Core.MediaFiles.MediaInfo; +using NzbDrone.Core.Qualities; +using NzbDrone.Core.Movies; + +namespace NzbDrone.Core.Organizer +{ + public interface IBuildFileNames + { + string BuildFileName(Movie movie, MovieFile movieFile, NamingConfig namingConfig = null); + string BuildFilePath(Movie movie, string fileName, string extension); + string BuildMoviePath(Movie movie, NamingConfig namingConfig = null); + BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec); + string GetMovieFolder(Movie movie, NamingConfig namingConfig = null); + } + + public class FileNameBuilder : IBuildFileNames + { + private readonly INamingConfigService _namingConfigService; + private readonly IQualityDefinitionService _qualityDefinitionService; + private readonly ICached _episodeFormatCache; + private readonly ICached _absoluteEpisodeFormatCache; + private readonly Logger _logger; + + private static readonly Regex TitleRegex = new Regex(@"\{(?[- ._\[(]*)(?(?:[a-z0-9]+)(?:(?[- ._]+)(?:[a-z0-9]+))?)(?::(?[a-z0-9]+))?(?[- ._)\]]*)\}", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + private static readonly Regex EpisodeRegex = new Regex(@"(?\{episode(?:\:0+)?})", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + private static readonly Regex TagsRegex = new Regex(@"(?\{tags(?:\:0+)?})", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + private static readonly Regex SeasonRegex = new Regex(@"(?\{season(?:\:0+)?})", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + private static readonly Regex AbsoluteEpisodeRegex = new Regex(@"(?\{absolute(?:\:0+)?})", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + public static readonly Regex SeasonEpisodePatternRegex = new Regex(@"(?(?<=})[- ._]+?)?(?s?{season(?:\:0+)?}(?[- ._]?[ex])(?{episode(?:\:0+)?}))(?[- ._]+?(?={))?", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + public static readonly Regex AbsoluteEpisodePatternRegex = new Regex(@"(?(?<=})[- ._]+?)?(?{absolute(?:\:0+)?})(?[- ._]+?(?={))?", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + public static readonly Regex AirDateRegex = new Regex(@"\{Air(\s|\W|_)Date\}", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + public static readonly Regex SeriesTitleRegex = new Regex(@"(?\{(?:Series)(?[- ._])(Clean)?Title\})", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + public static readonly Regex MovieTitleRegex = new Regex(@"(?\{((?:(Movie|Original))(?[- ._])(Clean)?(Title|Filename)(The)?)\})", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + + private static readonly Regex FileNameCleanupRegex = new Regex(@"([- ._])(\1)+", RegexOptions.Compiled); + private static readonly Regex TrimSeparatorsRegex = new Regex(@"[- ._]$", RegexOptions.Compiled); + + private static readonly Regex ScenifyRemoveChars = new Regex(@"(?<=\s)(,|<|>|\/|\\|;|:|'|""|\||`|~|!|\?|@|$|%|^|\*|-|_|=){1}(?=\s)|('|:|\?|,)(?=(?:(?:s|m)\s)|\s|$)|(\(|\)|\[|\]|\{|\})", RegexOptions.Compiled | RegexOptions.IgnoreCase); + private static readonly Regex ScenifyReplaceChars = new Regex(@"[\/]", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + //TODO: Support Written numbers (One, Two, etc) and Roman Numerals (I, II, III etc) + private static readonly Regex MultiPartCleanupRegex = new Regex(@"(?:\(\d+\)|(Part|Pt\.?)\s?\d+)$", RegexOptions.Compiled | RegexOptions.IgnoreCase); + + private static readonly char[] EpisodeTitleTrimCharacters = new[] { ' ', '.', '?' }; + + public FileNameBuilder(INamingConfigService namingConfigService, + IQualityDefinitionService qualityDefinitionService, + ICacheManager cacheManager, + Logger logger) + { + _namingConfigService = namingConfigService; + _qualityDefinitionService = qualityDefinitionService; + //_movieFormatCache = cacheManager.GetCache(GetType(), "movieFormat"); + _episodeFormatCache = cacheManager.GetCache(GetType(), "episodeFormat"); + _absoluteEpisodeFormatCache = cacheManager.GetCache(GetType(), "absoluteEpisodeFormat"); + _logger = logger; + } + + public string BuildFileName(Movie movie, MovieFile movieFile, NamingConfig namingConfig = null) + { + if (namingConfig == null) + { + namingConfig = _namingConfigService.GetConfig(); + } + + if (!namingConfig.RenameEpisodes) + { + return GetOriginalTitle(movieFile); + } + + var pattern = namingConfig.StandardMovieFormat; + var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); + + AddMovieTokens(tokenHandlers, movie); + AddReleaseDateTokens(tokenHandlers, movie.Year); + AddImdbIdTokens(tokenHandlers, movie.ImdbId); + AddQualityTokens(tokenHandlers, movie, movieFile); + AddMediaInfoTokens(tokenHandlers, movieFile); + AddMovieFileTokens(tokenHandlers, movieFile); + AddTagsTokens(tokenHandlers, movieFile); + + var fileName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim(); + fileName = FileNameCleanupRegex.Replace(fileName, match => match.Captures[0].Value[0].ToString()); + fileName = TrimSeparatorsRegex.Replace(fileName, string.Empty); + + return fileName; + } + + public string BuildFilePath(Movie movie, string fileName, string extension) + { + Ensure.That(extension, () => extension).IsNotNullOrWhiteSpace(); + + var path = ""; + + if (movie.PathState > 0) + { + path = movie.Path; + } + else + { + path = BuildMoviePath(movie); + } + + return Path.Combine(path, fileName + extension); + } + + public string BuildMoviePath(Movie movie, NamingConfig namingConfig = null) + { + if (namingConfig == null) + { + namingConfig = _namingConfigService.GetConfig(); + } + + var path = movie.Path; + var directory = new DirectoryInfo(path).Name; + var parentDirectoryPath = new DirectoryInfo(path).Parent.FullName; + + var movieFile = movie.MovieFile; + + var pattern = namingConfig.MovieFolderFormat; + var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); + + AddMovieTokens(tokenHandlers, movie); + AddReleaseDateTokens(tokenHandlers, movie.Year); + AddImdbIdTokens(tokenHandlers, movie.ImdbId); + + if(movie.MovieFile != null) + { + + AddQualityTokens(tokenHandlers, movie, movieFile); + AddMediaInfoTokens(tokenHandlers, movieFile); + AddMovieFileTokens(tokenHandlers, movieFile); + AddTagsTokens(tokenHandlers, movieFile); + } + else + { + AddMovieFileTokens(tokenHandlers, new MovieFile { SceneName = $"{movie.Title} {movie.Year}", RelativePath = $"{movie.Title} {movie.Year}" }); + } + + + var directoryName = ReplaceTokens(pattern, tokenHandlers, namingConfig).Trim(); + directoryName = FileNameCleanupRegex.Replace(directoryName, match => match.Captures[0].Value[0].ToString()); + directoryName = TrimSeparatorsRegex.Replace(directoryName, string.Empty); + + return Path.Combine(parentDirectoryPath, directoryName); + } + + public BasicNamingConfig GetBasicNamingConfig(NamingConfig nameSpec) + { + return new BasicNamingConfig(); //For now let's be lazy + + //var episodeFormat = GetEpisodeFormat(nameSpec.StandardMovieFormat).LastOrDefault(); + + //if (episodeFormat == null) + //{ + // return new BasicNamingConfig(); + //} + + //var basicNamingConfig = new BasicNamingConfig + //{ + // Separator = episodeFormat.Separator, + // NumberStyle = episodeFormat.SeasonEpisodePattern + //}; + + //var titleTokens = TitleRegex.Matches(nameSpec.StandardMovieFormat); + + //foreach (Match match in titleTokens) + //{ + // var separator = match.Groups["separator"].Value; + // var token = match.Groups["token"].Value; + + // if (!separator.Equals(" ")) + // { + // basicNamingConfig.ReplaceSpaces = true; + // } + + // if (token.StartsWith("{Series", StringComparison.InvariantCultureIgnoreCase)) + // { + // basicNamingConfig.IncludeSeriesTitle = true; + // } + + // if (token.StartsWith("{Episode", StringComparison.InvariantCultureIgnoreCase)) + // { + // basicNamingConfig.IncludeEpisodeTitle = true; + // } + + // if (token.StartsWith("{Quality", StringComparison.InvariantCultureIgnoreCase)) + // { + // basicNamingConfig.IncludeQuality = true; + // } + //} + + //return basicNamingConfig; + } + + public string GetMovieFolder(Movie movie, NamingConfig namingConfig = null) + { + if (namingConfig == null) + { + namingConfig = _namingConfigService.GetConfig(); + } + + var movieFile = movie.MovieFile; + + var pattern = namingConfig.MovieFolderFormat; + var tokenHandlers = new Dictionary>(FileNameBuilderTokenEqualityComparer.Instance); + + AddMovieTokens(tokenHandlers, movie); + AddReleaseDateTokens(tokenHandlers, movie.Year); + AddImdbIdTokens(tokenHandlers, movie.ImdbId); + + if (movie.MovieFile != null) + { + AddQualityTokens(tokenHandlers, movie, movieFile); + AddMediaInfoTokens(tokenHandlers, movieFile); + AddMovieFileTokens(tokenHandlers, movieFile); + AddTagsTokens(tokenHandlers, movieFile); + } + else + { + AddMovieFileTokens(tokenHandlers, new MovieFile { SceneName = $"{movie.Title} {movie.Year}", RelativePath = $"{movie.Title} {movie.Year}"}); + } + + string name = ReplaceTokens(namingConfig.MovieFolderFormat, tokenHandlers, namingConfig); + return CleanFolderName(name, namingConfig.ReplaceIllegalCharacters, namingConfig.ColonReplacementFormat); + } + + public static string CleanTitle(string title) + { + title = title.Replace("&", "and"); + title = ScenifyReplaceChars.Replace(title, " "); + title = ScenifyRemoveChars.Replace(title, string.Empty); + + return title; + } + + public static string TitleThe(string title) + { + string[] prefixes = { "The ", "An ", "A " }; + + if (title.Length < 5) + { + return title; + } + + foreach (string prefix in prefixes) + { + int prefix_length = prefix.Length; + if (prefix.ToLower() == title.Substring(0, prefix_length).ToLower()) + { + title = title.Substring(prefix_length) + ", " + prefix.Trim(); + break; + } + } + + return title.Trim(); + } + + public static string CleanFileName(string name, bool replace = true, ColonReplacementFormat colonReplacement = ColonReplacementFormat.Delete) + { + var colonReplacementFormat = colonReplacement.GetFormatString(); + + string result = name; + string[] badCharacters = { "\\", "/", "<", ">", "?", "*", ":", "|", "\"" }; + string[] goodCharacters = { "+", "+", "", "", "!", "-", colonReplacementFormat, "", "" }; + + for (int i = 0; i < badCharacters.Length; i++) + { + result = result.Replace(badCharacters[i], replace ? goodCharacters[i] : string.Empty); + } + + return result.Trim(); + } + + public static string CleanFolderName(string name, bool replace = true, ColonReplacementFormat colonReplacement = ColonReplacementFormat.Delete) + { + name = FileNameCleanupRegex.Replace(name, match => match.Captures[0].Value[0].ToString()); + name = name.Trim(' ', '.'); + + return CleanFileName(name, replace, colonReplacement); + } + + private void AddMovieTokens(Dictionary> tokenHandlers, Movie movie) + { + tokenHandlers["{Movie Title}"] = m => movie.Title; + tokenHandlers["{Movie CleanTitle}"] = m => CleanTitle(movie.Title); + tokenHandlers["{Movie Title The}"] = m => TitleThe(movie.Title); + } + + private void AddTagsTokens(Dictionary> tokenHandlers, MovieFile movieFile) + { + if (movieFile.Edition.IsNotNullOrWhiteSpace()) + { + tokenHandlers["{Edition Tags}"] = m => CultureInfo.CurrentCulture.TextInfo.ToTitleCase(movieFile.Edition.ToLower()); + } + } + + private void AddReleaseDateTokens(Dictionary> tokenHandlers, int releaseYear) + { + tokenHandlers["{Release Year}"] = m => string.Format("{0}", releaseYear.ToString()); //Do I need m.CustomFormat? + } + + private void AddImdbIdTokens(Dictionary> tokenHandlers, string imdbId) + { + tokenHandlers["{IMDb Id}"] = m => $"{imdbId}"; + } + + private void AddMovieFileTokens(Dictionary> tokenHandlers, MovieFile movieFile) + { + tokenHandlers["{Original Title}"] = m => GetOriginalTitle(movieFile); + tokenHandlers["{Original Filename}"] = m => GetOriginalFileName(movieFile); + //tokenHandlers["{IMDb Id}"] = m => + tokenHandlers["{Release Group}"] = m => movieFile.ReleaseGroup ?? m.DefaultValue("Radarr"); + } + + private void AddQualityTokens(Dictionary> tokenHandlers, Movie movie, MovieFile movieFile) + { + if (movieFile?.Quality?.Quality == null) + { + tokenHandlers["{Quality Full}"] = m => ""; + tokenHandlers["{Quality Title}"] = m => ""; + tokenHandlers["{Quality Proper}"] = m => ""; + tokenHandlers["{Quality Real}"] = m => ""; + return; + } + + var qualityTitle = _qualityDefinitionService.Get(movieFile.Quality.Quality).Title; + var qualityProper = GetQualityProper(movie, movieFile.Quality); + var qualityReal = GetQualityReal(movie, movieFile.Quality); + + tokenHandlers["{Quality Full}"] = m => String.Format("{0} {1} {2}", qualityTitle, qualityProper, qualityReal); + tokenHandlers["{Quality Title}"] = m => qualityTitle; + tokenHandlers["{Quality Proper}"] = m => qualityProper; + tokenHandlers["{Quality Real}"] = m => qualityReal; + } + + private void AddMediaInfoTokens(Dictionary> tokenHandlers, MovieFile movieFile) + { + if (movieFile.MediaInfo == null) return; + + var sceneName = movieFile.GetSceneOrFileName(); + + var videoCodec = MediaInfoFormatter.FormatVideoCodec(movieFile.MediaInfo, sceneName); + var audioCodec = MediaInfoFormatter.FormatAudioCodec(movieFile.MediaInfo, sceneName); + var audioChannels = MediaInfoFormatter.FormatAudioChannels(movieFile.MediaInfo); + + // Workaround until https://github.com/MediaArea/MediaInfo/issues/299 is fixed and release + if (audioCodec.EqualsIgnoreCase("DTS-X")) + { + audioChannels = audioChannels - 1 + 0.1m; + } + + var mediaInfoAudioLanguages = GetLanguagesToken(movieFile.MediaInfo.AudioLanguages); + if (!mediaInfoAudioLanguages.IsNullOrWhiteSpace()) + { + mediaInfoAudioLanguages = $"[{mediaInfoAudioLanguages}]"; + } + var mediaInfoAudioLanguagesAll = mediaInfoAudioLanguages; + if (mediaInfoAudioLanguages == "[EN]") + { + mediaInfoAudioLanguages = string.Empty; + } + + var mediaInfoSubtitleLanguages = GetLanguagesToken(movieFile.MediaInfo.Subtitles); + if (!mediaInfoSubtitleLanguages.IsNullOrWhiteSpace()) + { + mediaInfoSubtitleLanguages = $"[{mediaInfoSubtitleLanguages}]"; + } + + var videoBitDepth = movieFile.MediaInfo.VideoBitDepth > 0 ? movieFile.MediaInfo.VideoBitDepth.ToString() : string.Empty; + var audioChannelsFormatted = audioChannels > 0 ? + audioChannels.ToString("F1", CultureInfo.InvariantCulture) : + string.Empty; + + var mediaInfo3D = movieFile.MediaInfo.VideoMultiViewCount > 1 ? "3D" : string.Empty; + + var videoColourPrimaries = movieFile.MediaInfo.VideoColourPrimaries ?? string.Empty; + var videoTransferCharacteristics = movieFile.MediaInfo.VideoTransferCharacteristics ?? string.Empty; + var mediaInfoHDR = string.Empty; + + if (movieFile.MediaInfo.VideoBitDepth >= 10 && !videoColourPrimaries.IsNullOrWhiteSpace() && !videoTransferCharacteristics.IsNullOrWhiteSpace()) + { + string[] validTransferFunctions = new string[] { "PQ", "HLG" }; + + if (videoColourPrimaries.EqualsIgnoreCase("BT.2020") && validTransferFunctions.Any(videoTransferCharacteristics.Contains)) + { + mediaInfoHDR = "HDR"; + } + } + + tokenHandlers["{MediaInfo Video}"] = m => videoCodec; + tokenHandlers["{MediaInfo VideoCodec}"] = m => videoCodec; + tokenHandlers["{MediaInfo VideoBitDepth}"] = m => videoBitDepth; + + tokenHandlers["{MediaInfo Audio}"] = m => audioCodec; + tokenHandlers["{MediaInfo AudioCodec}"] = m => audioCodec; + tokenHandlers["{MediaInfo AudioChannels}"] = m => audioChannelsFormatted; + tokenHandlers["{MediaInfo AudioLanguages}"] = m => mediaInfoAudioLanguages; + tokenHandlers["{MediaInfo AudioLanguagesAll}"] = m => mediaInfoAudioLanguagesAll; + + tokenHandlers["{MediaInfo SubtitleLanguages}"] = m => mediaInfoSubtitleLanguages; + + tokenHandlers["{MediaInfo 3D}"] = m => mediaInfo3D; + tokenHandlers["{MediaInfo HDR}"] = m => mediaInfoHDR; + + tokenHandlers["{MediaInfo Simple}"] = m => $"{videoCodec} {audioCodec}"; + tokenHandlers["{MediaInfo Full}"] = m => $"{videoCodec} {audioCodec}{mediaInfoAudioLanguages} {mediaInfoSubtitleLanguages}"; + } + + private string GetLanguagesToken(string mediaInfoLanguages) + { + List tokens = new List(); + foreach (var item in mediaInfoLanguages.Split('/')) + { + if (!string.IsNullOrWhiteSpace(item)) + tokens.Add(item.Trim()); + } + + var cultures = System.Globalization.CultureInfo.GetCultures(System.Globalization.CultureTypes.NeutralCultures); + for (int i = 0; i < tokens.Count; i++) + { + try + { + var cultureInfo = cultures.FirstOrDefault(p => p.EnglishName == tokens[i]); + + if (cultureInfo != null) + tokens[i] = cultureInfo.TwoLetterISOLanguageName.ToUpper(); + } + catch + { + } + } + + return string.Join("+", tokens.Distinct()); + } + + private string ReplaceTokens(string pattern, Dictionary> tokenHandlers, NamingConfig namingConfig) + { + return TitleRegex.Replace(pattern, match => ReplaceToken(match, tokenHandlers, namingConfig)); + } + + private string ReplaceToken(Match match, Dictionary> tokenHandlers, NamingConfig namingConfig) + { + var tokenMatch = new TokenMatch + { + RegexMatch = match, + Prefix = match.Groups["prefix"].Value, + Separator = match.Groups["separator"].Value, + Suffix = match.Groups["suffix"].Value, + Token = match.Groups["token"].Value, + CustomFormat = match.Groups["customFormat"].Value + }; + + if (tokenMatch.CustomFormat.IsNullOrWhiteSpace()) + { + tokenMatch.CustomFormat = null; + } + + var tokenHandler = tokenHandlers.GetValueOrDefault(tokenMatch.Token, m => string.Empty); + + var replacementText = tokenHandler(tokenMatch).Trim(); + + if (tokenMatch.Token.All(t => !char.IsLetter(t) || char.IsLower(t))) + { + replacementText = replacementText.ToLower(); + } + else if (tokenMatch.Token.All(t => !char.IsLetter(t) || char.IsUpper(t))) + { + replacementText = replacementText.ToUpper(); + } + + if (!tokenMatch.Separator.IsNullOrWhiteSpace()) + { + replacementText = replacementText.Replace(" ", tokenMatch.Separator); + } + + replacementText = CleanFileName(replacementText, namingConfig.ReplaceIllegalCharacters, namingConfig.ColonReplacementFormat); + + if (!replacementText.IsNullOrWhiteSpace()) + { + replacementText = tokenMatch.Prefix + replacementText + tokenMatch.Suffix; + } + + return replacementText; + } + + private string ReplaceNumberToken(string token, int value) + { + var split = token.Trim('{', '}').Split(':'); + if (split.Length == 1) return value.ToString("0"); + + return value.ToString(split[1]); + } + + private EpisodeFormat[] GetEpisodeFormat(string pattern) + { + return _episodeFormatCache.Get(pattern, () => SeasonEpisodePatternRegex.Matches(pattern).OfType() + .Select(match => new EpisodeFormat + { + EpisodeSeparator = match.Groups["episodeSeparator"].Value, + Separator = match.Groups["separator"].Value, + EpisodePattern = match.Groups["episode"].Value, + SeasonEpisodePattern = match.Groups["seasonEpisode"].Value, + }).ToArray()); + } + + private string GetQualityProper(Movie movie, QualityModel quality) + { + if (quality.Revision.Version > 1) + { + return "Proper"; + } + + return String.Empty; + } + + private string GetQualityReal(Movie movie, QualityModel quality) + { + if (quality.Revision.Real > 0) + { + return "REAL"; + } + + return string.Empty; + } + + private string GetOriginalTitle(MovieFile movieFile) + { + if (movieFile.SceneName.IsNullOrWhiteSpace()) + { + return GetOriginalFileName(movieFile); + } + + return movieFile.SceneName; + } + + private string GetOriginalFileName(MovieFile movieFile) + { + if (movieFile.RelativePath.IsNullOrWhiteSpace()) + { + return Path.GetFileNameWithoutExtension(movieFile.Path); + } + + return Path.GetFileNameWithoutExtension(movieFile.RelativePath); + } + } + + internal sealed class TokenMatch + { + public Match RegexMatch { get; set; } + public string Prefix { get; set; } + public string Separator { get; set; } + public string Suffix { get; set; } + public string Token { get; set; } + public string CustomFormat { get; set; } + + public string DefaultValue(string defaultValue) + { + if (string.IsNullOrEmpty(Prefix) && string.IsNullOrEmpty(Suffix)) + { + return defaultValue; + } + else + { + return string.Empty; + } + } + } + + public enum MultiEpisodeStyle + { + Extend = 0, + Duplicate = 1, + Repeat = 2, + Scene = 3, + Range = 4, + PrefixedRange = 5 + } +} diff --git a/src/NzbDrone.Core/Validation/Paths/MoviePathValidation.cs b/src/NzbDrone.Core/Validation/Paths/MoviePathValidation.cs index 41690b7cbd..ee2d55dcae 100644 --- a/src/NzbDrone.Core/Validation/Paths/MoviePathValidation.cs +++ b/src/NzbDrone.Core/Validation/Paths/MoviePathValidation.cs @@ -1,27 +1,27 @@ -using FluentValidation.Validators; -using NzbDrone.Common.Extensions; -using NzbDrone.Core.Movies; - -namespace NzbDrone.Core.Validation.Paths -{ - public class MoviePathValidator : PropertyValidator - { - private readonly IMovieService _moviesService; - - public MoviePathValidator(IMovieService moviesService) - : base("Path is already configured for another movie") - { - _moviesService = moviesService; - } - - protected override bool IsValid(PropertyValidatorContext context) - { - if (context.PropertyValue == null) return true; - - dynamic instance = context.ParentContext.InstanceToValidate; - var instanceId = (int)instance.Id; - - return (!_moviesService.GetAllMovies().Exists(s => s.Path.PathEquals(context.PropertyValue.ToString()) && s.Id != instanceId)); - } - } +using FluentValidation.Validators; +using NzbDrone.Common.Extensions; +using NzbDrone.Core.Movies; + +namespace NzbDrone.Core.Validation.Paths +{ + public class MoviePathValidator : PropertyValidator + { + private readonly IMovieService _moviesService; + + public MoviePathValidator(IMovieService moviesService) + : base("Path is already configured for another movie") + { + _moviesService = moviesService; + } + + protected override bool IsValid(PropertyValidatorContext context) + { + if (context.PropertyValue == null) return true; + + dynamic instance = context.ParentContext.InstanceToValidate; + var instanceId = (int)instance.Id; + + return (!_moviesService.GetAllMovies().Exists(s => s.Path.PathEquals(context.PropertyValue.ToString()) && s.Id != instanceId)); + } + } } \ No newline at end of file diff --git a/src/NzbDrone.Core/packages.config b/src/NzbDrone.Core/packages.config index 9edadfdf63..4502d1b3ec 100644 --- a/src/NzbDrone.Core/packages.config +++ b/src/NzbDrone.Core/packages.config @@ -1,15 +1,15 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/NzbDrone.Host/NzbDrone.Host.csproj b/src/NzbDrone.Host/NzbDrone.Host.csproj index ff7a2b57c2..7fbce7dbef 100644 --- a/src/NzbDrone.Host/NzbDrone.Host.csproj +++ b/src/NzbDrone.Host/NzbDrone.Host.csproj @@ -1,217 +1,217 @@ - - - - Debug - x86 - 8.0.30703 - 2.0 - {95C11A9E-56ED-456A-8447-2C89C1139266} - Library - Properties - Radarr.Host - Radarr.Host - v4.0 - 512 - - - false - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - true - ..\ - true - - - x86 - true - full - false - ..\..\_output\ - DEBUG;TRACE - prompt - 4 - true - BasicCorrectnessRules.ruleset - - - x86 - pdbonly - true - ..\..\_output\ - TRACE - prompt - 4 - - - OnOutputUpdated - - - Radarr.ico - - - - - False - ..\packages\Microsoft.Owin.2.1.0\lib\net40\Microsoft.Owin.dll - - - False - ..\packages\Microsoft.Owin.Host.HttpListener.2.1.0\lib\net40\Microsoft.Owin.Host.HttpListener.dll - - - False - ..\packages\Microsoft.Owin.Hosting.2.1.0\lib\net40\Microsoft.Owin.Hosting.dll - - - ..\packages\Nancy.1.4.3\lib\net40\Nancy.dll - True - - - ..\packages\Nancy.Owin.1.4.1\lib\net40\Nancy.Owin.dll - True - - - ..\packages\Newtonsoft.Json.9.0.1\lib\net40\Newtonsoft.Json.dll - - - ..\packages\NLog.4.5.0-rc06\lib\net40-client\NLog.dll - - - - - - - - - - ..\Libraries\Interop.NetFwTypeLib.dll - False - - - ..\packages\Owin.1.0\lib\net40\Owin.dll - - - - - - - Properties\SharedAssemblyInfo.cs - - - - - - - - - Component - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - False - Microsoft .NET Framework 4 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - false - - - False - Windows Installer 3.1 - true - - - - - {1B9A82C4-BCA1-4834-A33E-226F17BE070B} - Microsoft.AspNet.SignalR.Core - - - {2B8C6DAD-4D85-41B1-83FD-248D9F347522} - Microsoft.AspNet.SignalR.Owin - - - {FD286DF8-2D3A-4394-8AD5-443FADE55FB2} - NzbDrone.Api - - - {F2BE0FDF-6E47-4827-A420-DD4EF82407F8} - NzbDrone.Common - - - {FF5EE3B6-913B-47CE-9CEB-11C51B4E1205} - NzbDrone.Core - - - {7C2CC69F-5CA0-4E5C-85CB-983F9F6C3B36} - NzbDrone.SignalR - - - - - - - - - - - + + + + Debug + x86 + 8.0.30703 + 2.0 + {95C11A9E-56ED-456A-8447-2C89C1139266} + Library + Properties + Radarr.Host + Radarr.Host + v4.0 + 512 + + + false + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + true + ..\ + true + + + x86 + true + full + false + ..\..\_output\ + DEBUG;TRACE + prompt + 4 + true + BasicCorrectnessRules.ruleset + + + x86 + pdbonly + true + ..\..\_output\ + TRACE + prompt + 4 + + + OnOutputUpdated + + + Radarr.ico + + + + + False + ..\packages\Microsoft.Owin.2.1.0\lib\net40\Microsoft.Owin.dll + + + False + ..\packages\Microsoft.Owin.Host.HttpListener.2.1.0\lib\net40\Microsoft.Owin.Host.HttpListener.dll + + + False + ..\packages\Microsoft.Owin.Hosting.2.1.0\lib\net40\Microsoft.Owin.Hosting.dll + + + ..\packages\Nancy.1.4.3\lib\net40\Nancy.dll + True + + + ..\packages\Nancy.Owin.1.4.1\lib\net40\Nancy.Owin.dll + True + + + ..\packages\Newtonsoft.Json.9.0.1\lib\net40\Newtonsoft.Json.dll + + + ..\packages\NLog.4.5.0-rc06\lib\net40-client\NLog.dll + + + + + + + + + + ..\Libraries\Interop.NetFwTypeLib.dll + False + + + ..\packages\Owin.1.0\lib\net40\Owin.dll + + + + + + + Properties\SharedAssemblyInfo.cs + + + + + + + + + Component + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + False + Microsoft .NET Framework 4 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + false + + + False + Windows Installer 3.1 + true + + + + + {1B9A82C4-BCA1-4834-A33E-226F17BE070B} + Microsoft.AspNet.SignalR.Core + + + {2B8C6DAD-4D85-41B1-83FD-248D9F347522} + Microsoft.AspNet.SignalR.Owin + + + {FD286DF8-2D3A-4394-8AD5-443FADE55FB2} + NzbDrone.Api + + + {F2BE0FDF-6E47-4827-A420-DD4EF82407F8} + NzbDrone.Common + + + {FF5EE3B6-913B-47CE-9CEB-11C51B4E1205} + NzbDrone.Core + + + {7C2CC69F-5CA0-4E5C-85CB-983F9F6C3B36} + NzbDrone.SignalR + + + + + + + + + + + xcopy /s /y "$(SolutionDir)\Libraries\Sqlite\*.*" "$(TargetDir)" - + cp -rv $(SolutionDir)Libraries\Sqlite\*.* $(TargetDir) - - + + + --> \ No newline at end of file diff --git a/src/NzbDrone.Host/packages.config b/src/NzbDrone.Host/packages.config index c9daf9da8f..8603cacf93 100644 --- a/src/NzbDrone.Host/packages.config +++ b/src/NzbDrone.Host/packages.config @@ -1,11 +1,11 @@ - - - - - - - - - - + + + + + + + + + + \ No newline at end of file diff --git a/src/NzbDrone.Integration.Test/NzbDrone.Integration.Test.csproj b/src/NzbDrone.Integration.Test/NzbDrone.Integration.Test.csproj index a24ba785ed..290c589fc2 100644 --- a/src/NzbDrone.Integration.Test/NzbDrone.Integration.Test.csproj +++ b/src/NzbDrone.Integration.Test/NzbDrone.Integration.Test.csproj @@ -1,199 +1,199 @@ - - - - - Debug - x86 - {8CEFECD0-A6C2-498F-98B1-3FBE5820F9AB} - Library - Properties - NzbDrone.Integration.Test - NzbDrone.Integration.Test - v4.0 - 512 - ..\ - true - 12.0.0 - 2.0 - - - true - bin\x86\Debug\ - DEBUG;TRACE - full - x86 - prompt - MinimumRecommendedRules.ruleset - 4 - false - - - bin\x86\Release\ - TRACE - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset - 4 - - - - ..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.dll - True - - - ..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.Core.dll - True - - - ..\packages\FluentValidation.6.2.1.0\lib\portable-net40+sl50+wp80+win8+wpa81\FluentValidation.dll - True - - - ..\packages\Microsoft.AspNet.SignalR.Client.1.2.1\lib\net40\Microsoft.AspNet.SignalR.Client.dll - - - False - ..\packages\Microsoft.Owin.2.1.0\lib\net40\Microsoft.Owin.dll - - - ..\packages\Microsoft.Owin.Host.HttpListener.2.1.0\lib\net40\Microsoft.Owin.Host.HttpListener.dll - - - False - ..\packages\Microsoft.Owin.Hosting.2.1.0\lib\net40\Microsoft.Owin.Hosting.dll - - - ..\packages\Nancy.1.4.3\lib\net40\Nancy.dll - True - - - ..\packages\Nancy.Owin.1.4.1\lib\net40\Nancy.Owin.dll - True - - - ..\packages\Newtonsoft.Json.9.0.1\lib\net40\Newtonsoft.Json.dll - - - ..\packages\NLog.4.5.0-rc06\lib\net40-client\NLog.dll - - - ..\packages\NUnit.3.5.0\lib\net40\nunit.framework.dll - True - - - ..\packages\RestSharp.105.2.3\lib\net4\RestSharp.dll - True - - - - - - - - - - - - - - ..\packages\Moq.4.0.10827\lib\NET40\Moq.dll - - - ..\packages\Owin.1.0\lib\net40\Owin.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - App.config - - - - - - {FD286DF8-2D3A-4394-8AD5-443FADE55FB2} - NzbDrone.Api - - - {F2BE0FDF-6E47-4827-A420-DD4EF82407F8} - NzbDrone.Common - - - {FF5EE3B6-913B-47CE-9CEB-11C51B4E1205} - NzbDrone.Core - - - {95C11A9E-56ED-456A-8447-2C89C1139266} - NzbDrone.Host - - - {7C2CC69F-5CA0-4E5C-85CB-983F9F6C3B36} - NzbDrone.SignalR - - - {CADDFCE0-7509-4430-8364-2074E1EEFCA2} - NzbDrone.Test.Common - - - - - sqlite3.dll - Always - - - - - - - - - xcopy /s /y "$(SolutionDir)\..\_output\NzbDrone.Mono.*" "$(TargetDir)" - xcopy /s /y "$(SolutionDir)\..\_output\NzbDrone.Windows.*" "$(TargetDir)" - - - cp -rv $(SolutionDir)\..\_output\NzbDrone.Mono.* $(TargetDir) - cp -rv $(SolutionDir)\..\_output\NzbDrone.Windows.* $(TargetDir) - - - + + + + + Debug + x86 + {8CEFECD0-A6C2-498F-98B1-3FBE5820F9AB} + Library + Properties + NzbDrone.Integration.Test + NzbDrone.Integration.Test + v4.0 + 512 + ..\ + true + 12.0.0 + 2.0 + + + true + bin\x86\Debug\ + DEBUG;TRACE + full + x86 + prompt + MinimumRecommendedRules.ruleset + 4 + false + + + bin\x86\Release\ + TRACE + true + pdbonly + x86 + prompt + MinimumRecommendedRules.ruleset + 4 + + + + ..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.dll + True + + + ..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.Core.dll + True + + + ..\packages\FluentValidation.6.2.1.0\lib\portable-net40+sl50+wp80+win8+wpa81\FluentValidation.dll + True + + + ..\packages\Microsoft.AspNet.SignalR.Client.1.2.1\lib\net40\Microsoft.AspNet.SignalR.Client.dll + + + False + ..\packages\Microsoft.Owin.2.1.0\lib\net40\Microsoft.Owin.dll + + + ..\packages\Microsoft.Owin.Host.HttpListener.2.1.0\lib\net40\Microsoft.Owin.Host.HttpListener.dll + + + False + ..\packages\Microsoft.Owin.Hosting.2.1.0\lib\net40\Microsoft.Owin.Hosting.dll + + + ..\packages\Nancy.1.4.3\lib\net40\Nancy.dll + True + + + ..\packages\Nancy.Owin.1.4.1\lib\net40\Nancy.Owin.dll + True + + + ..\packages\Newtonsoft.Json.9.0.1\lib\net40\Newtonsoft.Json.dll + + + ..\packages\NLog.4.5.0-rc06\lib\net40-client\NLog.dll + + + ..\packages\NUnit.3.5.0\lib\net40\nunit.framework.dll + True + + + ..\packages\RestSharp.105.2.3\lib\net4\RestSharp.dll + True + + + + + + + + + + + + + + ..\packages\Moq.4.0.10827\lib\NET40\Moq.dll + + + ..\packages\Owin.1.0\lib\net40\Owin.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + App.config + + + + + + {FD286DF8-2D3A-4394-8AD5-443FADE55FB2} + NzbDrone.Api + + + {F2BE0FDF-6E47-4827-A420-DD4EF82407F8} + NzbDrone.Common + + + {FF5EE3B6-913B-47CE-9CEB-11C51B4E1205} + NzbDrone.Core + + + {95C11A9E-56ED-456A-8447-2C89C1139266} + NzbDrone.Host + + + {7C2CC69F-5CA0-4E5C-85CB-983F9F6C3B36} + NzbDrone.SignalR + + + {CADDFCE0-7509-4430-8364-2074E1EEFCA2} + NzbDrone.Test.Common + + + + + sqlite3.dll + Always + + + + + + + + + xcopy /s /y "$(SolutionDir)\..\_output\NzbDrone.Mono.*" "$(TargetDir)" + xcopy /s /y "$(SolutionDir)\..\_output\NzbDrone.Windows.*" "$(TargetDir)" + + + cp -rv $(SolutionDir)\..\_output\NzbDrone.Mono.* $(TargetDir) + cp -rv $(SolutionDir)\..\_output\NzbDrone.Windows.* $(TargetDir) + + + \ No newline at end of file diff --git a/src/NzbDrone.Integration.Test/packages.config b/src/NzbDrone.Integration.Test/packages.config index e771f93448..ffc10f644f 100644 --- a/src/NzbDrone.Integration.Test/packages.config +++ b/src/NzbDrone.Integration.Test/packages.config @@ -1,17 +1,17 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/NzbDrone.Mono/NzbDrone.Mono.csproj b/src/NzbDrone.Mono/NzbDrone.Mono.csproj index e41cf94762..eb0213f87b 100644 --- a/src/NzbDrone.Mono/NzbDrone.Mono.csproj +++ b/src/NzbDrone.Mono/NzbDrone.Mono.csproj @@ -1,104 +1,104 @@ - - - - - Debug - AnyCPU - {15AD7579-A314-4626-B556-663F51D97CD1} - Library - Properties - NzbDrone.Mono - NzbDrone.Mono - v4.0 - 512 - - ..\ - true - - - true - full - false - ..\..\_output\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - true - ..\..\_output\ - DEBUG;TRACE - full - x86 - prompt - MinimumRecommendedRules.ruleset - - - ..\..\_output\ - TRACE - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset - - - - ..\packages\NLog.4.5.0-rc06\lib\net40-client\NLog.dll - - - - - - - - - - - - - - False - ..\Libraries\Mono.Posix.dll - - - - - - - - - - - - - - - - - - - {f2be0fdf-6e47-4827-a420-dd4ef82407f8} - NzbDrone.Common - - - - - - + + + + + Debug + AnyCPU + {15AD7579-A314-4626-B556-663F51D97CD1} + Library + Properties + NzbDrone.Mono + NzbDrone.Mono + v4.0 + 512 + + ..\ + true + + + true + full + false + ..\..\_output\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + ..\..\_output\ + DEBUG;TRACE + full + x86 + prompt + MinimumRecommendedRules.ruleset + + + ..\..\_output\ + TRACE + true + pdbonly + x86 + prompt + MinimumRecommendedRules.ruleset + + + + ..\packages\NLog.4.5.0-rc06\lib\net40-client\NLog.dll + + + + + + + + + + + + + + False + ..\Libraries\Mono.Posix.dll + + + + + + + + + + + + + + + + + + + {f2be0fdf-6e47-4827-a420-dd4ef82407f8} + NzbDrone.Common + + + + + + + --> \ No newline at end of file diff --git a/src/NzbDrone.Mono/packages.config b/src/NzbDrone.Mono/packages.config index 6aa24212b7..f12e916fdf 100644 --- a/src/NzbDrone.Mono/packages.config +++ b/src/NzbDrone.Mono/packages.config @@ -1,4 +1,4 @@ - - - + + + \ No newline at end of file diff --git a/src/NzbDrone.Test.Common/App.config b/src/NzbDrone.Test.Common/App.config index 416bc96270..886337c3a8 100644 --- a/src/NzbDrone.Test.Common/App.config +++ b/src/NzbDrone.Test.Common/App.config @@ -1,26 +1,26 @@ - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/NzbDrone.Test.Common/NzbDrone.Test.Common.csproj b/src/NzbDrone.Test.Common/NzbDrone.Test.Common.csproj index e9fbbe0e76..3cff52dba7 100644 --- a/src/NzbDrone.Test.Common/NzbDrone.Test.Common.csproj +++ b/src/NzbDrone.Test.Common/NzbDrone.Test.Common.csproj @@ -1,132 +1,132 @@ - - - - Debug - x86 - 8.0.30703 - 2.0 - {CADDFCE0-7509-4430-8364-2074E1EEFCA2} - Library - Properties - NzbDrone.Test.Common - NzbDrone.Test.Common - v4.0 - 512 - ..\ - true - - - true - bin\x86\Debug\ - DEBUG;TRACE - full - x86 - prompt - MinimumRecommendedRules.ruleset - 4 - false - - - bin\x86\Release\ - TRACE - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset - 4 - - - - ..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.dll - True - - - ..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.Core.dll - True - - - ..\packages\Newtonsoft.Json.9.0.1\lib\net40\Newtonsoft.Json.dll - - - ..\packages\NLog.4.5.0-rc06\lib\net40-client\NLog.dll - - - ..\packages\NUnit.3.5.0\lib\net40\nunit.framework.dll - True - - - ..\packages\RestSharp.105.2.3\lib\net4\RestSharp.dll - True - - - - - - - - - - - - - - ..\packages\CommonServiceLocator.1.0\lib\NET35\Microsoft.Practices.ServiceLocation.dll - - - ..\packages\Unity.2.1.505.2\lib\NET35\Microsoft.Practices.Unity.dll - - - ..\packages\Unity.2.1.505.2\lib\NET35\Microsoft.Practices.Unity.Configuration.dll - - - ..\packages\Moq.4.0.10827\lib\NET40\Moq.dll - - - - - - - - - - - - - - - - - - - - - - - - - - Designer - Always - - - - - - {F2BE0FDF-6E47-4827-A420-DD4EF82407F8} - NzbDrone.Common - - - {FF5EE3B6-913B-47CE-9CEB-11C51B4E1205} - NzbDrone.Core - - - + + + + Debug + x86 + 8.0.30703 + 2.0 + {CADDFCE0-7509-4430-8364-2074E1EEFCA2} + Library + Properties + NzbDrone.Test.Common + NzbDrone.Test.Common + v4.0 + 512 + ..\ + true + + + true + bin\x86\Debug\ + DEBUG;TRACE + full + x86 + prompt + MinimumRecommendedRules.ruleset + 4 + false + + + bin\x86\Release\ + TRACE + true + pdbonly + x86 + prompt + MinimumRecommendedRules.ruleset + 4 + + + + ..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.dll + True + + + ..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.Core.dll + True + + + ..\packages\Newtonsoft.Json.9.0.1\lib\net40\Newtonsoft.Json.dll + + + ..\packages\NLog.4.5.0-rc06\lib\net40-client\NLog.dll + + + ..\packages\NUnit.3.5.0\lib\net40\nunit.framework.dll + True + + + ..\packages\RestSharp.105.2.3\lib\net4\RestSharp.dll + True + + + + + + + + + + + + + + ..\packages\CommonServiceLocator.1.0\lib\NET35\Microsoft.Practices.ServiceLocation.dll + + + ..\packages\Unity.2.1.505.2\lib\NET35\Microsoft.Practices.Unity.dll + + + ..\packages\Unity.2.1.505.2\lib\NET35\Microsoft.Practices.Unity.Configuration.dll + + + ..\packages\Moq.4.0.10827\lib\NET40\Moq.dll + + + + + + + + + + + + + + + + + + + + + + + + + + Designer + Always + + + + + + {F2BE0FDF-6E47-4827-A420-DD4EF82407F8} + NzbDrone.Common + + + {FF5EE3B6-913B-47CE-9CEB-11C51B4E1205} + NzbDrone.Core + + + + --> \ No newline at end of file diff --git a/src/NzbDrone.Test.Common/packages.config b/src/NzbDrone.Test.Common/packages.config index 14802ed9f4..c585cdaab1 100644 --- a/src/NzbDrone.Test.Common/packages.config +++ b/src/NzbDrone.Test.Common/packages.config @@ -1,11 +1,11 @@ - - - - - - - - - - + + + + + + + + + + \ No newline at end of file diff --git a/src/NzbDrone.Update.Test/NzbDrone.Update.Test.csproj b/src/NzbDrone.Update.Test/NzbDrone.Update.Test.csproj index 694d4219d3..4d917c17c2 100644 --- a/src/NzbDrone.Update.Test/NzbDrone.Update.Test.csproj +++ b/src/NzbDrone.Update.Test/NzbDrone.Update.Test.csproj @@ -1,112 +1,112 @@ - - - - Debug - x86 - 8.0.30703 - 2.0 - {35388E8E-0CDB-4A84-AD16-E4B6EFDA5D97} - Library - Properties - NzbDrone.Update.Test - NzbDrone.Update.Test - v4.0 - - - 512 - ..\ - true - - - x86 - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - x86 - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - ..\packages\NBuilder.4.0.0\lib\net40\FizzWare.NBuilder.dll - True - - - ..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.dll - True - - - ..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.Core.dll - True - - - ..\packages\NLog.4.5.0-rc06\lib\net40-client\NLog.dll - - - ..\packages\NUnit.3.5.0\lib\net40\nunit.framework.dll - True - - - - - - - - - - - - - - ..\packages\Moq.4.0.10827\lib\NET40\Moq.dll - - - - - - - - - - - - App.config - - - - - - {F2BE0FDF-6E47-4827-A420-DD4EF82407F8} - NzbDrone.Common - - - {CADDFCE0-7509-4430-8364-2074E1EEFCA2} - NzbDrone.Test.Common - - - {4CCC53CD-8D5E-4CC4-97D2-5C9312AC2BD7} - NzbDrone.Update - - - - - - + + + + Debug + x86 + 8.0.30703 + 2.0 + {35388E8E-0CDB-4A84-AD16-E4B6EFDA5D97} + Library + Properties + NzbDrone.Update.Test + NzbDrone.Update.Test + v4.0 + + + 512 + ..\ + true + + + x86 + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + x86 + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\NBuilder.4.0.0\lib\net40\FizzWare.NBuilder.dll + True + + + ..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.dll + True + + + ..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.Core.dll + True + + + ..\packages\NLog.4.5.0-rc06\lib\net40-client\NLog.dll + + + ..\packages\NUnit.3.5.0\lib\net40\nunit.framework.dll + True + + + + + + + + + + + + + + ..\packages\Moq.4.0.10827\lib\NET40\Moq.dll + + + + + + + + + + + + App.config + + + + + + {F2BE0FDF-6E47-4827-A420-DD4EF82407F8} + NzbDrone.Common + + + {CADDFCE0-7509-4430-8364-2074E1EEFCA2} + NzbDrone.Test.Common + + + {4CCC53CD-8D5E-4CC4-97D2-5C9312AC2BD7} + NzbDrone.Update + + + + + + + --> \ No newline at end of file diff --git a/src/NzbDrone.Update.Test/packages.config b/src/NzbDrone.Update.Test/packages.config index 36c0e6d75e..6f97f16cea 100644 --- a/src/NzbDrone.Update.Test/packages.config +++ b/src/NzbDrone.Update.Test/packages.config @@ -1,8 +1,8 @@ - - - - - - - + + + + + + + \ No newline at end of file diff --git a/src/NzbDrone.Update/NzbDrone.Update.csproj b/src/NzbDrone.Update/NzbDrone.Update.csproj index 912ad3efba..3ba24f919b 100644 --- a/src/NzbDrone.Update/NzbDrone.Update.csproj +++ b/src/NzbDrone.Update/NzbDrone.Update.csproj @@ -1,92 +1,92 @@ - - - - Debug - x86 - 8.0.30703 - 2.0 - {4CCC53CD-8D5E-4CC4-97D2-5C9312AC2BD7} - WinExe - Properties - NzbDrone.Update - Radarr.Update - v4.0 - - - 512 - ..\ - true - - - x86 - true - full - false - ..\..\_output\NzbDrone.Update\ - DEBUG;TRACE - prompt - 4 - - - x86 - pdbonly - true - ..\..\_output\NzbDrone.Update\ - TRACE - prompt - 4 - - - - - ..\packages\Newtonsoft.Json.9.0.1\lib\net40\Newtonsoft.Json.dll - - - ..\packages\NLog.4.5.0-rc06\lib\net40-client\NLog.dll - - - - - - - - - - - - - Properties\SharedAssemblyInfo.cs - - - - - - - - - - - - - - - - - - - - - {F2BE0FDF-6E47-4827-A420-DD4EF82407F8} - NzbDrone.Common - - - - + + + + Debug + x86 + 8.0.30703 + 2.0 + {4CCC53CD-8D5E-4CC4-97D2-5C9312AC2BD7} + WinExe + Properties + NzbDrone.Update + Radarr.Update + v4.0 + + + 512 + ..\ + true + + + x86 + true + full + false + ..\..\_output\NzbDrone.Update\ + DEBUG;TRACE + prompt + 4 + + + x86 + pdbonly + true + ..\..\_output\NzbDrone.Update\ + TRACE + prompt + 4 + + + + + ..\packages\Newtonsoft.Json.9.0.1\lib\net40\Newtonsoft.Json.dll + + + ..\packages\NLog.4.5.0-rc06\lib\net40-client\NLog.dll + + + + + + + + + + + + + Properties\SharedAssemblyInfo.cs + + + + + + + + + + + + + + + + + + + + + {F2BE0FDF-6E47-4827-A420-DD4EF82407F8} + NzbDrone.Common + + + + + --> \ No newline at end of file diff --git a/src/NzbDrone.Update/packages.config b/src/NzbDrone.Update/packages.config index 63e82cffea..f033567c84 100644 --- a/src/NzbDrone.Update/packages.config +++ b/src/NzbDrone.Update/packages.config @@ -1,5 +1,5 @@ - - - - + + + + \ No newline at end of file diff --git a/src/NzbDrone.Windows/NzbDrone.Windows.csproj b/src/NzbDrone.Windows/NzbDrone.Windows.csproj index ee3ec4f409..5dc717e943 100644 --- a/src/NzbDrone.Windows/NzbDrone.Windows.csproj +++ b/src/NzbDrone.Windows/NzbDrone.Windows.csproj @@ -1,91 +1,91 @@ - - - - - Debug - AnyCPU - {911284D3-F130-459E-836C-2430B6FBF21D} - Library - Properties - NzbDrone.Windows - NzbDrone.Windows - v4.0 - 512 - ..\ - true - - - true - full - false - ..\..\_output\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - true - ..\..\_output\ - DEBUG;TRACE - full - x86 - prompt - MinimumRecommendedRules.ruleset - - - ..\..\_output\ - TRACE - true - pdbonly - x86 - prompt - MinimumRecommendedRules.ruleset - - - - ..\packages\NLog.4.5.0-rc06\lib\net40-client\NLog.dll - - - - - - - - - - - - - - - - - - - - - - {f2be0fdf-6e47-4827-a420-dd4ef82407f8} - NzbDrone.Common - - - - - - + + + + + Debug + AnyCPU + {911284D3-F130-459E-836C-2430B6FBF21D} + Library + Properties + NzbDrone.Windows + NzbDrone.Windows + v4.0 + 512 + ..\ + true + + + true + full + false + ..\..\_output\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + ..\..\_output\ + DEBUG;TRACE + full + x86 + prompt + MinimumRecommendedRules.ruleset + + + ..\..\_output\ + TRACE + true + pdbonly + x86 + prompt + MinimumRecommendedRules.ruleset + + + + ..\packages\NLog.4.5.0-rc06\lib\net40-client\NLog.dll + + + + + + + + + + + + + + + + + + + + + + {f2be0fdf-6e47-4827-a420-dd4ef82407f8} + NzbDrone.Common + + + + + + + --> \ No newline at end of file diff --git a/src/NzbDrone.Windows/packages.config b/src/NzbDrone.Windows/packages.config index 6aa24212b7..f12e916fdf 100644 --- a/src/NzbDrone.Windows/packages.config +++ b/src/NzbDrone.Windows/packages.config @@ -1,4 +1,4 @@ - - - + + + \ No newline at end of file diff --git a/src/NzbDrone/NzbDrone.csproj b/src/NzbDrone/NzbDrone.csproj index 350d008a9a..786fffed04 100644 --- a/src/NzbDrone/NzbDrone.csproj +++ b/src/NzbDrone/NzbDrone.csproj @@ -1,185 +1,185 @@ - - - - Debug - x86 - 8.0.30703 - 2.0 - {D12F7F2F-8A3C-415F-88FA-6DD061A84869} - WinExe - Properties - NzbDrone - Radarr - v4.0 - 512 - - - false - ..\ - true - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - true - - - x86 - true - full - false - ..\..\_output\ - DEBUG;TRACE - prompt - 4 - true - BasicCorrectnessRules.ruleset - - - x86 - pdbonly - true - ..\..\_output\ - TRACE - prompt - 4 - - - Resources\Radarr.ico - - - NzbDrone.WindowsApp - - - OnOutputUpdated - - - - - False - ..\packages\Microsoft.Owin.2.1.0\lib\net40\Microsoft.Owin.dll - - - False - ..\packages\Microsoft.Owin.Hosting.2.1.0\lib\net40\Microsoft.Owin.Hosting.dll - - - ..\packages\Newtonsoft.Json.9.0.1\lib\net40\Newtonsoft.Json.dll - - - ..\packages\NLog.4.5.0-rc06\lib\net40-client\NLog.dll - - - - - - - - - - - - ..\packages\Owin.1.0\lib\net40\Owin.dll - - - - - - Properties\SharedAssemblyInfo.cs - - - - - True - True - Resources.resx - - - Form - - - - - - False - Microsoft .NET Framework 4 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 Client Profile - false - - - False - .NET Framework 3.5 SP1 - false - - - False - Windows Installer 3.1 - true - - - - - {1B9A82C4-BCA1-4834-A33E-226F17BE070B} - Microsoft.AspNet.SignalR.Core - - - {2B8C6DAD-4D85-41B1-83FD-248D9F347522} - Microsoft.AspNet.SignalR.Owin - - - {F2BE0FDF-6E47-4827-A420-DD4EF82407F8} - NzbDrone.Common - - - {95C11A9E-56ED-456A-8447-2C89C1139266} - NzbDrone.Host - - - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - - - app.config - - - - - - - - - - - - - + + + + Debug + x86 + 8.0.30703 + 2.0 + {D12F7F2F-8A3C-415F-88FA-6DD061A84869} + WinExe + Properties + NzbDrone + Radarr + v4.0 + 512 + + + false + ..\ + true + publish\ + true + Disk + false + Foreground + 7 + Days + false + false + true + 0 + 1.0.0.%2a + false + true + + + x86 + true + full + false + ..\..\_output\ + DEBUG;TRACE + prompt + 4 + true + BasicCorrectnessRules.ruleset + + + x86 + pdbonly + true + ..\..\_output\ + TRACE + prompt + 4 + + + Resources\Radarr.ico + + + NzbDrone.WindowsApp + + + OnOutputUpdated + + + + + False + ..\packages\Microsoft.Owin.2.1.0\lib\net40\Microsoft.Owin.dll + + + False + ..\packages\Microsoft.Owin.Hosting.2.1.0\lib\net40\Microsoft.Owin.Hosting.dll + + + ..\packages\Newtonsoft.Json.9.0.1\lib\net40\Newtonsoft.Json.dll + + + ..\packages\NLog.4.5.0-rc06\lib\net40-client\NLog.dll + + + + + + + + + + + + ..\packages\Owin.1.0\lib\net40\Owin.dll + + + + + + Properties\SharedAssemblyInfo.cs + + + + + True + True + Resources.resx + + + Form + + + + + + False + Microsoft .NET Framework 4 %28x86 and x64%29 + true + + + False + .NET Framework 3.5 SP1 Client Profile + false + + + False + .NET Framework 3.5 SP1 + false + + + False + Windows Installer 3.1 + true + + + + + {1B9A82C4-BCA1-4834-A33E-226F17BE070B} + Microsoft.AspNet.SignalR.Core + + + {2B8C6DAD-4D85-41B1-83FD-248D9F347522} + Microsoft.AspNet.SignalR.Owin + + + {F2BE0FDF-6E47-4827-A420-DD4EF82407F8} + NzbDrone.Common + + + {95C11A9E-56ED-456A-8447-2C89C1139266} + NzbDrone.Host + + + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + + + app.config + + + + + + + + + + + + + xcopy /s /y "$(SolutionDir)\Libraries\Sqlite\*.*" "$(TargetDir)" - - + + + --> \ No newline at end of file diff --git a/src/NzbDrone/packages.config b/src/NzbDrone/packages.config index 7001c53f24..537aa75d1b 100644 --- a/src/NzbDrone/packages.config +++ b/src/NzbDrone/packages.config @@ -1,8 +1,8 @@ - - - - - - - + + + + + + + \ No newline at end of file diff --git a/src/UI/JsLibraries/typeahead.js b/src/UI/JsLibraries/typeahead.js index 450a6ca43b..1963150140 100644 --- a/src/UI/JsLibraries/typeahead.js +++ b/src/UI/JsLibraries/typeahead.js @@ -1,1716 +1,1716 @@ -/*! - * typeahead.js 0.10.2 - * https://github.com/twitter/typeahead.js - * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT - */ - -(function($) { - var _ = { - isMsie: function() { - return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false; - }, - isBlankString: function(str) { - return !str || /^\s*$/.test(str); - }, - escapeRegExChars: function(str) { - return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); - }, - isString: function(obj) { - return typeof obj === "string"; - }, - isNumber: function(obj) { - return typeof obj === "number"; - }, - isArray: $.isArray, - isFunction: $.isFunction, - isObject: $.isPlainObject, - isUndefined: function(obj) { - return typeof obj === "undefined"; - }, - bind: $.proxy, - each: function(collection, cb) { - $.each(collection, reverseArgs); - function reverseArgs(index, value) { - return cb(value, index); - } - }, - map: $.map, - filter: $.grep, - every: function(obj, test) { - var result = true; - if (!obj) { - return result; - } - $.each(obj, function(key, val) { - if (!(result = test.call(null, val, key, obj))) { - return false; - } - }); - return !!result; - }, - some: function(obj, test) { - var result = false; - if (!obj) { - return result; - } - $.each(obj, function(key, val) { - if (result = test.call(null, val, key, obj)) { - return false; - } - }); - return !!result; - }, - mixin: $.extend, - getUniqueId: function() { - var counter = 0; - return function() { - return counter++; - }; - }(), - templatify: function templatify(obj) { - return $.isFunction(obj) ? obj : template; - function template() { - return String(obj); - } - }, - defer: function(fn) { - setTimeout(fn, 0); - }, - debounce: function(func, wait, immediate) { - var timeout, result; - return function() { - var context = this, args = arguments, later, callNow; - later = function() { - timeout = null; - if (!immediate) { - result = func.apply(context, args); - } - }; - callNow = immediate && !timeout; - clearTimeout(timeout); - timeout = setTimeout(later, wait); - if (callNow) { - result = func.apply(context, args); - } - return result; - }; - }, - throttle: function(func, wait) { - var context, args, timeout, result, previous, later; - previous = 0; - later = function() { - previous = new Date(); - timeout = null; - result = func.apply(context, args); - }; - return function() { - var now = new Date(), remaining = wait - (now - previous); - context = this; - args = arguments; - if (remaining <= 0) { - clearTimeout(timeout); - timeout = null; - previous = now; - result = func.apply(context, args); - } else if (!timeout) { - timeout = setTimeout(later, remaining); - } - return result; - }; - }, - noop: function() {} - }; - var VERSION = "0.10.2"; - var tokenizers = function(root) { - return { - nonword: nonword, - whitespace: whitespace, - obj: { - nonword: getObjTokenizer(nonword), - whitespace: getObjTokenizer(whitespace) - } - }; - function whitespace(s) { - return s.split(/\s+/); - } - function nonword(s) { - return s.split(/\W+/); - } - function getObjTokenizer(tokenizer) { - return function setKey(key) { - return function tokenize(o) { - return tokenizer(o[key]); - }; - }; - } - }(); - var LruCache = function() { - function LruCache(maxSize) { - this.maxSize = maxSize || 100; - this.size = 0; - this.hash = {}; - this.list = new List(); - } - _.mixin(LruCache.prototype, { - set: function set(key, val) { - var tailItem = this.list.tail, node; - if (this.size >= this.maxSize) { - this.list.remove(tailItem); - delete this.hash[tailItem.key]; - } - if (node = this.hash[key]) { - node.val = val; - this.list.moveToFront(node); - } else { - node = new Node(key, val); - this.list.add(node); - this.hash[key] = node; - this.size++; - } - }, - get: function get(key) { - var node = this.hash[key]; - if (node) { - this.list.moveToFront(node); - return node.val; - } - } - }); - function List() { - this.head = this.tail = null; - } - _.mixin(List.prototype, { - add: function add(node) { - if (this.head) { - node.next = this.head; - this.head.prev = node; - } - this.head = node; - this.tail = this.tail || node; - }, - remove: function remove(node) { - node.prev ? node.prev.next = node.next : this.head = node.next; - node.next ? node.next.prev = node.prev : this.tail = node.prev; - }, - moveToFront: function(node) { - this.remove(node); - this.add(node); - } - }); - function Node(key, val) { - this.key = key; - this.val = val; - this.prev = this.next = null; - } - return LruCache; - }(); - var PersistentStorage = function() { - var ls, methods; - try { - ls = window.localStorage; - ls.setItem("~~~", "!"); - ls.removeItem("~~~"); - } catch (err) { - ls = null; - } - function PersistentStorage(namespace) { - this.prefix = [ "__", namespace, "__" ].join(""); - this.ttlKey = "__ttl__"; - this.keyMatcher = new RegExp("^" + this.prefix); - } - if (ls && window.JSON) { - methods = { - _prefix: function(key) { - return this.prefix + key; - }, - _ttlKey: function(key) { - return this._prefix(key) + this.ttlKey; - }, - get: function(key) { - if (this.isExpired(key)) { - this.remove(key); - } - return decode(ls.getItem(this._prefix(key))); - }, - set: function(key, val, ttl) { - if (_.isNumber(ttl)) { - ls.setItem(this._ttlKey(key), encode(now() + ttl)); - } else { - ls.removeItem(this._ttlKey(key)); - } - return ls.setItem(this._prefix(key), encode(val)); - }, - remove: function(key) { - ls.removeItem(this._ttlKey(key)); - ls.removeItem(this._prefix(key)); - return this; - }, - clear: function() { - var i, key, keys = [], len = ls.length; - for (i = 0; i < len; i++) { - if ((key = ls.key(i)).match(this.keyMatcher)) { - keys.push(key.replace(this.keyMatcher, "")); - } - } - for (i = keys.length; i--; ) { - this.remove(keys[i]); - } - return this; - }, - isExpired: function(key) { - var ttl = decode(ls.getItem(this._ttlKey(key))); - return _.isNumber(ttl) && now() > ttl ? true : false; - } - }; - } else { - methods = { - get: _.noop, - set: _.noop, - remove: _.noop, - clear: _.noop, - isExpired: _.noop - }; - } - _.mixin(PersistentStorage.prototype, methods); - return PersistentStorage; - function now() { - return new Date().getTime(); - } - function encode(val) { - return JSON.stringify(_.isUndefined(val) ? null : val); - } - function decode(val) { - return JSON.parse(val); - } - }(); - var Transport = function() { - var pendingRequestsCount = 0, pendingRequests = {}, maxPendingRequests = 6, requestCache = new LruCache(10); - function Transport(o) { - o = o || {}; - this._send = o.transport ? callbackToDeferred(o.transport) : $.ajax; - this._get = o.rateLimiter ? o.rateLimiter(this._get) : this._get; - } - Transport.setMaxPendingRequests = function setMaxPendingRequests(num) { - maxPendingRequests = num; - }; - Transport.resetCache = function clearCache() { - requestCache = new LruCache(10); - }; - _.mixin(Transport.prototype, { - _get: function(url, o, cb) { - var that = this, jqXhr; - if (jqXhr = pendingRequests[url]) { - jqXhr.done(done).fail(fail); - } else if (pendingRequestsCount < maxPendingRequests) { - pendingRequestsCount++; - pendingRequests[url] = this._send(url, o).done(done).fail(fail).always(always); - } else { - this.onDeckRequestArgs = [].slice.call(arguments, 0); - } - function done(resp) { - cb && cb(null, resp); - requestCache.set(url, resp); - } - function fail() { - cb && cb(true); - } - function always() { - pendingRequestsCount--; - delete pendingRequests[url]; - if (that.onDeckRequestArgs) { - that._get.apply(that, that.onDeckRequestArgs); - that.onDeckRequestArgs = null; - } - } - }, - get: function(url, o, cb) { - var resp; - if (_.isFunction(o)) { - cb = o; - o = {}; - } - if (resp = requestCache.get(url)) { - _.defer(function() { - cb && cb(null, resp); - }); - } else { - this._get(url, o, cb); - } - return !!resp; - } - }); - return Transport; - function callbackToDeferred(fn) { - return function customSendWrapper(url, o) { - var deferred = $.Deferred(); - fn(url, o, onSuccess, onError); - return deferred; - function onSuccess(resp) { - _.defer(function() { - deferred.resolve(resp); - }); - } - function onError(err) { - _.defer(function() { - deferred.reject(err); - }); - } - }; - } - }(); - var SearchIndex = function() { - function SearchIndex(o) { - o = o || {}; - if (!o.datumTokenizer || !o.queryTokenizer) { - $.error("datumTokenizer and queryTokenizer are both required"); - } - this.datumTokenizer = o.datumTokenizer; - this.queryTokenizer = o.queryTokenizer; - this.reset(); - } - _.mixin(SearchIndex.prototype, { - bootstrap: function bootstrap(o) { - this.datums = o.datums; - this.trie = o.trie; - }, - add: function(data) { - var that = this; - data = _.isArray(data) ? data : [ data ]; - _.each(data, function(datum) { - var id, tokens; - id = that.datums.push(datum) - 1; - tokens = normalizeTokens(that.datumTokenizer(datum)); - _.each(tokens, function(token) { - var node, chars, ch; - node = that.trie; - chars = token.split(""); - while (ch = chars.shift()) { - node = node.children[ch] || (node.children[ch] = newNode()); - node.ids.push(id); - } - }); - }); - }, - get: function get(query) { - var that = this, tokens, matches; - tokens = normalizeTokens(this.queryTokenizer(query)); - _.each(tokens, function(token) { - var node, chars, ch, ids; - if (matches && matches.length === 0) { - return false; - } - node = that.trie; - chars = token.split(""); - while (node && (ch = chars.shift())) { - node = node.children[ch]; - } - if (node && chars.length === 0) { - ids = node.ids.slice(0); - matches = matches ? getIntersection(matches, ids) : ids; - } else { - matches = []; - return false; - } - }); - return matches ? _.map(unique(matches), function(id) { - return that.datums[id]; - }) : []; - }, - reset: function reset() { - this.datums = []; - this.trie = newNode(); - }, - serialize: function serialize() { - return { - datums: this.datums, - trie: this.trie - }; - } - }); - return SearchIndex; - function normalizeTokens(tokens) { - tokens = _.filter(tokens, function(token) { - return !!token; - }); - tokens = _.map(tokens, function(token) { - return token.toLowerCase(); - }); - return tokens; - } - function newNode() { - return { - ids: [], - children: {} - }; - } - function unique(array) { - var seen = {}, uniques = []; - for (var i = 0; i < array.length; i++) { - if (!seen[array[i]]) { - seen[array[i]] = true; - uniques.push(array[i]); - } - } - return uniques; - } - function getIntersection(arrayA, arrayB) { - var ai = 0, bi = 0, intersection = []; - arrayA = arrayA.sort(compare); - arrayB = arrayB.sort(compare); - while (ai < arrayA.length && bi < arrayB.length) { - if (arrayA[ai] < arrayB[bi]) { - ai++; - } else if (arrayA[ai] > arrayB[bi]) { - bi++; - } else { - intersection.push(arrayA[ai]); - ai++; - bi++; - } - } - return intersection; - function compare(a, b) { - return a - b; - } - } - }(); - var oParser = function() { - return { - local: getLocal, - prefetch: getPrefetch, - remote: getRemote - }; - function getLocal(o) { - return o.local || null; - } - function getPrefetch(o) { - var prefetch, defaults; - defaults = { - url: null, - thumbprint: "", - ttl: 24 * 60 * 60 * 1e3, - filter: null, - ajax: {} - }; - if (prefetch = o.prefetch || null) { - prefetch = _.isString(prefetch) ? { - url: prefetch - } : prefetch; - prefetch = _.mixin(defaults, prefetch); - prefetch.thumbprint = VERSION + prefetch.thumbprint; - prefetch.ajax.type = prefetch.ajax.type || "GET"; - prefetch.ajax.dataType = prefetch.ajax.dataType || "json"; - !prefetch.url && $.error("prefetch requires url to be set"); - } - return prefetch; - } - function getRemote(o) { - var remote, defaults; - defaults = { - url: null, - wildcard: "%QUERY", - replace: null, - rateLimitBy: "debounce", - rateLimitWait: 300, - send: null, - filter: null, - ajax: {} - }; - if (remote = o.remote || null) { - remote = _.isString(remote) ? { - url: remote - } : remote; - remote = _.mixin(defaults, remote); - remote.rateLimiter = /^throttle$/i.test(remote.rateLimitBy) ? byThrottle(remote.rateLimitWait) : byDebounce(remote.rateLimitWait); - remote.ajax.type = remote.ajax.type || "GET"; - remote.ajax.dataType = remote.ajax.dataType || "json"; - delete remote.rateLimitBy; - delete remote.rateLimitWait; - !remote.url && $.error("remote requires url to be set"); - } - return remote; - function byDebounce(wait) { - return function(fn) { - return _.debounce(fn, wait); - }; - } - function byThrottle(wait) { - return function(fn) { - return _.throttle(fn, wait); - }; - } - } - }(); - (function(root) { - var old, keys; - old = root.Bloodhound; - keys = { - data: "data", - protocol: "protocol", - thumbprint: "thumbprint" - }; - root.Bloodhound = Bloodhound; - function Bloodhound(o) { - if (!o || !o.local && !o.prefetch && !o.remote) { - $.error("one of local, prefetch, or remote is required"); - } - this.limit = o.limit || 5; - this.sorter = getSorter(o.sorter); - this.dupDetector = o.dupDetector || ignoreDuplicates; - this.local = oParser.local(o); - this.prefetch = oParser.prefetch(o); - this.remote = oParser.remote(o); - this.cacheKey = this.prefetch ? this.prefetch.cacheKey || this.prefetch.url : null; - this.index = new SearchIndex({ - datumTokenizer: o.datumTokenizer, - queryTokenizer: o.queryTokenizer - }); - this.storage = this.cacheKey ? new PersistentStorage(this.cacheKey) : null; - } - Bloodhound.noConflict = function noConflict() { - root.Bloodhound = old; - return Bloodhound; - }; - Bloodhound.tokenizers = tokenizers; - _.mixin(Bloodhound.prototype, { - _loadPrefetch: function loadPrefetch(o) { - var that = this, serialized, deferred; - if (serialized = this._readFromStorage(o.thumbprint)) { - this.index.bootstrap(serialized); - deferred = $.Deferred().resolve(); - } else { - deferred = $.ajax(o.url, o.ajax).done(handlePrefetchResponse); - } - return deferred; - function handlePrefetchResponse(resp) { - that.clear(); - that.add(o.filter ? o.filter(resp) : resp); - that._saveToStorage(that.index.serialize(), o.thumbprint, o.ttl); - } - }, - _getFromRemote: function getFromRemote(query, cb) { - var that = this, url, uriEncodedQuery; - query = query || ""; - uriEncodedQuery = encodeURIComponent(query); - url = this.remote.replace ? this.remote.replace(this.remote.url, query) : this.remote.url.replace(this.remote.wildcard, uriEncodedQuery); - return this.transport.get(url, this.remote.ajax, handleRemoteResponse); - function handleRemoteResponse(err, resp) { - err ? cb([]) : cb(that.remote.filter ? that.remote.filter(resp) : resp); - } - }, - _saveToStorage: function saveToStorage(data, thumbprint, ttl) { - if (this.storage) { - this.storage.set(keys.data, data, ttl); - this.storage.set(keys.protocol, location.protocol, ttl); - this.storage.set(keys.thumbprint, thumbprint, ttl); - } - }, - _readFromStorage: function readFromStorage(thumbprint) { - var stored = {}, isExpired; - if (this.storage) { - stored.data = this.storage.get(keys.data); - stored.protocol = this.storage.get(keys.protocol); - stored.thumbprint = this.storage.get(keys.thumbprint); - } - isExpired = stored.thumbprint !== thumbprint || stored.protocol !== location.protocol; - return stored.data && !isExpired ? stored.data : null; - }, - _initialize: function initialize() { - var that = this, local = this.local, deferred; - deferred = this.prefetch ? this._loadPrefetch(this.prefetch) : $.Deferred().resolve(); - local && deferred.done(addLocalToIndex); - this.transport = this.remote ? new Transport(this.remote) : null; - return this.initPromise = deferred.promise(); - function addLocalToIndex() { - that.add(_.isFunction(local) ? local() : local); - } - }, - initialize: function initialize(force) { - return !this.initPromise || force ? this._initialize() : this.initPromise; - }, - add: function add(data) { - this.index.add(data); - }, - get: function get(query, cb) { - var that = this, matches = [], cacheHit = false; - matches = this.index.get(query); - matches = this.sorter(matches).slice(0, this.limit); - if (matches.length < this.limit && this.transport) { - cacheHit = this._getFromRemote(query, returnRemoteMatches); - } - if (!cacheHit) { - (matches.length > 0 || !this.transport) && cb && cb(matches); - } - function returnRemoteMatches(remoteMatches) { - var matchesWithBackfill = matches.slice(0); - _.each(remoteMatches, function(remoteMatch) { - var isDuplicate; - isDuplicate = _.some(matchesWithBackfill, function(match) { - return that.dupDetector(remoteMatch, match); - }); - !isDuplicate && matchesWithBackfill.push(remoteMatch); - return matchesWithBackfill.length < that.limit; - }); - cb && cb(that.sorter(matchesWithBackfill)); - } - }, - clear: function clear() { - this.index.reset(); - }, - clearPrefetchCache: function clearPrefetchCache() { - this.storage && this.storage.clear(); - }, - clearRemoteCache: function clearRemoteCache() { - this.transport && Transport.resetCache(); - }, - ttAdapter: function ttAdapter() { - return _.bind(this.get, this); - } - }); - return Bloodhound; - function getSorter(sortFn) { - return _.isFunction(sortFn) ? sort : noSort; - function sort(array) { - return array.sort(sortFn); - } - function noSort(array) { - return array; - } - } - function ignoreDuplicates() { - return false; - } - })(this); - var html = { - wrapper: '', - dropdown: '', - dataset: '
', - suggestions: '', - suggestion: '
' - }; - var css = { - wrapper: { - position: "relative", - display: "inline-block" - }, - hint: { - position: "absolute", - top: "0", - left: "0", - borderColor: "transparent", - boxShadow: "none" - }, - input: { - position: "relative", - verticalAlign: "top", - backgroundColor: "transparent" - }, - inputWithNoHint: { - position: "relative", - verticalAlign: "top" - }, - dropdown: { - position: "absolute", - top: "100%", - left: "0", - zIndex: "100", - display: "none" - }, - suggestions: { - display: "block" - }, - suggestion: { - whiteSpace: "nowrap", - cursor: "pointer" - }, - suggestionChild: { - whiteSpace: "normal" - }, - ltr: { - left: "0", - right: "auto" - }, - rtl: { - left: "auto", - right: " 0" - } - }; - if (_.isMsie()) { - _.mixin(css.input, { - backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)" - }); - } - if (_.isMsie() && _.isMsie() <= 7) { - _.mixin(css.input, { - marginTop: "-1px" - }); - } - var EventBus = function() { - var namespace = "typeahead:"; - function EventBus(o) { - if (!o || !o.el) { - $.error("EventBus initialized without el"); - } - this.$el = $(o.el); - } - _.mixin(EventBus.prototype, { - trigger: function(type) { - var args = [].slice.call(arguments, 1); - this.$el.trigger(namespace + type, args); - } - }); - return EventBus; - }(); - var EventEmitter = function() { - var splitter = /\s+/, nextTick = getNextTick(); - return { - onSync: onSync, - onAsync: onAsync, - off: off, - trigger: trigger - }; - function on(method, types, cb, context) { - var type; - if (!cb) { - return this; - } - types = types.split(splitter); - cb = context ? bindContext(cb, context) : cb; - this._callbacks = this._callbacks || {}; - while (type = types.shift()) { - this._callbacks[type] = this._callbacks[type] || { - sync: [], - async: [] - }; - this._callbacks[type][method].push(cb); - } - return this; - } - function onAsync(types, cb, context) { - return on.call(this, "async", types, cb, context); - } - function onSync(types, cb, context) { - return on.call(this, "sync", types, cb, context); - } - function off(types) { - var type; - if (!this._callbacks) { - return this; - } - types = types.split(splitter); - while (type = types.shift()) { - delete this._callbacks[type]; - } - return this; - } - function trigger(types) { - var type, callbacks, args, syncFlush, asyncFlush; - if (!this._callbacks) { - return this; - } - types = types.split(splitter); - args = [].slice.call(arguments, 1); - while ((type = types.shift()) && (callbacks = this._callbacks[type])) { - syncFlush = getFlush(callbacks.sync, this, [ type ].concat(args)); - asyncFlush = getFlush(callbacks.async, this, [ type ].concat(args)); - syncFlush() && nextTick(asyncFlush); - } - return this; - } - function getFlush(callbacks, context, args) { - return flush; - function flush() { - var cancelled; - for (var i = 0; !cancelled && i < callbacks.length; i += 1) { - cancelled = callbacks[i].apply(context, args) === false; - } - return !cancelled; - } - } - function getNextTick() { - var nextTickFn; - if (window.setImmediate) { - nextTickFn = function nextTickSetImmediate(fn) { - setImmediate(function() { - fn(); - }); - }; - } else { - nextTickFn = function nextTickSetTimeout(fn) { - setTimeout(function() { - fn(); - }, 0); - }; - } - return nextTickFn; - } - function bindContext(fn, context) { - return fn.bind ? fn.bind(context) : function() { - fn.apply(context, [].slice.call(arguments, 0)); - }; - } - }(); - var highlight = function(doc) { - var defaults = { - node: null, - pattern: null, - tagName: "strong", - className: null, - wordsOnly: false, - caseSensitive: false - }; - return function hightlight(o) { - var regex; - o = _.mixin({}, defaults, o); - if (!o.node || !o.pattern) { - return; - } - o.pattern = _.isArray(o.pattern) ? o.pattern : [ o.pattern ]; - regex = getRegex(o.pattern, o.caseSensitive, o.wordsOnly); - traverse(o.node, hightlightTextNode); - function hightlightTextNode(textNode) { - var match, patternNode; - if (match = regex.exec(textNode.data)) { - wrapperNode = doc.createElement(o.tagName); - o.className && (wrapperNode.className = o.className); - patternNode = textNode.splitText(match.index); - patternNode.splitText(match[0].length); - wrapperNode.appendChild(patternNode.cloneNode(true)); - textNode.parentNode.replaceChild(wrapperNode, patternNode); - } - return !!match; - } - function traverse(el, hightlightTextNode) { - var childNode, TEXT_NODE_TYPE = 3; - for (var i = 0; i < el.childNodes.length; i++) { - childNode = el.childNodes[i]; - if (childNode.nodeType === TEXT_NODE_TYPE) { - i += hightlightTextNode(childNode) ? 1 : 0; - } else { - traverse(childNode, hightlightTextNode); - } - } - } - }; - function getRegex(patterns, caseSensitive, wordsOnly) { - var escapedPatterns = [], regexStr; - for (var i = 0; i < patterns.length; i++) { - escapedPatterns.push(_.escapeRegExChars(patterns[i])); - } - regexStr = wordsOnly ? "\\b(" + escapedPatterns.join("|") + ")\\b" : "(" + escapedPatterns.join("|") + ")"; - return caseSensitive ? new RegExp(regexStr) : new RegExp(regexStr, "i"); - } - }(window.document); - var Input = function() { - var specialKeyCodeMap; - specialKeyCodeMap = { - 9: "tab", - 27: "esc", - 37: "left", - 39: "right", - 13: "enter", - 38: "up", - 40: "down" - }; - function Input(o) { - var that = this, onBlur, onFocus, onKeydown, onInput; - o = o || {}; - if (!o.input) { - $.error("input is missing"); - } - onBlur = _.bind(this._onBlur, this); - onFocus = _.bind(this._onFocus, this); - onKeydown = _.bind(this._onKeydown, this); - onInput = _.bind(this._onInput, this); - this.$hint = $(o.hint); - this.$input = $(o.input).on("blur.tt", onBlur).on("focus.tt", onFocus).on("keydown.tt", onKeydown); - if (this.$hint.length === 0) { - this.setHint = this.getHint = this.clearHint = this.clearHintIfInvalid = _.noop; - } - if (!_.isMsie()) { - this.$input.on("input.tt", onInput); - } else { - this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) { - if (specialKeyCodeMap[$e.which || $e.keyCode]) { - return; - } - _.defer(_.bind(that._onInput, that, $e)); - }); - } - this.query = this.$input.val(); - this.$overflowHelper = buildOverflowHelper(this.$input); - } - Input.normalizeQuery = function(str) { - return (str || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " "); - }; - _.mixin(Input.prototype, EventEmitter, { - _onBlur: function onBlur() { - this.resetInputValue(); - this.trigger("blurred"); - }, - _onFocus: function onFocus() { - this.trigger("focused"); - }, - _onKeydown: function onKeydown($e) { - var keyName = specialKeyCodeMap[$e.which || $e.keyCode]; - this._managePreventDefault(keyName, $e); - if (keyName && this._shouldTrigger(keyName, $e)) { - this.trigger(keyName + "Keyed", $e); - } - }, - _onInput: function onInput() { - this._checkInputValue(); - }, - _managePreventDefault: function managePreventDefault(keyName, $e) { - var preventDefault, hintValue, inputValue; - switch (keyName) { - case "tab": - hintValue = this.getHint(); - inputValue = this.getInputValue(); - preventDefault = hintValue && hintValue !== inputValue && !withModifier($e); - break; - - case "up": - case "down": - preventDefault = !withModifier($e); - break; - - default: - preventDefault = false; - } - preventDefault && $e.preventDefault(); - }, - _shouldTrigger: function shouldTrigger(keyName, $e) { - var trigger; - switch (keyName) { - case "tab": - trigger = !withModifier($e); - break; - - default: - trigger = true; - } - return trigger; - }, - _checkInputValue: function checkInputValue() { - var inputValue, areEquivalent, hasDifferentWhitespace; - inputValue = this.getInputValue(); - areEquivalent = areQueriesEquivalent(inputValue, this.query); - hasDifferentWhitespace = areEquivalent ? this.query.length !== inputValue.length : false; - if (!areEquivalent) { - this.trigger("queryChanged", this.query = inputValue); - } else if (hasDifferentWhitespace) { - this.trigger("whitespaceChanged", this.query); - } - }, - focus: function focus() { - this.$input.focus(); - }, - blur: function blur() { - this.$input.blur(); - }, - getQuery: function getQuery() { - return this.query; - }, - setQuery: function setQuery(query) { - this.query = query; - }, - getInputValue: function getInputValue() { - return this.$input.val(); - }, - setInputValue: function setInputValue(value, silent) { - this.$input.val(value); - silent ? this.clearHint() : this._checkInputValue(); - }, - resetInputValue: function resetInputValue() { - this.setInputValue(this.query, true); - }, - getHint: function getHint() { - return this.$hint.val(); - }, - setHint: function setHint(value) { - this.$hint.val(value); - }, - clearHint: function clearHint() { - this.setHint(""); - }, - clearHintIfInvalid: function clearHintIfInvalid() { - var val, hint, valIsPrefixOfHint, isValid; - val = this.getInputValue(); - hint = this.getHint(); - valIsPrefixOfHint = val !== hint && hint.indexOf(val) === 0; - isValid = val !== "" && valIsPrefixOfHint && !this.hasOverflow(); - !isValid && this.clearHint(); - }, - getLanguageDirection: function getLanguageDirection() { - return (this.$input.css("direction") || "ltr").toLowerCase(); - }, - hasOverflow: function hasOverflow() { - var constraint = this.$input.width() - 2; - this.$overflowHelper.text(this.getInputValue()); - return this.$overflowHelper.width() >= constraint; - }, - isCursorAtEnd: function() { - var valueLength, selectionStart, range; - valueLength = this.$input.val().length; - selectionStart = this.$input[0].selectionStart; - if (_.isNumber(selectionStart)) { - return selectionStart === valueLength; - } else if (document.selection) { - range = document.selection.createRange(); - range.moveStart("character", -valueLength); - return valueLength === range.text.length; - } - return true; - }, - destroy: function destroy() { - this.$hint.off(".tt"); - this.$input.off(".tt"); - this.$hint = this.$input = this.$overflowHelper = null; - } - }); - return Input; - function buildOverflowHelper($input) { - return $('').css({ - position: "absolute", - visibility: "hidden", - whiteSpace: "pre", - fontFamily: $input.css("font-family"), - fontSize: $input.css("font-size"), - fontStyle: $input.css("font-style"), - fontVariant: $input.css("font-variant"), - fontWeight: $input.css("font-weight"), - wordSpacing: $input.css("word-spacing"), - letterSpacing: $input.css("letter-spacing"), - textIndent: $input.css("text-indent"), - textRendering: $input.css("text-rendering"), - textTransform: $input.css("text-transform") - }).insertAfter($input); - } - function areQueriesEquivalent(a, b) { - return Input.normalizeQuery(a) === Input.normalizeQuery(b); - } - function withModifier($e) { - return $e.altKey || $e.ctrlKey || $e.metaKey || $e.shiftKey; - } - }(); - var Dataset = function() { - var datasetKey = "ttDataset", valueKey = "ttValue", datumKey = "ttDatum"; - function Dataset(o) { - o = o || {}; - o.templates = o.templates || {}; - if (!o.source) { - $.error("missing source"); - } - if (o.name && !isValidName(o.name)) { - $.error("invalid dataset name: " + o.name); - } - this.query = null; - this.highlight = !!o.highlight; - this.name = o.name || _.getUniqueId(); - this.source = o.source; - this.displayFn = getDisplayFn(o.display || o.displayKey); - this.templates = getTemplates(o.templates, this.displayFn); - this.$el = $(html.dataset.replace("%CLASS%", this.name)); - } - Dataset.extractDatasetName = function extractDatasetName(el) { - return $(el).data(datasetKey); - }; - Dataset.extractValue = function extractDatum(el) { - return $(el).data(valueKey); - }; - Dataset.extractDatum = function extractDatum(el) { - return $(el).data(datumKey); - }; - _.mixin(Dataset.prototype, EventEmitter, { - _render: function render(query, suggestions) { - if (!this.$el) { - return; - } - var that = this, hasSuggestions; - this.$el.empty(); - hasSuggestions = suggestions && suggestions.length; - if (!hasSuggestions && this.templates.empty) { - this.$el.html(getEmptyHtml()).prepend(that.templates.header ? getHeaderHtml() : null).append(that.templates.footer ? getFooterHtml() : null); - } else if (hasSuggestions) { - this.$el.html(getSuggestionsHtml()).prepend(that.templates.header ? getHeaderHtml() : null).append(that.templates.footer ? getFooterHtml() : null); - } - this.trigger("rendered"); - function getEmptyHtml() { - return that.templates.empty({ - query: query, - isEmpty: true - }); - } - function getSuggestionsHtml() { - var $suggestions, nodes; - $suggestions = $(html.suggestions).css(css.suggestions); - nodes = _.map(suggestions, getSuggestionNode); - $suggestions.append.apply($suggestions, nodes); - that.highlight && highlight({ - node: $suggestions[0], - pattern: query - }); - return $suggestions; - function getSuggestionNode(suggestion) { - var $el; - $el = $(html.suggestion).append(that.templates.suggestion(suggestion)).data(datasetKey, that.name).data(valueKey, that.displayFn(suggestion)).data(datumKey, suggestion); - $el.children().each(function() { - $(this).css(css.suggestionChild); - }); - return $el; - } - } - function getHeaderHtml() { - return that.templates.header({ - query: query, - isEmpty: !hasSuggestions - }); - } - function getFooterHtml() { - return that.templates.footer({ - query: query, - isEmpty: !hasSuggestions - }); - } - }, - getRoot: function getRoot() { - return this.$el; - }, - update: function update(query) { - var that = this; - this.query = query; - this.canceled = false; - this.source(query, render); - function render(suggestions) { - if (!that.canceled && query === that.query) { - that._render(query, suggestions); - } - } - }, - cancel: function cancel() { - this.canceled = true; - }, - clear: function clear() { - this.cancel(); - this.$el.empty(); - this.trigger("rendered"); - }, - isEmpty: function isEmpty() { - return this.$el.is(":empty"); - }, - destroy: function destroy() { - this.$el = null; - } - }); - return Dataset; - function getDisplayFn(display) { - display = display || "value"; - return _.isFunction(display) ? display : displayFn; - function displayFn(obj) { - return obj[display]; - } - } - function getTemplates(templates, displayFn) { - return { - empty: templates.empty && _.templatify(templates.empty), - header: templates.header && _.templatify(templates.header), - footer: templates.footer && _.templatify(templates.footer), - suggestion: templates.suggestion || suggestionTemplate - }; - function suggestionTemplate(context) { - return "

" + displayFn(context) + "

"; - } - } - function isValidName(str) { - return /^[_a-zA-Z0-9-]+$/.test(str); - } - }(); - var Dropdown = function() { - function Dropdown(o) { - var that = this, onSuggestionClick, onSuggestionMouseEnter, onSuggestionMouseLeave; - o = o || {}; - if (!o.menu) { - $.error("menu is required"); - } - this.isOpen = false; - this.isEmpty = true; - this.datasets = _.map(o.datasets, initializeDataset); - onSuggestionClick = _.bind(this._onSuggestionClick, this); - onSuggestionMouseEnter = _.bind(this._onSuggestionMouseEnter, this); - onSuggestionMouseLeave = _.bind(this._onSuggestionMouseLeave, this); - this.$menu = $(o.menu).on("click.tt", ".tt-suggestion", onSuggestionClick).on("mouseenter.tt", ".tt-suggestion", onSuggestionMouseEnter).on("mouseleave.tt", ".tt-suggestion", onSuggestionMouseLeave); - _.each(this.datasets, function(dataset) { - that.$menu.append(dataset.getRoot()); - dataset.onSync("rendered", that._onRendered, that); - }); - } - _.mixin(Dropdown.prototype, EventEmitter, { - _onSuggestionClick: function onSuggestionClick($e) { - this.trigger("suggestionClicked", $($e.currentTarget)); - }, - _onSuggestionMouseEnter: function onSuggestionMouseEnter($e) { - this._removeCursor(); - this._setCursor($($e.currentTarget), true); - }, - _onSuggestionMouseLeave: function onSuggestionMouseLeave() { - this._removeCursor(); - }, - _onRendered: function onRendered() { - this.isEmpty = _.every(this.datasets, isDatasetEmpty); - this.isEmpty ? this._hide() : this.isOpen && this._show(); - this.trigger("datasetRendered"); - function isDatasetEmpty(dataset) { - return dataset.isEmpty(); - } - }, - _hide: function() { - this.$menu.hide(); - }, - _show: function() { - this.$menu.css("display", "block"); - }, - _getSuggestions: function getSuggestions() { - return this.$menu.find(".tt-suggestion"); - }, - _getCursor: function getCursor() { - return this.$menu.find(".tt-cursor").first(); - }, - _setCursor: function setCursor($el, silent) { - $el.first().addClass("tt-cursor"); - !silent && this.trigger("cursorMoved"); - }, - _removeCursor: function removeCursor() { - this._getCursor().removeClass("tt-cursor"); - }, - _moveCursor: function moveCursor(increment) { - var $suggestions, $oldCursor, newCursorIndex, $newCursor; - if (!this.isOpen) { - return; - } - $oldCursor = this._getCursor(); - $suggestions = this._getSuggestions(); - this._removeCursor(); - newCursorIndex = $suggestions.index($oldCursor) + increment; - newCursorIndex = (newCursorIndex + 1) % ($suggestions.length + 1) - 1; - if (newCursorIndex === -1) { - this.trigger("cursorRemoved"); - return; - } else if (newCursorIndex < -1) { - newCursorIndex = $suggestions.length - 1; - } - this._setCursor($newCursor = $suggestions.eq(newCursorIndex)); - this._ensureVisible($newCursor); - }, - _ensureVisible: function ensureVisible($el) { - var elTop, elBottom, menuScrollTop, menuHeight; - elTop = $el.position().top; - elBottom = elTop + $el.outerHeight(true); - menuScrollTop = this.$menu.scrollTop(); - menuHeight = this.$menu.height() + parseInt(this.$menu.css("paddingTop"), 10) + parseInt(this.$menu.css("paddingBottom"), 10); - if (elTop < 0) { - this.$menu.scrollTop(menuScrollTop + elTop); - } else if (menuHeight < elBottom) { - this.$menu.scrollTop(menuScrollTop + (elBottom - menuHeight)); - } - }, - close: function close() { - if (this.isOpen) { - this.isOpen = false; - this._removeCursor(); - this._hide(); - this.trigger("closed"); - } - }, - open: function open() { - if (!this.isOpen) { - this.isOpen = true; - !this.isEmpty && this._show(); - this.trigger("opened"); - } - }, - setLanguageDirection: function setLanguageDirection(dir) { - this.$menu.css(dir === "ltr" ? css.ltr : css.rtl); - }, - moveCursorUp: function moveCursorUp() { - this._moveCursor(-1); - }, - moveCursorDown: function moveCursorDown() { - this._moveCursor(+1); - }, - getDatumForSuggestion: function getDatumForSuggestion($el) { - var datum = null; - if ($el.length) { - datum = { - raw: Dataset.extractDatum($el), - value: Dataset.extractValue($el), - datasetName: Dataset.extractDatasetName($el) - }; - } - return datum; - }, - getDatumForCursor: function getDatumForCursor() { - return this.getDatumForSuggestion(this._getCursor().first()); - }, - getDatumForTopSuggestion: function getDatumForTopSuggestion() { - return this.getDatumForSuggestion(this._getSuggestions().first()); - }, - update: function update(query) { - _.each(this.datasets, updateDataset); - function updateDataset(dataset) { - dataset.update(query); - } - }, - empty: function empty() { - _.each(this.datasets, clearDataset); - this.isEmpty = true; - function clearDataset(dataset) { - dataset.clear(); - } - }, - isVisible: function isVisible() { - return this.isOpen && !this.isEmpty; - }, - destroy: function destroy() { - this.$menu.off(".tt"); - this.$menu = null; - _.each(this.datasets, destroyDataset); - function destroyDataset(dataset) { - dataset.destroy(); - } - } - }); - return Dropdown; - function initializeDataset(oDataset) { - return new Dataset(oDataset); - } - }(); - var Typeahead = function() { - var attrsKey = "ttAttrs"; - function Typeahead(o) { - var $menu, $input, $hint; - o = o || {}; - if (!o.input) { - $.error("missing input"); - } - this.isActivated = false; - this.autoselect = !!o.autoselect; - this.minLength = _.isNumber(o.minLength) ? o.minLength : 1; - this.$node = buildDomStructure(o.input, o.withHint); - $menu = this.$node.find(".tt-dropdown-menu"); - $input = this.$node.find(".tt-input"); - $hint = this.$node.find(".tt-hint"); - $input.on("blur.tt", function($e) { - var active, isActive, hasActive; - active = document.activeElement; - isActive = $menu.is(active); - hasActive = $menu.has(active).length > 0; - if (_.isMsie() && (isActive || hasActive)) { - $e.preventDefault(); - $e.stopImmediatePropagation(); - _.defer(function() { - $input.focus(); - }); - } - }); - $menu.on("mousedown.tt", function($e) { - $e.preventDefault(); - }); - this.eventBus = o.eventBus || new EventBus({ - el: $input - }); - this.dropdown = new Dropdown({ - menu: $menu, - datasets: o.datasets - }).onSync("suggestionClicked", this._onSuggestionClicked, this).onSync("cursorMoved", this._onCursorMoved, this).onSync("cursorRemoved", this._onCursorRemoved, this).onSync("opened", this._onOpened, this).onSync("closed", this._onClosed, this).onAsync("datasetRendered", this._onDatasetRendered, this); - this.input = new Input({ - input: $input, - hint: $hint - }).onSync("focused", this._onFocused, this).onSync("blurred", this._onBlurred, this).onSync("enterKeyed", this._onEnterKeyed, this).onSync("tabKeyed", this._onTabKeyed, this).onSync("escKeyed", this._onEscKeyed, this).onSync("upKeyed", this._onUpKeyed, this).onSync("downKeyed", this._onDownKeyed, this).onSync("leftKeyed", this._onLeftKeyed, this).onSync("rightKeyed", this._onRightKeyed, this).onSync("queryChanged", this._onQueryChanged, this).onSync("whitespaceChanged", this._onWhitespaceChanged, this); - this._setLanguageDirection(); - } - _.mixin(Typeahead.prototype, { - _onSuggestionClicked: function onSuggestionClicked(type, $el) { - var datum; - if (datum = this.dropdown.getDatumForSuggestion($el)) { - this._select(datum); - } - }, - _onCursorMoved: function onCursorMoved() { - var datum = this.dropdown.getDatumForCursor(); - this.input.setInputValue(datum.value, true); - this.eventBus.trigger("cursorchanged", datum.raw, datum.datasetName); - }, - _onCursorRemoved: function onCursorRemoved() { - this.input.resetInputValue(); - this._updateHint(); - }, - _onDatasetRendered: function onDatasetRendered() { - this._updateHint(); - }, - _onOpened: function onOpened() { - this._updateHint(); - this.eventBus.trigger("opened"); - }, - _onClosed: function onClosed() { - this.input.clearHint(); - this.eventBus.trigger("closed"); - }, - _onFocused: function onFocused() { - this.isActivated = true; - this.dropdown.open(); - }, - _onBlurred: function onBlurred() { - this.isActivated = false; - this.dropdown.empty(); - this.dropdown.close(); - }, - _onEnterKeyed: function onEnterKeyed(type, $e) { - var cursorDatum, topSuggestionDatum; - cursorDatum = this.dropdown.getDatumForCursor(); - topSuggestionDatum = this.dropdown.getDatumForTopSuggestion(); - if (cursorDatum) { - this._select(cursorDatum); - $e.preventDefault(); - } else if (this.autoselect && topSuggestionDatum) { - this._select(topSuggestionDatum); - $e.preventDefault(); - } - }, - _onTabKeyed: function onTabKeyed(type, $e) { - var datum; - if (datum = this.dropdown.getDatumForCursor()) { - this._select(datum); - $e.preventDefault(); - } else { - this._autocomplete(true); - } - }, - _onEscKeyed: function onEscKeyed() { - this.dropdown.close(); - this.input.resetInputValue(); - }, - _onUpKeyed: function onUpKeyed() { - var query = this.input.getQuery(); - this.dropdown.isEmpty && query.length >= this.minLength ? this.dropdown.update(query) : this.dropdown.moveCursorUp(); - this.dropdown.open(); - }, - _onDownKeyed: function onDownKeyed() { - var query = this.input.getQuery(); - this.dropdown.isEmpty && query.length >= this.minLength ? this.dropdown.update(query) : this.dropdown.moveCursorDown(); - this.dropdown.open(); - }, - _onLeftKeyed: function onLeftKeyed() { - this.dir === "rtl" && this._autocomplete(); - }, - _onRightKeyed: function onRightKeyed() { - this.dir === "ltr" && this._autocomplete(); - }, - _onQueryChanged: function onQueryChanged(e, query) { - this.input.clearHintIfInvalid(); - query.length >= this.minLength ? this.dropdown.update(query) : this.dropdown.empty(); - this.dropdown.open(); - this._setLanguageDirection(); - }, - _onWhitespaceChanged: function onWhitespaceChanged() { - this._updateHint(); - this.dropdown.open(); - }, - _setLanguageDirection: function setLanguageDirection() { - var dir; - if (this.dir !== (dir = this.input.getLanguageDirection())) { - this.dir = dir; - this.$node.css("direction", dir); - this.dropdown.setLanguageDirection(dir); - } - }, - _updateHint: function updateHint() { - var datum, val, query, escapedQuery, frontMatchRegEx, match; - datum = this.dropdown.getDatumForTopSuggestion(); - if (datum && this.dropdown.isVisible() && !this.input.hasOverflow()) { - val = this.input.getInputValue(); - query = Input.normalizeQuery(val); - escapedQuery = _.escapeRegExChars(query); - frontMatchRegEx = new RegExp("^(?:" + escapedQuery + ")(.+$)", "i"); - match = frontMatchRegEx.exec(datum.value); - match ? this.input.setHint(val + match[1]) : this.input.clearHint(); - } else { - this.input.clearHint(); - } - }, - _autocomplete: function autocomplete(laxCursor) { - var hint, query, isCursorAtEnd, datum; - hint = this.input.getHint(); - query = this.input.getQuery(); - isCursorAtEnd = laxCursor || this.input.isCursorAtEnd(); - if (hint && query !== hint && isCursorAtEnd) { - datum = this.dropdown.getDatumForTopSuggestion(); - datum && this.input.setInputValue(datum.value); - this.eventBus.trigger("autocompleted", datum.raw, datum.datasetName); - } - }, - _select: function select(datum) { - this.input.setQuery(datum.value); - this.input.setInputValue(datum.value, true); - this._setLanguageDirection(); - this.eventBus.trigger("selected", datum.raw, datum.datasetName); - this.dropdown.close(); - _.defer(_.bind(this.dropdown.empty, this.dropdown)); - }, - open: function open() { - this.dropdown.open(); - }, - close: function close() { - this.dropdown.close(); - }, - setVal: function setVal(val) { - if (this.isActivated) { - this.input.setInputValue(val); - } else { - this.input.setQuery(val); - this.input.setInputValue(val, true); - } - this._setLanguageDirection(); - }, - getVal: function getVal() { - return this.input.getQuery(); - }, - destroy: function destroy() { - this.input.destroy(); - this.dropdown.destroy(); - destroyDomStructure(this.$node); - this.$node = null; - } - }); - return Typeahead; - function buildDomStructure(input, withHint) { - var $input, $wrapper, $dropdown, $hint; - $input = $(input); - $wrapper = $(html.wrapper).css(css.wrapper); - $dropdown = $(html.dropdown).css(css.dropdown); - $hint = $input.clone().css(css.hint).css(getBackgroundStyles($input)); - $hint.val("").removeData().addClass("tt-hint").removeAttr("id name placeholder").prop("disabled", true).attr({ - autocomplete: "off", - spellcheck: "false" - }); - $input.data(attrsKey, { - dir: $input.attr("dir"), - autocomplete: $input.attr("autocomplete"), - spellcheck: $input.attr("spellcheck"), - style: $input.attr("style") - }); - $input.addClass("tt-input").attr({ - autocomplete: "off", - spellcheck: false - }).css(withHint ? css.input : css.inputWithNoHint); - try { - !$input.attr("dir") && $input.attr("dir", "auto"); - } catch (e) {} - return $input.wrap($wrapper).parent().prepend(withHint ? $hint : null).append($dropdown); - } - function getBackgroundStyles($el) { - return { - backgroundAttachment: $el.css("background-attachment"), - backgroundClip: $el.css("background-clip"), - backgroundColor: $el.css("background-color"), - backgroundImage: $el.css("background-image"), - backgroundOrigin: $el.css("background-origin"), - backgroundPosition: $el.css("background-position"), - backgroundRepeat: $el.css("background-repeat"), - backgroundSize: $el.css("background-size") - }; - } - function destroyDomStructure($node) { - var $input = $node.find(".tt-input"); - _.each($input.data(attrsKey), function(val, key) { - _.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val); - }); - $input.detach().removeData(attrsKey).removeClass("tt-input").insertAfter($node); - $node.remove(); - } - }(); - (function() { - var old, typeaheadKey, methods; - old = $.fn.typeahead; - typeaheadKey = "ttTypeahead"; - methods = { - initialize: function initialize(o, datasets) { - datasets = _.isArray(datasets) ? datasets : [].slice.call(arguments, 1); - o = o || {}; - return this.each(attach); - function attach() { - var $input = $(this), eventBus, typeahead; - _.each(datasets, function(d) { - d.highlight = !!o.highlight; - }); - typeahead = new Typeahead({ - input: $input, - eventBus: eventBus = new EventBus({ - el: $input - }), - withHint: _.isUndefined(o.hint) ? true : !!o.hint, - minLength: o.minLength, - autoselect: o.autoselect, - datasets: datasets - }); - $input.data(typeaheadKey, typeahead); - } - }, - open: function open() { - return this.each(openTypeahead); - function openTypeahead() { - var $input = $(this), typeahead; - if (typeahead = $input.data(typeaheadKey)) { - typeahead.open(); - } - } - }, - close: function close() { - return this.each(closeTypeahead); - function closeTypeahead() { - var $input = $(this), typeahead; - if (typeahead = $input.data(typeaheadKey)) { - typeahead.close(); - } - } - }, - val: function val(newVal) { - return !arguments.length ? getVal(this.first()) : this.each(setVal); - function setVal() { - var $input = $(this), typeahead; - if (typeahead = $input.data(typeaheadKey)) { - typeahead.setVal(newVal); - } - } - function getVal($input) { - var typeahead, query; - if (typeahead = $input.data(typeaheadKey)) { - query = typeahead.getVal(); - } - return query; - } - }, - destroy: function destroy() { - return this.each(unattach); - function unattach() { - var $input = $(this), typeahead; - if (typeahead = $input.data(typeaheadKey)) { - typeahead.destroy(); - $input.removeData(typeaheadKey); - } - } - } - }; - $.fn.typeahead = function(method) { - if (methods[method]) { - return methods[method].apply(this, [].slice.call(arguments, 1)); - } else { - return methods.initialize.apply(this, arguments); - } - }; - $.fn.typeahead.noConflict = function noConflict() { - $.fn.typeahead = old; - return this; - }; - })(); +/*! + * typeahead.js 0.10.2 + * https://github.com/twitter/typeahead.js + * Copyright 2013-2014 Twitter, Inc. and other contributors; Licensed MIT + */ + +(function($) { + var _ = { + isMsie: function() { + return /(msie|trident)/i.test(navigator.userAgent) ? navigator.userAgent.match(/(msie |rv:)(\d+(.\d+)?)/i)[2] : false; + }, + isBlankString: function(str) { + return !str || /^\s*$/.test(str); + }, + escapeRegExChars: function(str) { + return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); + }, + isString: function(obj) { + return typeof obj === "string"; + }, + isNumber: function(obj) { + return typeof obj === "number"; + }, + isArray: $.isArray, + isFunction: $.isFunction, + isObject: $.isPlainObject, + isUndefined: function(obj) { + return typeof obj === "undefined"; + }, + bind: $.proxy, + each: function(collection, cb) { + $.each(collection, reverseArgs); + function reverseArgs(index, value) { + return cb(value, index); + } + }, + map: $.map, + filter: $.grep, + every: function(obj, test) { + var result = true; + if (!obj) { + return result; + } + $.each(obj, function(key, val) { + if (!(result = test.call(null, val, key, obj))) { + return false; + } + }); + return !!result; + }, + some: function(obj, test) { + var result = false; + if (!obj) { + return result; + } + $.each(obj, function(key, val) { + if (result = test.call(null, val, key, obj)) { + return false; + } + }); + return !!result; + }, + mixin: $.extend, + getUniqueId: function() { + var counter = 0; + return function() { + return counter++; + }; + }(), + templatify: function templatify(obj) { + return $.isFunction(obj) ? obj : template; + function template() { + return String(obj); + } + }, + defer: function(fn) { + setTimeout(fn, 0); + }, + debounce: function(func, wait, immediate) { + var timeout, result; + return function() { + var context = this, args = arguments, later, callNow; + later = function() { + timeout = null; + if (!immediate) { + result = func.apply(context, args); + } + }; + callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) { + result = func.apply(context, args); + } + return result; + }; + }, + throttle: function(func, wait) { + var context, args, timeout, result, previous, later; + previous = 0; + later = function() { + previous = new Date(); + timeout = null; + result = func.apply(context, args); + }; + return function() { + var now = new Date(), remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0) { + clearTimeout(timeout); + timeout = null; + previous = now; + result = func.apply(context, args); + } else if (!timeout) { + timeout = setTimeout(later, remaining); + } + return result; + }; + }, + noop: function() {} + }; + var VERSION = "0.10.2"; + var tokenizers = function(root) { + return { + nonword: nonword, + whitespace: whitespace, + obj: { + nonword: getObjTokenizer(nonword), + whitespace: getObjTokenizer(whitespace) + } + }; + function whitespace(s) { + return s.split(/\s+/); + } + function nonword(s) { + return s.split(/\W+/); + } + function getObjTokenizer(tokenizer) { + return function setKey(key) { + return function tokenize(o) { + return tokenizer(o[key]); + }; + }; + } + }(); + var LruCache = function() { + function LruCache(maxSize) { + this.maxSize = maxSize || 100; + this.size = 0; + this.hash = {}; + this.list = new List(); + } + _.mixin(LruCache.prototype, { + set: function set(key, val) { + var tailItem = this.list.tail, node; + if (this.size >= this.maxSize) { + this.list.remove(tailItem); + delete this.hash[tailItem.key]; + } + if (node = this.hash[key]) { + node.val = val; + this.list.moveToFront(node); + } else { + node = new Node(key, val); + this.list.add(node); + this.hash[key] = node; + this.size++; + } + }, + get: function get(key) { + var node = this.hash[key]; + if (node) { + this.list.moveToFront(node); + return node.val; + } + } + }); + function List() { + this.head = this.tail = null; + } + _.mixin(List.prototype, { + add: function add(node) { + if (this.head) { + node.next = this.head; + this.head.prev = node; + } + this.head = node; + this.tail = this.tail || node; + }, + remove: function remove(node) { + node.prev ? node.prev.next = node.next : this.head = node.next; + node.next ? node.next.prev = node.prev : this.tail = node.prev; + }, + moveToFront: function(node) { + this.remove(node); + this.add(node); + } + }); + function Node(key, val) { + this.key = key; + this.val = val; + this.prev = this.next = null; + } + return LruCache; + }(); + var PersistentStorage = function() { + var ls, methods; + try { + ls = window.localStorage; + ls.setItem("~~~", "!"); + ls.removeItem("~~~"); + } catch (err) { + ls = null; + } + function PersistentStorage(namespace) { + this.prefix = [ "__", namespace, "__" ].join(""); + this.ttlKey = "__ttl__"; + this.keyMatcher = new RegExp("^" + this.prefix); + } + if (ls && window.JSON) { + methods = { + _prefix: function(key) { + return this.prefix + key; + }, + _ttlKey: function(key) { + return this._prefix(key) + this.ttlKey; + }, + get: function(key) { + if (this.isExpired(key)) { + this.remove(key); + } + return decode(ls.getItem(this._prefix(key))); + }, + set: function(key, val, ttl) { + if (_.isNumber(ttl)) { + ls.setItem(this._ttlKey(key), encode(now() + ttl)); + } else { + ls.removeItem(this._ttlKey(key)); + } + return ls.setItem(this._prefix(key), encode(val)); + }, + remove: function(key) { + ls.removeItem(this._ttlKey(key)); + ls.removeItem(this._prefix(key)); + return this; + }, + clear: function() { + var i, key, keys = [], len = ls.length; + for (i = 0; i < len; i++) { + if ((key = ls.key(i)).match(this.keyMatcher)) { + keys.push(key.replace(this.keyMatcher, "")); + } + } + for (i = keys.length; i--; ) { + this.remove(keys[i]); + } + return this; + }, + isExpired: function(key) { + var ttl = decode(ls.getItem(this._ttlKey(key))); + return _.isNumber(ttl) && now() > ttl ? true : false; + } + }; + } else { + methods = { + get: _.noop, + set: _.noop, + remove: _.noop, + clear: _.noop, + isExpired: _.noop + }; + } + _.mixin(PersistentStorage.prototype, methods); + return PersistentStorage; + function now() { + return new Date().getTime(); + } + function encode(val) { + return JSON.stringify(_.isUndefined(val) ? null : val); + } + function decode(val) { + return JSON.parse(val); + } + }(); + var Transport = function() { + var pendingRequestsCount = 0, pendingRequests = {}, maxPendingRequests = 6, requestCache = new LruCache(10); + function Transport(o) { + o = o || {}; + this._send = o.transport ? callbackToDeferred(o.transport) : $.ajax; + this._get = o.rateLimiter ? o.rateLimiter(this._get) : this._get; + } + Transport.setMaxPendingRequests = function setMaxPendingRequests(num) { + maxPendingRequests = num; + }; + Transport.resetCache = function clearCache() { + requestCache = new LruCache(10); + }; + _.mixin(Transport.prototype, { + _get: function(url, o, cb) { + var that = this, jqXhr; + if (jqXhr = pendingRequests[url]) { + jqXhr.done(done).fail(fail); + } else if (pendingRequestsCount < maxPendingRequests) { + pendingRequestsCount++; + pendingRequests[url] = this._send(url, o).done(done).fail(fail).always(always); + } else { + this.onDeckRequestArgs = [].slice.call(arguments, 0); + } + function done(resp) { + cb && cb(null, resp); + requestCache.set(url, resp); + } + function fail() { + cb && cb(true); + } + function always() { + pendingRequestsCount--; + delete pendingRequests[url]; + if (that.onDeckRequestArgs) { + that._get.apply(that, that.onDeckRequestArgs); + that.onDeckRequestArgs = null; + } + } + }, + get: function(url, o, cb) { + var resp; + if (_.isFunction(o)) { + cb = o; + o = {}; + } + if (resp = requestCache.get(url)) { + _.defer(function() { + cb && cb(null, resp); + }); + } else { + this._get(url, o, cb); + } + return !!resp; + } + }); + return Transport; + function callbackToDeferred(fn) { + return function customSendWrapper(url, o) { + var deferred = $.Deferred(); + fn(url, o, onSuccess, onError); + return deferred; + function onSuccess(resp) { + _.defer(function() { + deferred.resolve(resp); + }); + } + function onError(err) { + _.defer(function() { + deferred.reject(err); + }); + } + }; + } + }(); + var SearchIndex = function() { + function SearchIndex(o) { + o = o || {}; + if (!o.datumTokenizer || !o.queryTokenizer) { + $.error("datumTokenizer and queryTokenizer are both required"); + } + this.datumTokenizer = o.datumTokenizer; + this.queryTokenizer = o.queryTokenizer; + this.reset(); + } + _.mixin(SearchIndex.prototype, { + bootstrap: function bootstrap(o) { + this.datums = o.datums; + this.trie = o.trie; + }, + add: function(data) { + var that = this; + data = _.isArray(data) ? data : [ data ]; + _.each(data, function(datum) { + var id, tokens; + id = that.datums.push(datum) - 1; + tokens = normalizeTokens(that.datumTokenizer(datum)); + _.each(tokens, function(token) { + var node, chars, ch; + node = that.trie; + chars = token.split(""); + while (ch = chars.shift()) { + node = node.children[ch] || (node.children[ch] = newNode()); + node.ids.push(id); + } + }); + }); + }, + get: function get(query) { + var that = this, tokens, matches; + tokens = normalizeTokens(this.queryTokenizer(query)); + _.each(tokens, function(token) { + var node, chars, ch, ids; + if (matches && matches.length === 0) { + return false; + } + node = that.trie; + chars = token.split(""); + while (node && (ch = chars.shift())) { + node = node.children[ch]; + } + if (node && chars.length === 0) { + ids = node.ids.slice(0); + matches = matches ? getIntersection(matches, ids) : ids; + } else { + matches = []; + return false; + } + }); + return matches ? _.map(unique(matches), function(id) { + return that.datums[id]; + }) : []; + }, + reset: function reset() { + this.datums = []; + this.trie = newNode(); + }, + serialize: function serialize() { + return { + datums: this.datums, + trie: this.trie + }; + } + }); + return SearchIndex; + function normalizeTokens(tokens) { + tokens = _.filter(tokens, function(token) { + return !!token; + }); + tokens = _.map(tokens, function(token) { + return token.toLowerCase(); + }); + return tokens; + } + function newNode() { + return { + ids: [], + children: {} + }; + } + function unique(array) { + var seen = {}, uniques = []; + for (var i = 0; i < array.length; i++) { + if (!seen[array[i]]) { + seen[array[i]] = true; + uniques.push(array[i]); + } + } + return uniques; + } + function getIntersection(arrayA, arrayB) { + var ai = 0, bi = 0, intersection = []; + arrayA = arrayA.sort(compare); + arrayB = arrayB.sort(compare); + while (ai < arrayA.length && bi < arrayB.length) { + if (arrayA[ai] < arrayB[bi]) { + ai++; + } else if (arrayA[ai] > arrayB[bi]) { + bi++; + } else { + intersection.push(arrayA[ai]); + ai++; + bi++; + } + } + return intersection; + function compare(a, b) { + return a - b; + } + } + }(); + var oParser = function() { + return { + local: getLocal, + prefetch: getPrefetch, + remote: getRemote + }; + function getLocal(o) { + return o.local || null; + } + function getPrefetch(o) { + var prefetch, defaults; + defaults = { + url: null, + thumbprint: "", + ttl: 24 * 60 * 60 * 1e3, + filter: null, + ajax: {} + }; + if (prefetch = o.prefetch || null) { + prefetch = _.isString(prefetch) ? { + url: prefetch + } : prefetch; + prefetch = _.mixin(defaults, prefetch); + prefetch.thumbprint = VERSION + prefetch.thumbprint; + prefetch.ajax.type = prefetch.ajax.type || "GET"; + prefetch.ajax.dataType = prefetch.ajax.dataType || "json"; + !prefetch.url && $.error("prefetch requires url to be set"); + } + return prefetch; + } + function getRemote(o) { + var remote, defaults; + defaults = { + url: null, + wildcard: "%QUERY", + replace: null, + rateLimitBy: "debounce", + rateLimitWait: 300, + send: null, + filter: null, + ajax: {} + }; + if (remote = o.remote || null) { + remote = _.isString(remote) ? { + url: remote + } : remote; + remote = _.mixin(defaults, remote); + remote.rateLimiter = /^throttle$/i.test(remote.rateLimitBy) ? byThrottle(remote.rateLimitWait) : byDebounce(remote.rateLimitWait); + remote.ajax.type = remote.ajax.type || "GET"; + remote.ajax.dataType = remote.ajax.dataType || "json"; + delete remote.rateLimitBy; + delete remote.rateLimitWait; + !remote.url && $.error("remote requires url to be set"); + } + return remote; + function byDebounce(wait) { + return function(fn) { + return _.debounce(fn, wait); + }; + } + function byThrottle(wait) { + return function(fn) { + return _.throttle(fn, wait); + }; + } + } + }(); + (function(root) { + var old, keys; + old = root.Bloodhound; + keys = { + data: "data", + protocol: "protocol", + thumbprint: "thumbprint" + }; + root.Bloodhound = Bloodhound; + function Bloodhound(o) { + if (!o || !o.local && !o.prefetch && !o.remote) { + $.error("one of local, prefetch, or remote is required"); + } + this.limit = o.limit || 5; + this.sorter = getSorter(o.sorter); + this.dupDetector = o.dupDetector || ignoreDuplicates; + this.local = oParser.local(o); + this.prefetch = oParser.prefetch(o); + this.remote = oParser.remote(o); + this.cacheKey = this.prefetch ? this.prefetch.cacheKey || this.prefetch.url : null; + this.index = new SearchIndex({ + datumTokenizer: o.datumTokenizer, + queryTokenizer: o.queryTokenizer + }); + this.storage = this.cacheKey ? new PersistentStorage(this.cacheKey) : null; + } + Bloodhound.noConflict = function noConflict() { + root.Bloodhound = old; + return Bloodhound; + }; + Bloodhound.tokenizers = tokenizers; + _.mixin(Bloodhound.prototype, { + _loadPrefetch: function loadPrefetch(o) { + var that = this, serialized, deferred; + if (serialized = this._readFromStorage(o.thumbprint)) { + this.index.bootstrap(serialized); + deferred = $.Deferred().resolve(); + } else { + deferred = $.ajax(o.url, o.ajax).done(handlePrefetchResponse); + } + return deferred; + function handlePrefetchResponse(resp) { + that.clear(); + that.add(o.filter ? o.filter(resp) : resp); + that._saveToStorage(that.index.serialize(), o.thumbprint, o.ttl); + } + }, + _getFromRemote: function getFromRemote(query, cb) { + var that = this, url, uriEncodedQuery; + query = query || ""; + uriEncodedQuery = encodeURIComponent(query); + url = this.remote.replace ? this.remote.replace(this.remote.url, query) : this.remote.url.replace(this.remote.wildcard, uriEncodedQuery); + return this.transport.get(url, this.remote.ajax, handleRemoteResponse); + function handleRemoteResponse(err, resp) { + err ? cb([]) : cb(that.remote.filter ? that.remote.filter(resp) : resp); + } + }, + _saveToStorage: function saveToStorage(data, thumbprint, ttl) { + if (this.storage) { + this.storage.set(keys.data, data, ttl); + this.storage.set(keys.protocol, location.protocol, ttl); + this.storage.set(keys.thumbprint, thumbprint, ttl); + } + }, + _readFromStorage: function readFromStorage(thumbprint) { + var stored = {}, isExpired; + if (this.storage) { + stored.data = this.storage.get(keys.data); + stored.protocol = this.storage.get(keys.protocol); + stored.thumbprint = this.storage.get(keys.thumbprint); + } + isExpired = stored.thumbprint !== thumbprint || stored.protocol !== location.protocol; + return stored.data && !isExpired ? stored.data : null; + }, + _initialize: function initialize() { + var that = this, local = this.local, deferred; + deferred = this.prefetch ? this._loadPrefetch(this.prefetch) : $.Deferred().resolve(); + local && deferred.done(addLocalToIndex); + this.transport = this.remote ? new Transport(this.remote) : null; + return this.initPromise = deferred.promise(); + function addLocalToIndex() { + that.add(_.isFunction(local) ? local() : local); + } + }, + initialize: function initialize(force) { + return !this.initPromise || force ? this._initialize() : this.initPromise; + }, + add: function add(data) { + this.index.add(data); + }, + get: function get(query, cb) { + var that = this, matches = [], cacheHit = false; + matches = this.index.get(query); + matches = this.sorter(matches).slice(0, this.limit); + if (matches.length < this.limit && this.transport) { + cacheHit = this._getFromRemote(query, returnRemoteMatches); + } + if (!cacheHit) { + (matches.length > 0 || !this.transport) && cb && cb(matches); + } + function returnRemoteMatches(remoteMatches) { + var matchesWithBackfill = matches.slice(0); + _.each(remoteMatches, function(remoteMatch) { + var isDuplicate; + isDuplicate = _.some(matchesWithBackfill, function(match) { + return that.dupDetector(remoteMatch, match); + }); + !isDuplicate && matchesWithBackfill.push(remoteMatch); + return matchesWithBackfill.length < that.limit; + }); + cb && cb(that.sorter(matchesWithBackfill)); + } + }, + clear: function clear() { + this.index.reset(); + }, + clearPrefetchCache: function clearPrefetchCache() { + this.storage && this.storage.clear(); + }, + clearRemoteCache: function clearRemoteCache() { + this.transport && Transport.resetCache(); + }, + ttAdapter: function ttAdapter() { + return _.bind(this.get, this); + } + }); + return Bloodhound; + function getSorter(sortFn) { + return _.isFunction(sortFn) ? sort : noSort; + function sort(array) { + return array.sort(sortFn); + } + function noSort(array) { + return array; + } + } + function ignoreDuplicates() { + return false; + } + })(this); + var html = { + wrapper: '', + dropdown: '', + dataset: '
', + suggestions: '', + suggestion: '
' + }; + var css = { + wrapper: { + position: "relative", + display: "inline-block" + }, + hint: { + position: "absolute", + top: "0", + left: "0", + borderColor: "transparent", + boxShadow: "none" + }, + input: { + position: "relative", + verticalAlign: "top", + backgroundColor: "transparent" + }, + inputWithNoHint: { + position: "relative", + verticalAlign: "top" + }, + dropdown: { + position: "absolute", + top: "100%", + left: "0", + zIndex: "100", + display: "none" + }, + suggestions: { + display: "block" + }, + suggestion: { + whiteSpace: "nowrap", + cursor: "pointer" + }, + suggestionChild: { + whiteSpace: "normal" + }, + ltr: { + left: "0", + right: "auto" + }, + rtl: { + left: "auto", + right: " 0" + } + }; + if (_.isMsie()) { + _.mixin(css.input, { + backgroundImage: "url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)" + }); + } + if (_.isMsie() && _.isMsie() <= 7) { + _.mixin(css.input, { + marginTop: "-1px" + }); + } + var EventBus = function() { + var namespace = "typeahead:"; + function EventBus(o) { + if (!o || !o.el) { + $.error("EventBus initialized without el"); + } + this.$el = $(o.el); + } + _.mixin(EventBus.prototype, { + trigger: function(type) { + var args = [].slice.call(arguments, 1); + this.$el.trigger(namespace + type, args); + } + }); + return EventBus; + }(); + var EventEmitter = function() { + var splitter = /\s+/, nextTick = getNextTick(); + return { + onSync: onSync, + onAsync: onAsync, + off: off, + trigger: trigger + }; + function on(method, types, cb, context) { + var type; + if (!cb) { + return this; + } + types = types.split(splitter); + cb = context ? bindContext(cb, context) : cb; + this._callbacks = this._callbacks || {}; + while (type = types.shift()) { + this._callbacks[type] = this._callbacks[type] || { + sync: [], + async: [] + }; + this._callbacks[type][method].push(cb); + } + return this; + } + function onAsync(types, cb, context) { + return on.call(this, "async", types, cb, context); + } + function onSync(types, cb, context) { + return on.call(this, "sync", types, cb, context); + } + function off(types) { + var type; + if (!this._callbacks) { + return this; + } + types = types.split(splitter); + while (type = types.shift()) { + delete this._callbacks[type]; + } + return this; + } + function trigger(types) { + var type, callbacks, args, syncFlush, asyncFlush; + if (!this._callbacks) { + return this; + } + types = types.split(splitter); + args = [].slice.call(arguments, 1); + while ((type = types.shift()) && (callbacks = this._callbacks[type])) { + syncFlush = getFlush(callbacks.sync, this, [ type ].concat(args)); + asyncFlush = getFlush(callbacks.async, this, [ type ].concat(args)); + syncFlush() && nextTick(asyncFlush); + } + return this; + } + function getFlush(callbacks, context, args) { + return flush; + function flush() { + var cancelled; + for (var i = 0; !cancelled && i < callbacks.length; i += 1) { + cancelled = callbacks[i].apply(context, args) === false; + } + return !cancelled; + } + } + function getNextTick() { + var nextTickFn; + if (window.setImmediate) { + nextTickFn = function nextTickSetImmediate(fn) { + setImmediate(function() { + fn(); + }); + }; + } else { + nextTickFn = function nextTickSetTimeout(fn) { + setTimeout(function() { + fn(); + }, 0); + }; + } + return nextTickFn; + } + function bindContext(fn, context) { + return fn.bind ? fn.bind(context) : function() { + fn.apply(context, [].slice.call(arguments, 0)); + }; + } + }(); + var highlight = function(doc) { + var defaults = { + node: null, + pattern: null, + tagName: "strong", + className: null, + wordsOnly: false, + caseSensitive: false + }; + return function hightlight(o) { + var regex; + o = _.mixin({}, defaults, o); + if (!o.node || !o.pattern) { + return; + } + o.pattern = _.isArray(o.pattern) ? o.pattern : [ o.pattern ]; + regex = getRegex(o.pattern, o.caseSensitive, o.wordsOnly); + traverse(o.node, hightlightTextNode); + function hightlightTextNode(textNode) { + var match, patternNode; + if (match = regex.exec(textNode.data)) { + wrapperNode = doc.createElement(o.tagName); + o.className && (wrapperNode.className = o.className); + patternNode = textNode.splitText(match.index); + patternNode.splitText(match[0].length); + wrapperNode.appendChild(patternNode.cloneNode(true)); + textNode.parentNode.replaceChild(wrapperNode, patternNode); + } + return !!match; + } + function traverse(el, hightlightTextNode) { + var childNode, TEXT_NODE_TYPE = 3; + for (var i = 0; i < el.childNodes.length; i++) { + childNode = el.childNodes[i]; + if (childNode.nodeType === TEXT_NODE_TYPE) { + i += hightlightTextNode(childNode) ? 1 : 0; + } else { + traverse(childNode, hightlightTextNode); + } + } + } + }; + function getRegex(patterns, caseSensitive, wordsOnly) { + var escapedPatterns = [], regexStr; + for (var i = 0; i < patterns.length; i++) { + escapedPatterns.push(_.escapeRegExChars(patterns[i])); + } + regexStr = wordsOnly ? "\\b(" + escapedPatterns.join("|") + ")\\b" : "(" + escapedPatterns.join("|") + ")"; + return caseSensitive ? new RegExp(regexStr) : new RegExp(regexStr, "i"); + } + }(window.document); + var Input = function() { + var specialKeyCodeMap; + specialKeyCodeMap = { + 9: "tab", + 27: "esc", + 37: "left", + 39: "right", + 13: "enter", + 38: "up", + 40: "down" + }; + function Input(o) { + var that = this, onBlur, onFocus, onKeydown, onInput; + o = o || {}; + if (!o.input) { + $.error("input is missing"); + } + onBlur = _.bind(this._onBlur, this); + onFocus = _.bind(this._onFocus, this); + onKeydown = _.bind(this._onKeydown, this); + onInput = _.bind(this._onInput, this); + this.$hint = $(o.hint); + this.$input = $(o.input).on("blur.tt", onBlur).on("focus.tt", onFocus).on("keydown.tt", onKeydown); + if (this.$hint.length === 0) { + this.setHint = this.getHint = this.clearHint = this.clearHintIfInvalid = _.noop; + } + if (!_.isMsie()) { + this.$input.on("input.tt", onInput); + } else { + this.$input.on("keydown.tt keypress.tt cut.tt paste.tt", function($e) { + if (specialKeyCodeMap[$e.which || $e.keyCode]) { + return; + } + _.defer(_.bind(that._onInput, that, $e)); + }); + } + this.query = this.$input.val(); + this.$overflowHelper = buildOverflowHelper(this.$input); + } + Input.normalizeQuery = function(str) { + return (str || "").replace(/^\s*/g, "").replace(/\s{2,}/g, " "); + }; + _.mixin(Input.prototype, EventEmitter, { + _onBlur: function onBlur() { + this.resetInputValue(); + this.trigger("blurred"); + }, + _onFocus: function onFocus() { + this.trigger("focused"); + }, + _onKeydown: function onKeydown($e) { + var keyName = specialKeyCodeMap[$e.which || $e.keyCode]; + this._managePreventDefault(keyName, $e); + if (keyName && this._shouldTrigger(keyName, $e)) { + this.trigger(keyName + "Keyed", $e); + } + }, + _onInput: function onInput() { + this._checkInputValue(); + }, + _managePreventDefault: function managePreventDefault(keyName, $e) { + var preventDefault, hintValue, inputValue; + switch (keyName) { + case "tab": + hintValue = this.getHint(); + inputValue = this.getInputValue(); + preventDefault = hintValue && hintValue !== inputValue && !withModifier($e); + break; + + case "up": + case "down": + preventDefault = !withModifier($e); + break; + + default: + preventDefault = false; + } + preventDefault && $e.preventDefault(); + }, + _shouldTrigger: function shouldTrigger(keyName, $e) { + var trigger; + switch (keyName) { + case "tab": + trigger = !withModifier($e); + break; + + default: + trigger = true; + } + return trigger; + }, + _checkInputValue: function checkInputValue() { + var inputValue, areEquivalent, hasDifferentWhitespace; + inputValue = this.getInputValue(); + areEquivalent = areQueriesEquivalent(inputValue, this.query); + hasDifferentWhitespace = areEquivalent ? this.query.length !== inputValue.length : false; + if (!areEquivalent) { + this.trigger("queryChanged", this.query = inputValue); + } else if (hasDifferentWhitespace) { + this.trigger("whitespaceChanged", this.query); + } + }, + focus: function focus() { + this.$input.focus(); + }, + blur: function blur() { + this.$input.blur(); + }, + getQuery: function getQuery() { + return this.query; + }, + setQuery: function setQuery(query) { + this.query = query; + }, + getInputValue: function getInputValue() { + return this.$input.val(); + }, + setInputValue: function setInputValue(value, silent) { + this.$input.val(value); + silent ? this.clearHint() : this._checkInputValue(); + }, + resetInputValue: function resetInputValue() { + this.setInputValue(this.query, true); + }, + getHint: function getHint() { + return this.$hint.val(); + }, + setHint: function setHint(value) { + this.$hint.val(value); + }, + clearHint: function clearHint() { + this.setHint(""); + }, + clearHintIfInvalid: function clearHintIfInvalid() { + var val, hint, valIsPrefixOfHint, isValid; + val = this.getInputValue(); + hint = this.getHint(); + valIsPrefixOfHint = val !== hint && hint.indexOf(val) === 0; + isValid = val !== "" && valIsPrefixOfHint && !this.hasOverflow(); + !isValid && this.clearHint(); + }, + getLanguageDirection: function getLanguageDirection() { + return (this.$input.css("direction") || "ltr").toLowerCase(); + }, + hasOverflow: function hasOverflow() { + var constraint = this.$input.width() - 2; + this.$overflowHelper.text(this.getInputValue()); + return this.$overflowHelper.width() >= constraint; + }, + isCursorAtEnd: function() { + var valueLength, selectionStart, range; + valueLength = this.$input.val().length; + selectionStart = this.$input[0].selectionStart; + if (_.isNumber(selectionStart)) { + return selectionStart === valueLength; + } else if (document.selection) { + range = document.selection.createRange(); + range.moveStart("character", -valueLength); + return valueLength === range.text.length; + } + return true; + }, + destroy: function destroy() { + this.$hint.off(".tt"); + this.$input.off(".tt"); + this.$hint = this.$input = this.$overflowHelper = null; + } + }); + return Input; + function buildOverflowHelper($input) { + return $('').css({ + position: "absolute", + visibility: "hidden", + whiteSpace: "pre", + fontFamily: $input.css("font-family"), + fontSize: $input.css("font-size"), + fontStyle: $input.css("font-style"), + fontVariant: $input.css("font-variant"), + fontWeight: $input.css("font-weight"), + wordSpacing: $input.css("word-spacing"), + letterSpacing: $input.css("letter-spacing"), + textIndent: $input.css("text-indent"), + textRendering: $input.css("text-rendering"), + textTransform: $input.css("text-transform") + }).insertAfter($input); + } + function areQueriesEquivalent(a, b) { + return Input.normalizeQuery(a) === Input.normalizeQuery(b); + } + function withModifier($e) { + return $e.altKey || $e.ctrlKey || $e.metaKey || $e.shiftKey; + } + }(); + var Dataset = function() { + var datasetKey = "ttDataset", valueKey = "ttValue", datumKey = "ttDatum"; + function Dataset(o) { + o = o || {}; + o.templates = o.templates || {}; + if (!o.source) { + $.error("missing source"); + } + if (o.name && !isValidName(o.name)) { + $.error("invalid dataset name: " + o.name); + } + this.query = null; + this.highlight = !!o.highlight; + this.name = o.name || _.getUniqueId(); + this.source = o.source; + this.displayFn = getDisplayFn(o.display || o.displayKey); + this.templates = getTemplates(o.templates, this.displayFn); + this.$el = $(html.dataset.replace("%CLASS%", this.name)); + } + Dataset.extractDatasetName = function extractDatasetName(el) { + return $(el).data(datasetKey); + }; + Dataset.extractValue = function extractDatum(el) { + return $(el).data(valueKey); + }; + Dataset.extractDatum = function extractDatum(el) { + return $(el).data(datumKey); + }; + _.mixin(Dataset.prototype, EventEmitter, { + _render: function render(query, suggestions) { + if (!this.$el) { + return; + } + var that = this, hasSuggestions; + this.$el.empty(); + hasSuggestions = suggestions && suggestions.length; + if (!hasSuggestions && this.templates.empty) { + this.$el.html(getEmptyHtml()).prepend(that.templates.header ? getHeaderHtml() : null).append(that.templates.footer ? getFooterHtml() : null); + } else if (hasSuggestions) { + this.$el.html(getSuggestionsHtml()).prepend(that.templates.header ? getHeaderHtml() : null).append(that.templates.footer ? getFooterHtml() : null); + } + this.trigger("rendered"); + function getEmptyHtml() { + return that.templates.empty({ + query: query, + isEmpty: true + }); + } + function getSuggestionsHtml() { + var $suggestions, nodes; + $suggestions = $(html.suggestions).css(css.suggestions); + nodes = _.map(suggestions, getSuggestionNode); + $suggestions.append.apply($suggestions, nodes); + that.highlight && highlight({ + node: $suggestions[0], + pattern: query + }); + return $suggestions; + function getSuggestionNode(suggestion) { + var $el; + $el = $(html.suggestion).append(that.templates.suggestion(suggestion)).data(datasetKey, that.name).data(valueKey, that.displayFn(suggestion)).data(datumKey, suggestion); + $el.children().each(function() { + $(this).css(css.suggestionChild); + }); + return $el; + } + } + function getHeaderHtml() { + return that.templates.header({ + query: query, + isEmpty: !hasSuggestions + }); + } + function getFooterHtml() { + return that.templates.footer({ + query: query, + isEmpty: !hasSuggestions + }); + } + }, + getRoot: function getRoot() { + return this.$el; + }, + update: function update(query) { + var that = this; + this.query = query; + this.canceled = false; + this.source(query, render); + function render(suggestions) { + if (!that.canceled && query === that.query) { + that._render(query, suggestions); + } + } + }, + cancel: function cancel() { + this.canceled = true; + }, + clear: function clear() { + this.cancel(); + this.$el.empty(); + this.trigger("rendered"); + }, + isEmpty: function isEmpty() { + return this.$el.is(":empty"); + }, + destroy: function destroy() { + this.$el = null; + } + }); + return Dataset; + function getDisplayFn(display) { + display = display || "value"; + return _.isFunction(display) ? display : displayFn; + function displayFn(obj) { + return obj[display]; + } + } + function getTemplates(templates, displayFn) { + return { + empty: templates.empty && _.templatify(templates.empty), + header: templates.header && _.templatify(templates.header), + footer: templates.footer && _.templatify(templates.footer), + suggestion: templates.suggestion || suggestionTemplate + }; + function suggestionTemplate(context) { + return "

" + displayFn(context) + "

"; + } + } + function isValidName(str) { + return /^[_a-zA-Z0-9-]+$/.test(str); + } + }(); + var Dropdown = function() { + function Dropdown(o) { + var that = this, onSuggestionClick, onSuggestionMouseEnter, onSuggestionMouseLeave; + o = o || {}; + if (!o.menu) { + $.error("menu is required"); + } + this.isOpen = false; + this.isEmpty = true; + this.datasets = _.map(o.datasets, initializeDataset); + onSuggestionClick = _.bind(this._onSuggestionClick, this); + onSuggestionMouseEnter = _.bind(this._onSuggestionMouseEnter, this); + onSuggestionMouseLeave = _.bind(this._onSuggestionMouseLeave, this); + this.$menu = $(o.menu).on("click.tt", ".tt-suggestion", onSuggestionClick).on("mouseenter.tt", ".tt-suggestion", onSuggestionMouseEnter).on("mouseleave.tt", ".tt-suggestion", onSuggestionMouseLeave); + _.each(this.datasets, function(dataset) { + that.$menu.append(dataset.getRoot()); + dataset.onSync("rendered", that._onRendered, that); + }); + } + _.mixin(Dropdown.prototype, EventEmitter, { + _onSuggestionClick: function onSuggestionClick($e) { + this.trigger("suggestionClicked", $($e.currentTarget)); + }, + _onSuggestionMouseEnter: function onSuggestionMouseEnter($e) { + this._removeCursor(); + this._setCursor($($e.currentTarget), true); + }, + _onSuggestionMouseLeave: function onSuggestionMouseLeave() { + this._removeCursor(); + }, + _onRendered: function onRendered() { + this.isEmpty = _.every(this.datasets, isDatasetEmpty); + this.isEmpty ? this._hide() : this.isOpen && this._show(); + this.trigger("datasetRendered"); + function isDatasetEmpty(dataset) { + return dataset.isEmpty(); + } + }, + _hide: function() { + this.$menu.hide(); + }, + _show: function() { + this.$menu.css("display", "block"); + }, + _getSuggestions: function getSuggestions() { + return this.$menu.find(".tt-suggestion"); + }, + _getCursor: function getCursor() { + return this.$menu.find(".tt-cursor").first(); + }, + _setCursor: function setCursor($el, silent) { + $el.first().addClass("tt-cursor"); + !silent && this.trigger("cursorMoved"); + }, + _removeCursor: function removeCursor() { + this._getCursor().removeClass("tt-cursor"); + }, + _moveCursor: function moveCursor(increment) { + var $suggestions, $oldCursor, newCursorIndex, $newCursor; + if (!this.isOpen) { + return; + } + $oldCursor = this._getCursor(); + $suggestions = this._getSuggestions(); + this._removeCursor(); + newCursorIndex = $suggestions.index($oldCursor) + increment; + newCursorIndex = (newCursorIndex + 1) % ($suggestions.length + 1) - 1; + if (newCursorIndex === -1) { + this.trigger("cursorRemoved"); + return; + } else if (newCursorIndex < -1) { + newCursorIndex = $suggestions.length - 1; + } + this._setCursor($newCursor = $suggestions.eq(newCursorIndex)); + this._ensureVisible($newCursor); + }, + _ensureVisible: function ensureVisible($el) { + var elTop, elBottom, menuScrollTop, menuHeight; + elTop = $el.position().top; + elBottom = elTop + $el.outerHeight(true); + menuScrollTop = this.$menu.scrollTop(); + menuHeight = this.$menu.height() + parseInt(this.$menu.css("paddingTop"), 10) + parseInt(this.$menu.css("paddingBottom"), 10); + if (elTop < 0) { + this.$menu.scrollTop(menuScrollTop + elTop); + } else if (menuHeight < elBottom) { + this.$menu.scrollTop(menuScrollTop + (elBottom - menuHeight)); + } + }, + close: function close() { + if (this.isOpen) { + this.isOpen = false; + this._removeCursor(); + this._hide(); + this.trigger("closed"); + } + }, + open: function open() { + if (!this.isOpen) { + this.isOpen = true; + !this.isEmpty && this._show(); + this.trigger("opened"); + } + }, + setLanguageDirection: function setLanguageDirection(dir) { + this.$menu.css(dir === "ltr" ? css.ltr : css.rtl); + }, + moveCursorUp: function moveCursorUp() { + this._moveCursor(-1); + }, + moveCursorDown: function moveCursorDown() { + this._moveCursor(+1); + }, + getDatumForSuggestion: function getDatumForSuggestion($el) { + var datum = null; + if ($el.length) { + datum = { + raw: Dataset.extractDatum($el), + value: Dataset.extractValue($el), + datasetName: Dataset.extractDatasetName($el) + }; + } + return datum; + }, + getDatumForCursor: function getDatumForCursor() { + return this.getDatumForSuggestion(this._getCursor().first()); + }, + getDatumForTopSuggestion: function getDatumForTopSuggestion() { + return this.getDatumForSuggestion(this._getSuggestions().first()); + }, + update: function update(query) { + _.each(this.datasets, updateDataset); + function updateDataset(dataset) { + dataset.update(query); + } + }, + empty: function empty() { + _.each(this.datasets, clearDataset); + this.isEmpty = true; + function clearDataset(dataset) { + dataset.clear(); + } + }, + isVisible: function isVisible() { + return this.isOpen && !this.isEmpty; + }, + destroy: function destroy() { + this.$menu.off(".tt"); + this.$menu = null; + _.each(this.datasets, destroyDataset); + function destroyDataset(dataset) { + dataset.destroy(); + } + } + }); + return Dropdown; + function initializeDataset(oDataset) { + return new Dataset(oDataset); + } + }(); + var Typeahead = function() { + var attrsKey = "ttAttrs"; + function Typeahead(o) { + var $menu, $input, $hint; + o = o || {}; + if (!o.input) { + $.error("missing input"); + } + this.isActivated = false; + this.autoselect = !!o.autoselect; + this.minLength = _.isNumber(o.minLength) ? o.minLength : 1; + this.$node = buildDomStructure(o.input, o.withHint); + $menu = this.$node.find(".tt-dropdown-menu"); + $input = this.$node.find(".tt-input"); + $hint = this.$node.find(".tt-hint"); + $input.on("blur.tt", function($e) { + var active, isActive, hasActive; + active = document.activeElement; + isActive = $menu.is(active); + hasActive = $menu.has(active).length > 0; + if (_.isMsie() && (isActive || hasActive)) { + $e.preventDefault(); + $e.stopImmediatePropagation(); + _.defer(function() { + $input.focus(); + }); + } + }); + $menu.on("mousedown.tt", function($e) { + $e.preventDefault(); + }); + this.eventBus = o.eventBus || new EventBus({ + el: $input + }); + this.dropdown = new Dropdown({ + menu: $menu, + datasets: o.datasets + }).onSync("suggestionClicked", this._onSuggestionClicked, this).onSync("cursorMoved", this._onCursorMoved, this).onSync("cursorRemoved", this._onCursorRemoved, this).onSync("opened", this._onOpened, this).onSync("closed", this._onClosed, this).onAsync("datasetRendered", this._onDatasetRendered, this); + this.input = new Input({ + input: $input, + hint: $hint + }).onSync("focused", this._onFocused, this).onSync("blurred", this._onBlurred, this).onSync("enterKeyed", this._onEnterKeyed, this).onSync("tabKeyed", this._onTabKeyed, this).onSync("escKeyed", this._onEscKeyed, this).onSync("upKeyed", this._onUpKeyed, this).onSync("downKeyed", this._onDownKeyed, this).onSync("leftKeyed", this._onLeftKeyed, this).onSync("rightKeyed", this._onRightKeyed, this).onSync("queryChanged", this._onQueryChanged, this).onSync("whitespaceChanged", this._onWhitespaceChanged, this); + this._setLanguageDirection(); + } + _.mixin(Typeahead.prototype, { + _onSuggestionClicked: function onSuggestionClicked(type, $el) { + var datum; + if (datum = this.dropdown.getDatumForSuggestion($el)) { + this._select(datum); + } + }, + _onCursorMoved: function onCursorMoved() { + var datum = this.dropdown.getDatumForCursor(); + this.input.setInputValue(datum.value, true); + this.eventBus.trigger("cursorchanged", datum.raw, datum.datasetName); + }, + _onCursorRemoved: function onCursorRemoved() { + this.input.resetInputValue(); + this._updateHint(); + }, + _onDatasetRendered: function onDatasetRendered() { + this._updateHint(); + }, + _onOpened: function onOpened() { + this._updateHint(); + this.eventBus.trigger("opened"); + }, + _onClosed: function onClosed() { + this.input.clearHint(); + this.eventBus.trigger("closed"); + }, + _onFocused: function onFocused() { + this.isActivated = true; + this.dropdown.open(); + }, + _onBlurred: function onBlurred() { + this.isActivated = false; + this.dropdown.empty(); + this.dropdown.close(); + }, + _onEnterKeyed: function onEnterKeyed(type, $e) { + var cursorDatum, topSuggestionDatum; + cursorDatum = this.dropdown.getDatumForCursor(); + topSuggestionDatum = this.dropdown.getDatumForTopSuggestion(); + if (cursorDatum) { + this._select(cursorDatum); + $e.preventDefault(); + } else if (this.autoselect && topSuggestionDatum) { + this._select(topSuggestionDatum); + $e.preventDefault(); + } + }, + _onTabKeyed: function onTabKeyed(type, $e) { + var datum; + if (datum = this.dropdown.getDatumForCursor()) { + this._select(datum); + $e.preventDefault(); + } else { + this._autocomplete(true); + } + }, + _onEscKeyed: function onEscKeyed() { + this.dropdown.close(); + this.input.resetInputValue(); + }, + _onUpKeyed: function onUpKeyed() { + var query = this.input.getQuery(); + this.dropdown.isEmpty && query.length >= this.minLength ? this.dropdown.update(query) : this.dropdown.moveCursorUp(); + this.dropdown.open(); + }, + _onDownKeyed: function onDownKeyed() { + var query = this.input.getQuery(); + this.dropdown.isEmpty && query.length >= this.minLength ? this.dropdown.update(query) : this.dropdown.moveCursorDown(); + this.dropdown.open(); + }, + _onLeftKeyed: function onLeftKeyed() { + this.dir === "rtl" && this._autocomplete(); + }, + _onRightKeyed: function onRightKeyed() { + this.dir === "ltr" && this._autocomplete(); + }, + _onQueryChanged: function onQueryChanged(e, query) { + this.input.clearHintIfInvalid(); + query.length >= this.minLength ? this.dropdown.update(query) : this.dropdown.empty(); + this.dropdown.open(); + this._setLanguageDirection(); + }, + _onWhitespaceChanged: function onWhitespaceChanged() { + this._updateHint(); + this.dropdown.open(); + }, + _setLanguageDirection: function setLanguageDirection() { + var dir; + if (this.dir !== (dir = this.input.getLanguageDirection())) { + this.dir = dir; + this.$node.css("direction", dir); + this.dropdown.setLanguageDirection(dir); + } + }, + _updateHint: function updateHint() { + var datum, val, query, escapedQuery, frontMatchRegEx, match; + datum = this.dropdown.getDatumForTopSuggestion(); + if (datum && this.dropdown.isVisible() && !this.input.hasOverflow()) { + val = this.input.getInputValue(); + query = Input.normalizeQuery(val); + escapedQuery = _.escapeRegExChars(query); + frontMatchRegEx = new RegExp("^(?:" + escapedQuery + ")(.+$)", "i"); + match = frontMatchRegEx.exec(datum.value); + match ? this.input.setHint(val + match[1]) : this.input.clearHint(); + } else { + this.input.clearHint(); + } + }, + _autocomplete: function autocomplete(laxCursor) { + var hint, query, isCursorAtEnd, datum; + hint = this.input.getHint(); + query = this.input.getQuery(); + isCursorAtEnd = laxCursor || this.input.isCursorAtEnd(); + if (hint && query !== hint && isCursorAtEnd) { + datum = this.dropdown.getDatumForTopSuggestion(); + datum && this.input.setInputValue(datum.value); + this.eventBus.trigger("autocompleted", datum.raw, datum.datasetName); + } + }, + _select: function select(datum) { + this.input.setQuery(datum.value); + this.input.setInputValue(datum.value, true); + this._setLanguageDirection(); + this.eventBus.trigger("selected", datum.raw, datum.datasetName); + this.dropdown.close(); + _.defer(_.bind(this.dropdown.empty, this.dropdown)); + }, + open: function open() { + this.dropdown.open(); + }, + close: function close() { + this.dropdown.close(); + }, + setVal: function setVal(val) { + if (this.isActivated) { + this.input.setInputValue(val); + } else { + this.input.setQuery(val); + this.input.setInputValue(val, true); + } + this._setLanguageDirection(); + }, + getVal: function getVal() { + return this.input.getQuery(); + }, + destroy: function destroy() { + this.input.destroy(); + this.dropdown.destroy(); + destroyDomStructure(this.$node); + this.$node = null; + } + }); + return Typeahead; + function buildDomStructure(input, withHint) { + var $input, $wrapper, $dropdown, $hint; + $input = $(input); + $wrapper = $(html.wrapper).css(css.wrapper); + $dropdown = $(html.dropdown).css(css.dropdown); + $hint = $input.clone().css(css.hint).css(getBackgroundStyles($input)); + $hint.val("").removeData().addClass("tt-hint").removeAttr("id name placeholder").prop("disabled", true).attr({ + autocomplete: "off", + spellcheck: "false" + }); + $input.data(attrsKey, { + dir: $input.attr("dir"), + autocomplete: $input.attr("autocomplete"), + spellcheck: $input.attr("spellcheck"), + style: $input.attr("style") + }); + $input.addClass("tt-input").attr({ + autocomplete: "off", + spellcheck: false + }).css(withHint ? css.input : css.inputWithNoHint); + try { + !$input.attr("dir") && $input.attr("dir", "auto"); + } catch (e) {} + return $input.wrap($wrapper).parent().prepend(withHint ? $hint : null).append($dropdown); + } + function getBackgroundStyles($el) { + return { + backgroundAttachment: $el.css("background-attachment"), + backgroundClip: $el.css("background-clip"), + backgroundColor: $el.css("background-color"), + backgroundImage: $el.css("background-image"), + backgroundOrigin: $el.css("background-origin"), + backgroundPosition: $el.css("background-position"), + backgroundRepeat: $el.css("background-repeat"), + backgroundSize: $el.css("background-size") + }; + } + function destroyDomStructure($node) { + var $input = $node.find(".tt-input"); + _.each($input.data(attrsKey), function(val, key) { + _.isUndefined(val) ? $input.removeAttr(key) : $input.attr(key, val); + }); + $input.detach().removeData(attrsKey).removeClass("tt-input").insertAfter($node); + $node.remove(); + } + }(); + (function() { + var old, typeaheadKey, methods; + old = $.fn.typeahead; + typeaheadKey = "ttTypeahead"; + methods = { + initialize: function initialize(o, datasets) { + datasets = _.isArray(datasets) ? datasets : [].slice.call(arguments, 1); + o = o || {}; + return this.each(attach); + function attach() { + var $input = $(this), eventBus, typeahead; + _.each(datasets, function(d) { + d.highlight = !!o.highlight; + }); + typeahead = new Typeahead({ + input: $input, + eventBus: eventBus = new EventBus({ + el: $input + }), + withHint: _.isUndefined(o.hint) ? true : !!o.hint, + minLength: o.minLength, + autoselect: o.autoselect, + datasets: datasets + }); + $input.data(typeaheadKey, typeahead); + } + }, + open: function open() { + return this.each(openTypeahead); + function openTypeahead() { + var $input = $(this), typeahead; + if (typeahead = $input.data(typeaheadKey)) { + typeahead.open(); + } + } + }, + close: function close() { + return this.each(closeTypeahead); + function closeTypeahead() { + var $input = $(this), typeahead; + if (typeahead = $input.data(typeaheadKey)) { + typeahead.close(); + } + } + }, + val: function val(newVal) { + return !arguments.length ? getVal(this.first()) : this.each(setVal); + function setVal() { + var $input = $(this), typeahead; + if (typeahead = $input.data(typeaheadKey)) { + typeahead.setVal(newVal); + } + } + function getVal($input) { + var typeahead, query; + if (typeahead = $input.data(typeaheadKey)) { + query = typeahead.getVal(); + } + return query; + } + }, + destroy: function destroy() { + return this.each(unattach); + function unattach() { + var $input = $(this), typeahead; + if (typeahead = $input.data(typeaheadKey)) { + typeahead.destroy(); + $input.removeData(typeaheadKey); + } + } + } + }; + $.fn.typeahead = function(method) { + if (methods[method]) { + return methods[method].apply(this, [].slice.call(arguments, 1)); + } else { + return methods.initialize.apply(this, arguments); + } + }; + $.fn.typeahead.noConflict = function noConflict() { + $.fn.typeahead = old; + return this; + }; + })(); })(window.jQuery); \ No newline at end of file diff --git a/src/UI/Movies/Titles/LanguageCell.js b/src/UI/Movies/Titles/LanguageCell.js index 0014a9e45c..2071d3c3d2 100644 --- a/src/UI/Movies/Titles/LanguageCell.js +++ b/src/UI/Movies/Titles/LanguageCell.js @@ -1,22 +1,22 @@ -var NzbDroneCell = require('../../Cells/NzbDroneCell'); - -module.exports = NzbDroneCell.extend({ - className : 'language-cell', - - render : function() { - this.$el.empty(); - - var language = this.model.get("language"); - - this.$el.html(this.toTitleCase(language)); - - return this; - }, - - toTitleCase : function(str) - { - return str.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();}); - } - - -}); +var NzbDroneCell = require('../../Cells/NzbDroneCell'); + +module.exports = NzbDroneCell.extend({ + className : 'language-cell', + + render : function() { + this.$el.empty(); + + var language = this.model.get("language"); + + this.$el.html(this.toTitleCase(language)); + + return this; + }, + + toTitleCase : function(str) + { + return str.replace(/\w\S*/g, function(txt){return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();}); + } + + +}); diff --git a/src/UI/Movies/Titles/TitleCell.js b/src/UI/Movies/Titles/TitleCell.js index 9450da8643..f3e5f6f80a 100644 --- a/src/UI/Movies/Titles/TitleCell.js +++ b/src/UI/Movies/Titles/TitleCell.js @@ -1,6 +1,6 @@ -var TemplatedCell = require('../../Cells/TemplatedCell'); - -module.exports = TemplatedCell.extend({ - className : 'movie-title-cell', - template : 'Movies/Titles/TitleTemplate' +var TemplatedCell = require('../../Cells/TemplatedCell'); + +module.exports = TemplatedCell.extend({ + className : 'movie-title-cell', + template : 'Movies/Titles/TitleTemplate' }); \ No newline at end of file diff --git a/src/UI/Movies/Titles/TitleModel.js b/src/UI/Movies/Titles/TitleModel.js index d51ee555f6..3986a5948a 100644 --- a/src/UI/Movies/Titles/TitleModel.js +++ b/src/UI/Movies/Titles/TitleModel.js @@ -1,3 +1,3 @@ -var Backbone = require('backbone'); - +var Backbone = require('backbone'); + module.exports = Backbone.Model.extend({}); \ No newline at end of file diff --git a/src/UI/Movies/Titles/TitlesCollection.js b/src/UI/Movies/Titles/TitlesCollection.js index 4b7914955d..1c926373e8 100644 --- a/src/UI/Movies/Titles/TitlesCollection.js +++ b/src/UI/Movies/Titles/TitlesCollection.js @@ -1,30 +1,30 @@ -var PagableCollection = require('backbone.pageable'); -var TitleModel = require('./TitleModel'); -var AsSortedCollection = require('../../Mixins/AsSortedCollection'); - -var Collection = PagableCollection.extend({ - url : window.NzbDrone.ApiRoot + "/aka", - model : TitleModel, - - state : { - pageSize : 2000, - sortKey : 'title', - order : -1 - }, - - mode : 'client', - - sortMappings : { - "source" : { - sortKey : "sourceType" - }, - "language" : { - sortKey : "language" - } - }, - -}); - -Collection = AsSortedCollection.call(Collection); - -module.exports = Collection; +var PagableCollection = require('backbone.pageable'); +var TitleModel = require('./TitleModel'); +var AsSortedCollection = require('../../Mixins/AsSortedCollection'); + +var Collection = PagableCollection.extend({ + url : window.NzbDrone.ApiRoot + "/aka", + model : TitleModel, + + state : { + pageSize : 2000, + sortKey : 'title', + order : -1 + }, + + mode : 'client', + + sortMappings : { + "source" : { + sortKey : "sourceType" + }, + "language" : { + sortKey : "language" + } + }, + +}); + +Collection = AsSortedCollection.call(Collection); + +module.exports = Collection; diff --git a/src/UI/Release/AlternativeTitleModel.js b/src/UI/Release/AlternativeTitleModel.js index 14b7db8648..505734a44d 100644 --- a/src/UI/Release/AlternativeTitleModel.js +++ b/src/UI/Release/AlternativeTitleModel.js @@ -1,6 +1,6 @@ -var Backbone = require('backbone'); -var _ = require('underscore'); - -module.exports = Backbone.Model.extend({ - urlRoot : window.NzbDrone.ApiRoot + '/alttitle', -}); +var Backbone = require('backbone'); +var _ = require('underscore'); + +module.exports = Backbone.Model.extend({ + urlRoot : window.NzbDrone.ApiRoot + '/alttitle', +}); diff --git a/src/UI/Release/AlternativeYearModel.js b/src/UI/Release/AlternativeYearModel.js index 1477167f51..5f6b703d75 100644 --- a/src/UI/Release/AlternativeYearModel.js +++ b/src/UI/Release/AlternativeYearModel.js @@ -1,6 +1,6 @@ -var Backbone = require('backbone'); -var _ = require('underscore'); - -module.exports = Backbone.Model.extend({ - urlRoot : window.NzbDrone.ApiRoot + '/altyear', -}); +var Backbone = require('backbone'); +var _ = require('underscore'); + +module.exports = Backbone.Model.extend({ + urlRoot : window.NzbDrone.ApiRoot + '/altyear', +}); diff --git a/src/UI/Release/ForceDownloadView.js b/src/UI/Release/ForceDownloadView.js index e6c1f70632..387c9cad84 100644 --- a/src/UI/Release/ForceDownloadView.js +++ b/src/UI/Release/ForceDownloadView.js @@ -1,81 +1,81 @@ -var _ = require('underscore'); -var $ = require('jquery'); -var vent = require('vent'); -var AppLayout = require('../AppLayout'); -var Marionette = require('marionette'); -var Config = require('../Config'); -var LanguageCollection = require('../Settings/Profile/Language/LanguageCollection'); -var AltTitleModel = require("./AlternativeTitleModel"); -var AltYearModel = require("./AlternativeYearModel"); -var Messenger = require('../Shared/Messenger'); -require('../Form/FormBuilder'); -require('bootstrap'); - -module.exports = Marionette.ItemView.extend({ - template : 'Release/ForceDownloadViewTemplate', - - events : { - 'click .x-download' : '_forceDownload', - }, - - ui : { - titleMapping : "#title-mapping", - yearMapping : "#year-mapping", - language : "#language-selection", - indicator : ".x-indicator", - }, - - initialize : function(options) { - this.release = options.release; - this.templateHelpers = {}; - - this._configureTemplateHelpers(); - }, - - onShow : function() { - if (this.release.get("mappingResult") === "wrongYear") { - this.ui.titleMapping.hide(); - } else { - this.ui.yearMapping.hide(); - } - }, - - _configureTemplateHelpers : function() { - this.templateHelpers.release = this.release.toJSON(); - this.templateHelpers.languages = LanguageCollection.toJSON(); - }, - - _forceDownload : function() { - this.ui.indicator.show(); - var self = this; - - if (this.release.get("mappingResult") === "wrongYear") { - var altYear = new AltYearModel({ - movieId : this.release.get("suspectedMovieId"), - year : this.release.get("year") - }); - this.savePromise = altYear.save(); - } else { - var altTitle = new AltTitleModel({ - movieId : this.release.get("suspectedMovieId"), - title : this.release.get("movieTitle"), - language : this.ui.language.val(), - }); - - this.savePromise = altTitle.save(); - } - - this.savePromise.always(function(){ - self.ui.indicator.hide(); - }); - - this.savePromise.success(function(){ - self.release.save(null, { - success : function() { - self.release.set('queued', true); - vent.trigger(vent.Commands.CloseModalCommand); - } - }); - }); - }, +var _ = require('underscore'); +var $ = require('jquery'); +var vent = require('vent'); +var AppLayout = require('../AppLayout'); +var Marionette = require('marionette'); +var Config = require('../Config'); +var LanguageCollection = require('../Settings/Profile/Language/LanguageCollection'); +var AltTitleModel = require("./AlternativeTitleModel"); +var AltYearModel = require("./AlternativeYearModel"); +var Messenger = require('../Shared/Messenger'); +require('../Form/FormBuilder'); +require('bootstrap'); + +module.exports = Marionette.ItemView.extend({ + template : 'Release/ForceDownloadViewTemplate', + + events : { + 'click .x-download' : '_forceDownload', + }, + + ui : { + titleMapping : "#title-mapping", + yearMapping : "#year-mapping", + language : "#language-selection", + indicator : ".x-indicator", + }, + + initialize : function(options) { + this.release = options.release; + this.templateHelpers = {}; + + this._configureTemplateHelpers(); + }, + + onShow : function() { + if (this.release.get("mappingResult") === "wrongYear") { + this.ui.titleMapping.hide(); + } else { + this.ui.yearMapping.hide(); + } + }, + + _configureTemplateHelpers : function() { + this.templateHelpers.release = this.release.toJSON(); + this.templateHelpers.languages = LanguageCollection.toJSON(); + }, + + _forceDownload : function() { + this.ui.indicator.show(); + var self = this; + + if (this.release.get("mappingResult") === "wrongYear") { + var altYear = new AltYearModel({ + movieId : this.release.get("suspectedMovieId"), + year : this.release.get("year") + }); + this.savePromise = altYear.save(); + } else { + var altTitle = new AltTitleModel({ + movieId : this.release.get("suspectedMovieId"), + title : this.release.get("movieTitle"), + language : this.ui.language.val(), + }); + + this.savePromise = altTitle.save(); + } + + this.savePromise.always(function(){ + self.ui.indicator.hide(); + }); + + this.savePromise.success(function(){ + self.release.save(null, { + success : function() { + self.release.set('queued', true); + vent.trigger(vent.Commands.CloseModalCommand); + } + }); + }); + }, }); \ No newline at end of file diff --git a/src/UI/Release/ForceDownloadViewTemplate.hbs b/src/UI/Release/ForceDownloadViewTemplate.hbs index 81063e31ad..5e35536a4e 100644 --- a/src/UI/Release/ForceDownloadViewTemplate.hbs +++ b/src/UI/Release/ForceDownloadViewTemplate.hbs @@ -1,44 +1,44 @@ -