1
0
mirror of https://github.com/Sonarr/Sonarr.git synced 2026-03-16 16:04:33 -04:00

Compare commits

..

1 Commits

Author SHA1 Message Date
Taloth Saldono
a19776553e WIP: Statistics API endpoint. 2016-01-31 15:33:13 +01:00
439 changed files with 9754 additions and 8159 deletions

1
.gitignore vendored
View File

@@ -128,4 +128,3 @@ output/*
._*
_start
_temp_*/**/*

View File

@@ -1,6 +1,6 @@
# How to Contribute #
We're always looking for people to help make Sonarr even better, there are a number of ways to contribute.
We're always looking for people to help make Sonarr even better, there are a number of ways to contribute. To get started, <a href="http://www.clahub.com/agreements/NzbDrone/NzbDrone">sign the Contributor License Agreement</a>.
## Documentation ##
Setup guides, FAQ, the more information we have on the wiki the better.
@@ -18,9 +18,7 @@ Setup guides, FAQ, the more information we have on the wiki the better.
1. Fork Sonarr
2. Clone (develop branch) *you may need pull in submodules separately if you client doesn't clone them automatically (CurlSharp)*
3. Run `npm install`
4. Run `npm start` - Used to compile the UI components and copy them.
Leave this window open.
If you have gulp globally installed you can use `gulp watch` instead
4. Run `gulp watch` - Used to compile the UI components and copy them (leave this window open)
5. Compile in Visual Studio
### Contributing Code ###

View File

@@ -1,5 +1,5 @@
#! /bin/bash
msBuild='/c/Program Files (x86)/MSBuild/14.0/Bin'
msBuild='/c/Windows/Microsoft.NET/Framework64/v4.0.30319/'
outputFolder='./_output'
outputFolderMono='./_output_mono'
outputFolderOsx='./_output_osx'
@@ -102,12 +102,12 @@ Build()
RunGulp()
{
echo "##teamcity[progressStart 'npm install']"
npm-cache install npm || CheckExitCode npm install
CheckExitCode npm install
echo "##teamcity[progressFinish 'npm install']"
echo "##teamcity[progressStart 'Running gulp']"
CheckExitCode npm run build
echo "##teamcity[progressFinish 'Running gulp']"
echo "##teamcity[progressStart 'Running Gulp']"
CheckExitCode gulp build
echo "##teamcity[progressFinish 'Running Gulp']"
}
CreateMdbs()
@@ -208,9 +208,9 @@ PackageTests()
find $sourceFolder -path $testSearchPattern -exec cp -r -u -T "{}" $testPackageFolder \;
if [ $runtime = "dotnet" ] ; then
$nuget install NUnit.ConsoleRunner -Version 3.2.0 -Output $testPackageFolder
$nuget install NUnit.Runners -Version 2.6.1 -Output $testPackageFolder
else
mono $nuget install NUnit.ConsoleRunner -Version 3.2.0 -Output $testPackageFolder
mono $nuget install NUnit.Runners -Version 2.6.1 -Output $testPackageFolder
fi
cp $outputFolder/*.dll $testPackageFolder

8
integration_mono.sh Normal file
View File

@@ -0,0 +1,8 @@
EXCLUDE="-exclude:Windows -include:IntegrationTest"
TESTDIR="."
NUNIT="$TESTDIR/NUnit.Runners.2.6.1/tools/nunit-console-x86.exe"
mono --debug --runtime=v4.0 $NUNIT $EXCLUDE -xml:NzbDrone.Api.Result.xml $TESTDIR/NzbDrone.Api.Test.dll
mono --debug --runtime=v4.0 $NUNIT $EXCLUDE -xml:NzbDrone.Core.Result.xml $TESTDIR/NzbDrone.Core.Test.dll
mono --debug --runtime=v4.0 $NUNIT $EXCLUDE -xml:NzbDrone.Integration.Result.xml $TESTDIR/NzbDrone.Integration.Test.dll
mono --debug --runtime=v4.0 $NUNIT $EXCLUDE -xml:NzbDrone.Common.Result.xml $TESTDIR/NzbDrone.Common.Test.dll

View File

@@ -1,31 +1,17 @@
#!/bin/sh
#get the bundle's MacOS directory full path
DIR=$(cd "$(dirname "$0")"; pwd)
#change these values to match your app
EXE_PATH="$DIR/NzbDrone.exe"
APPNAME="Sonarr"
#set up environment
if [[ -x '/opt/local/bin/mono' ]]; then
export PATH="/opt/local/bin:$PATH"
fi
export DYLD_FALLBACK_LIBRARY_PATH="$DIR"
if [ -e /Library/Frameworks/Mono.framework ]; then
MONO_FRAMEWORK_PATH=/Library/Frameworks/Mono.framework/Versions/Current
export PATH="$MONO_FRAMEWORK_PATH/bin:$PATH"
export DYLD_FALLBACK_LIBRARY_PATH="$DYLD_FALLBACK_LIBRARY_PATH:$MONO_FRAMEWORK_PATH/lib"
fi
if [[ -f '/opt/local/lib/libsqlite3.0.dylib' ]]; then
export DYLD_FALLBACK_LIBRARY_PATH="/opt/local/lib:$DYLD_FALLBACK_LIBRARY_PATH"
fi
export DYLD_FALLBACK_LIBRARY_PATH="$DYLD_FALLBACK_LIBRARY_PATH:$HOME/lib:/usr/local/lib:/lib:/usr/lib"
MONO_FRAMEWORK_PATH=/Library/Frameworks/Mono.framework/Versions/Current
export DYLD_FALLBACK_LIBRARY_PATH="$DIR:$MONO_FRAMEWORK_PATH/lib:/lib:/usr/lib"
export PATH="$MONO_FRAMEWORK_PATH/bin:$PATH"
#mono version check
REQUIRED_MAJOR=3
REQUIRED_MINOR=10
@@ -35,9 +21,6 @@ VERSION_MSG="$APPNAME requires Mono Runtime Environment(MRE) $REQUIRED_MAJOR.$RE
DOWNLOAD_URL="http://www.mono-project.com/download/#download-mac"
MONO_VERSION="$(mono --version | grep 'Mono JIT compiler version ' | cut -f5 -d\ )"
# if [[ -o DEBUG ]]; then osascript -e "display dialog \"MONO_VERSION: $MONO_VERSION\""; fi
MONO_VERSION_MAJOR="$(echo $MONO_VERSION | cut -f1 -d.)"
MONO_VERSION_MINOR="$(echo $MONO_VERSION | cut -f2 -d.)"
if [ -z "$MONO_VERSION" ] \

View File

@@ -4,15 +4,14 @@
"description": "Sonarr",
"main": "main.js",
"scripts": {
"build": "gulp build",
"start": "gulp watch"
"preinstall": ""
},
"repository": {
"type": "git",
"url": "git://github.com/Sonarr/Sonarr.git"
},
"author": "",
"license": "GPL-3.0",
"license": "BSD",
"gitHead": "9ff7aa1bf7fe38c4c5bdb92f56c8ad556916ed67",
"readmeFilename": "readme.md",
"dependencies": {

View File

@@ -24,6 +24,7 @@ Sonarr is a PVR for Usenet and BitTorrent users. It can monitor multiple RSS fee
- Visual Studio 2015 [Free Community Edition](https://www.visualstudio.com/en-us/products/visual-studio-community-vs.aspx)
- [Git](http://git-scm.com/downloads)
- [NodeJS](http://nodejs.org/download/)
- [Gulp](http://gulpjs.com)
### Setup ###
@@ -31,7 +32,8 @@ Sonarr is a PVR for Usenet and BitTorrent users. It can monitor multiple RSS fee
- Clone the repository into your development machine. [*info*](https://help.github.com/articles/working-with-repositories)
- Grab the submodules `git submodule init && git submodule update`
- install the required Node Packages `npm install`
- start gulp to monitor your dev environment for any changes that need post processing using `npm start` command.
- install gulp `npm install gulp -g`
- start gulp to monitor your dev environment for any changes that need post processing using `gulp watch` command.
*Please note gulp must be running at all times while you are working with Sonarr client source files.*

View File

@@ -296,7 +296,7 @@ namespace LogentriesCore
WriteDebugMessages("HostName parameter is not defined - trying to get it from System.Environment.MachineName");
m_HostName = "HostName=" + System.Environment.MachineName + " ";
}
catch (InvalidOperationException)
catch (InvalidOperationException ex)
{
// Cannot get host name automatically, so assume that HostName is not used
// and log message is sent without it.

View File

@@ -51,9 +51,8 @@
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.3.0-rc1\lib\net40\NLog.dll</HintPath>
<Private>True</Private>
<Reference Include="NLog">
<HintPath>..\packages\NLog.2.1.0\lib\net40\NLog.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
@@ -80,7 +79,6 @@
</ItemGroup>
<ItemGroup>
<None Include="fastJSON\license.txt" />
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="NLog" version="4.3.0-rc1" targetFramework="net40" />
</packages>
<package id="NLog" version="2.1.0" targetFramework="net40" />
</packages>

View File

@@ -774,8 +774,7 @@ namespace MonoTorrent
break;
case ("nodes"):
if (keypair.Value.ToString().Length != 0)
this.nodes = (BEncodedList)keypair.Value;
this.nodes = (BEncodedList)keypair.Value;
break;
case ("comment.utf-8"):

View File

@@ -46,9 +46,9 @@
<HintPath>..\packages\FluentAssertions.4.2.1\lib\net40\FluentAssertions.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="nunit.framework, Version=3.2.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>..\packages\NUnit.3.2.0\lib\net40\nunit.framework.dll</HintPath>
<Private>True</Private>
<Reference Include="nunit.framework, Version=2.6.3.13283, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\NUnit.2.6.3\lib\nunit.framework.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
@@ -111,4 +111,4 @@
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>

View File

@@ -3,6 +3,6 @@
<package id="FluentAssertions" version="4.2.1" targetFramework="net40" />
<package id="Moq" version="4.0.10827" />
<package id="NBuilder" version="3.0.1.1" targetFramework="net40" />
<package id="NUnit" version="3.2.0" targetFramework="net40" />
<package id="NUnit" version="2.6.3" targetFramework="net40" />
<package id="ValueInjecter" version="2.3.3" targetFramework="net40" />
</packages>

View File

@@ -26,7 +26,7 @@ namespace NzbDrone.Api.ErrorManagement
if (apiException != null)
{
_logger.Warn(apiException, "API Error");
_logger.WarnException("API Error", apiException);
return apiException.ToErrorResponse();
}
@@ -65,10 +65,10 @@ namespace NzbDrone.Api.ErrorManagement
var sqlErrorMessage = string.Format("[{0} {1}]", context.Request.Method, context.Request.Path);
_logger.Error(sqLiteException, sqlErrorMessage);
_logger.ErrorException(sqlErrorMessage, sqLiteException);
}
_logger.Fatal(exception, "Request Failed");
_logger.FatalException("Request Failed", exception);
return new ErrorModel
{

View File

@@ -60,7 +60,7 @@ namespace NzbDrone.Api.Extensions.Pipelines
catch (Exception ex)
{
_logger.Error(ex, "Unable to gzip response");
_logger.ErrorException("Unable to gzip response", ex);
throw;
}
}

View File

@@ -1,67 +0,0 @@
using System;
using System.Threading;
using Nancy;
using Nancy.Bootstrapper;
using NLog;
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;
public void Register(IPipelines pipelines)
{
pipelines.BeforeRequest.AddItemToStartOfPipeline(LogStart);
pipelines.AfterRequest.AddItemToEndOfPipeline(LogEnd);
}
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 static string GetRequestPathAndQuery(Request request)
{
if (request.Url.Query.IsNotNullOrWhiteSpace())
{
return string.Concat(request.Url.Path, "?", request.Url.Query);
}
else
{
return request.Url.Path;
}
}
}
}

View File

@@ -1,6 +1,5 @@
using System;
using NzbDrone.Api.REST;
using NzbDrone.Common.Http;
using NzbDrone.Core.HealthCheck;
namespace NzbDrone.Api.Health
@@ -9,6 +8,6 @@ namespace NzbDrone.Api.Health
{
public HealthCheckResult Type { get; set; }
public string Message { get; set; }
public HttpUri WikiUrl { get; set; }
public Uri WikiUrl { get; set; }
}
}

View File

@@ -68,7 +68,7 @@ namespace NzbDrone.Api.Indexers
}
catch (ReleaseDownloadException ex)
{
_logger.Error(ex, ex.Message);
_logger.ErrorException(ex.Message, ex);
throw new NzbDroneClientException(HttpStatusCode.Conflict, "Getting release from indexer failed");
}
@@ -96,7 +96,7 @@ namespace NzbDrone.Api.Indexers
}
catch (Exception ex)
{
_logger.Error(ex, "Episode search failed: " + ex.Message);
_logger.ErrorException("Episode search failed: " + ex.Message, ex);
}
return new List<ReleaseResource>();

View File

@@ -9,7 +9,6 @@ using NzbDrone.Core.Parser.Model;
using NzbDrone.Api.Mapping;
using NzbDrone.Api.Extensions;
using NLog;
using NzbDrone.Core.Indexers;
namespace NzbDrone.Api.Indexers
{
@@ -31,7 +30,7 @@ namespace NzbDrone.Api.Indexers
PostValidator.RuleFor(s => s.Title).NotEmpty();
PostValidator.RuleFor(s => s.DownloadUrl).NotEmpty();
PostValidator.RuleFor(s => s.Protocol).NotEmpty();
PostValidator.RuleFor(s => s.DownloadProtocol).NotEmpty();
PostValidator.RuleFor(s => s.PublishDate).NotEmpty();
}
@@ -39,14 +38,11 @@ namespace NzbDrone.Api.Indexers
{
_logger.Info("Release pushed: {0} - {1}", release.Title, release.DownloadUrl);
var info = release.Protocol == DownloadProtocol.Usenet ?
release.InjectTo<ReleaseInfo>() :
release.InjectTo<TorrentInfo>();
var info = release.InjectTo<ReleaseInfo>();
info.Guid = "PUSH-" + info.DownloadUrl;
var decisions = _downloadDecisionMaker.GetRssDecision(new List<ReleaseInfo> { info });
_downloadDecisionProcessor.ProcessDecisions(decisions);
var processed = _downloadDecisionProcessor.ProcessDecisions(decisions);
return MapDecisions(decisions).First().AsResponse();
}

View File

@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;
using NzbDrone.Api.REST;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Qualities;
@@ -49,25 +48,8 @@ namespace NzbDrone.Api.Indexers
public int? Leechers { get; set; }
public DownloadProtocol Protocol { get; set; }
// TODO: Remove in v3
// Used to support the original Release Push implementation
// JsonIgnore so we don't serialize it, but can still parse it
[JsonIgnore]
public DownloadProtocol DownloadProtocol
{
get
{
return Protocol;
}
set
{
if (value > 0 && Protocol == 0)
{
Protocol = value;
}
}
}
//TODO: besides a test I don't think this is used...
public DownloadProtocol DownloadProtocol { get; set; }
public bool IsDaily { get; set; }
public bool IsAbsoluteNumbering { get; set; }

View File

@@ -40,8 +40,8 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="FluentValidation, Version=6.2.1.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\FluentValidation.6.2.1.0\lib\portable-net40+sl50+wp80+win8+wpa81\FluentValidation.dll</HintPath>
<Reference Include="FluentValidation, Version=6.0.2.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\FluentValidation.6.0.2.0\lib\portable-net40+sl50+wp80+win8+wpa81\FluentValidation.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Nancy, Version=0.23.2.0, Culture=neutral, processorArchitecture=MSIL">
@@ -59,10 +59,6 @@
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Newtonsoft.Json.6.0.6\lib\net40\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.3.0-rc1\lib\net40\NLog.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Data" />
@@ -70,6 +66,9 @@
<Reference Include="DDay.iCal">
<HintPath>..\packages\DDay.iCal.1.0.2.575\lib\DDay.iCal.dll</HintPath>
</Reference>
<Reference Include="NLog">
<HintPath>..\packages\NLog.2.1.0\lib\net40\NLog.dll</HintPath>
</Reference>
<Reference Include="Omu.ValueInjecter">
<HintPath>..\packages\ValueInjecter.2.3.3\lib\net35\Omu.ValueInjecter.dll</HintPath>
</Reference>
@@ -100,7 +99,6 @@
<Compile Include="Commands\CommandResource.cs" />
<Compile Include="Extensions\AccessControlHeaders.cs" />
<Compile Include="Extensions\Pipelines\CorsPipeline.cs" />
<Compile Include="Extensions\Pipelines\RequestLoggingPipeline.cs" />
<Compile Include="Frontend\Mappers\LoginHtmlMapper.cs" />
<Compile Include="Frontend\Mappers\RobotsTxtMapper.cs" />
<Compile Include="Indexers\ReleaseModuleBase.cs" />
@@ -232,6 +230,7 @@
<Compile Include="Series\SeasonStatisticsResource.cs" />
<Compile Include="System\Backup\BackupModule.cs" />
<Compile Include="System\Backup\BackupResource.cs" />
<Compile Include="System\Statistics\StatisticsModule.cs" />
<Compile Include="System\Tasks\TaskModule.cs" />
<Compile Include="System\Tasks\TaskResource.cs" />
<Compile Include="System\SystemModule.cs" />

View File

@@ -92,6 +92,11 @@ namespace NzbDrone.Api
{
var providerDefinition = GetDefinition(providerResource, false);
if (providerDefinition.Enable)
{
Test(providerDefinition, false);
}
_providerFactory.Update(providerDefinition);
}
@@ -155,10 +160,8 @@ namespace NzbDrone.Api
private Response Test(TProviderResource providerResource)
{
// Don't validate when getting the definition so we can validate afterwards (avoids validation being skipped because the provider is disabled)
var providerDefinition = GetDefinition(providerResource, true, false);
var providerDefinition = GetDefinition(providerResource, true);
Validate(providerDefinition, true);
Test(providerDefinition, true);
return "{}";

View File

@@ -16,8 +16,7 @@ namespace NzbDrone.Api.RootFolders
RootFolderValidator rootFolderValidator,
PathExistsValidator pathExistsValidator,
DroneFactoryValidator droneFactoryValidator,
MappedNetworkDriveValidator mappedNetworkDriveValidator,
StartupFolderValidator startupFolderValidator)
MappedNetworkDriveValidator mappedNetworkDriveValidator)
: base(signalRBroadcaster)
{
_rootFolderService = rootFolderService;
@@ -33,7 +32,6 @@ namespace NzbDrone.Api.RootFolders
.SetValidator(rootFolderValidator)
.SetValidator(droneFactoryValidator)
.SetValidator(mappedNetworkDriveValidator)
.SetValidator(startupFolderValidator)
.SetValidator(pathExistsValidator);
}

View File

@@ -6,6 +6,5 @@ namespace NzbDrone.Api.Series
{
public string Title { get; set; }
public int SeasonNumber { get; set; }
public int SceneSeasonNumber { get; set; }
}
}

View File

@@ -0,0 +1,62 @@
using System;
using System.Linq;
using Nancy;
using Nancy.Routing;
using NLog;
using NzbDrone.Api.Extensions;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.History;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Statistics;
namespace NzbDrone.Api.System
{
public class StatisticsModule : NzbDroneApiModule
{
private readonly IStatisticsService _statisticsService;
private readonly Logger _logger;
public StatisticsModule(IStatisticsService statisticsService, Logger logger)
: base("system/statistics")
{
_statisticsService = statisticsService;
_logger = logger;
Get["/"] = x => GetGlobalStatistics();
Get["/indexer"] = x => GetIndexerStatistics();
}
private Response GetGlobalStatistics()
{
return new
{
Generated = DateTime.UtcNow,
Uptime = GetUpTime(),
History = _statisticsService.GetGlobalStatistics()
}.AsResponse();
}
private Response GetIndexerStatistics()
{
var stats = _statisticsService.GetIndexerStatistics();
return stats.AsResponse();
}
private TimeSpan? GetUpTime()
{
try
{
return DateTime.Now - global::System.Diagnostics.Process.GetCurrentProcess().StartTime;
}
catch (Exception ex)
{
_logger.DebugException("Failed to get uptime", ex);
return null;
}
}
}
}

View File

@@ -93,6 +93,7 @@ namespace NzbDrone.Api
break;
case Lifetime.PerRequest:
throw new InvalidOperationException("Unable to directly register a per request lifetime.");
break;
default:
throw new ArgumentOutOfRangeException();
}
@@ -119,6 +120,7 @@ namespace NzbDrone.Api
break;
case Lifetime.PerRequest:
throw new InvalidOperationException("Unable to directly register a per request lifetime.");
break;
default:
throw new ArgumentOutOfRangeException();
}

View File

@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="DDay.iCal" version="1.0.2.575" targetFramework="net40" />
<package id="FluentValidation" version="6.2.1.0" targetFramework="net40" />
<package id="FluentValidation" version="6.0.2.0" targetFramework="net40" />
<package id="Nancy" version="0.23.2" targetFramework="net40" />
<package id="Nancy.Authentication.Basic" version="0.23.2" targetFramework="net40" />
<package id="Nancy.Authentication.Forms" version="0.23.2" targetFramework="net40" />
<package id="Newtonsoft.Json" version="6.0.6" targetFramework="net40" />
<package id="NLog" version="4.3.0-rc1" targetFramework="net40" />
<package id="NLog" version="2.1.0" targetFramework="net40" />
<package id="ValueInjecter" version="2.3.3" targetFramework="net40" />
</packages>

View File

@@ -45,13 +45,9 @@
<HintPath>..\packages\FluentAssertions.4.2.1\lib\net40\FluentAssertions.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.3.0-rc1\lib\net40\NLog.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="nunit.framework, Version=3.2.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>..\packages\NUnit.3.2.0\lib\net40\nunit.framework.dll</HintPath>
<Private>True</Private>
<Reference Include="nunit.framework, Version=2.6.3.13283, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\NUnit.2.6.3\lib\nunit.framework.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
@@ -64,6 +60,9 @@
<Reference Include="Moq">
<HintPath>..\packages\Moq.4.0.10827\lib\NET40\Moq.dll</HintPath>
</Reference>
<Reference Include="NLog">
<HintPath>..\packages\NLog.2.1.0\lib\net40\NLog.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="ContainerFixture.cs" />

View File

@@ -3,6 +3,6 @@
<package id="FluentAssertions" version="4.2.1" targetFramework="net40" />
<package id="Moq" version="4.0.10827" />
<package id="NBuilder" version="3.0.1.1" />
<package id="NLog" version="4.3.0-rc1" targetFramework="net40" />
<package id="NUnit" version="3.2.0" targetFramework="net40" />
<package id="NLog" version="2.1.0" targetFramework="net40" />
<package id="NUnit" version="2.6.3" targetFramework="net40" />
</packages>

View File

@@ -46,13 +46,9 @@
<HintPath>..\packages\FluentAssertions.4.2.1\lib\net40\FluentAssertions.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.3.0-rc1\lib\net40\NLog.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="nunit.framework, Version=3.2.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>..\packages\NUnit.3.2.0\lib\net40\nunit.framework.dll</HintPath>
<Private>True</Private>
<Reference Include="nunit.framework, Version=2.6.3.13283, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\NUnit.2.6.3\lib\nunit.framework.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
@@ -62,6 +58,9 @@
<Reference Include="System.Xml" />
<Reference Include="System.Xml.Linq" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="NLog">
<HintPath>..\packages\NLog.2.1.0\lib\net40\NLog.dll</HintPath>
</Reference>
<Reference Include="WebDriver, Version=2.48.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Selenium.WebDriver.2.48.0\lib\net40\WebDriver.dll</HintPath>
@@ -103,4 +102,4 @@
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="FluentAssertions" version="4.2.1" targetFramework="net40" />
<package id="NLog" version="4.3.0-rc1" targetFramework="net40" />
<package id="NUnit" version="3.2.0" targetFramework="net40" />
<package id="NLog" version="2.1.0" targetFramework="net40" />
<package id="NUnit" version="2.6.3" targetFramework="net40" />
<package id="Selenium.Support" version="2.48.0" targetFramework="net40" />
<package id="Selenium.WebDriver" version="2.48.0" targetFramework="net40" />
</packages>

View File

@@ -1,102 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Cache;
namespace NzbDrone.Common.Test.CacheTests
{
[TestFixture]
public class CachedDictionaryFixture
{
private CachedDictionary<string> _cachedString;
private DictionaryWorker _worker;
[SetUp]
public void SetUp()
{
_worker = new DictionaryWorker();
_cachedString = new CachedDictionary<string>(_worker.GetDict, TimeSpan.FromMilliseconds(100));
}
[Test]
public void should_not_fetch_on_create()
{
_worker.HitCount.Should().Be(0);
}
[Test]
public void should_fetch_on_first_call()
{
var result = _cachedString.Get("Hi");
_worker.HitCount.Should().Be(1);
result.Should().Be("Value");
}
[Test]
public void should_fetch_once()
{
var result1 = _cachedString.Get("Hi");
var result2 = _cachedString.Get("HitCount");
_worker.HitCount.Should().Be(1);
}
[Test]
public void should_auto_refresh_after_lifetime()
{
var result1 = _cachedString.Get("Hi");
Thread.Sleep(200);
var result2 = _cachedString.Get("Hi");
_worker.HitCount.Should().Be(2);
}
[Test]
public void should_refresh_early_if_requested()
{
var result1 = _cachedString.Get("Hi");
Thread.Sleep(10);
_cachedString.RefreshIfExpired(TimeSpan.FromMilliseconds(1));
var result2 = _cachedString.Get("Hi");
_worker.HitCount.Should().Be(2);
}
[Test]
public void should_not_refresh_early_if_not_expired()
{
var result1 = _cachedString.Get("Hi");
_cachedString.RefreshIfExpired(TimeSpan.FromMilliseconds(50));
var result2 = _cachedString.Get("Hi");
_worker.HitCount.Should().Be(1);
}
}
public class DictionaryWorker
{
public int HitCount { get; private set; }
public Dictionary<string, string> GetDict()
{
HitCount++;
var result = new Dictionary<string, string>();
result["Hi"] = "Value";
result["HitCount"] = "Hit count is " + HitCount;
return result;
}
}
}

View File

@@ -89,7 +89,7 @@ namespace NzbDrone.Common.Test.CacheTests
int hitCount = 0;
_cachedString = new Cached<string>();
for (int i = 0; i < 10; i++)
for (int i = 0; i < 100; i++)
{
_cachedString.Get("key", () =>
{
@@ -97,7 +97,7 @@ namespace NzbDrone.Common.Test.CacheTests
return null;
}, TimeSpan.FromMilliseconds(300));
Thread.Sleep(100);
Thread.Sleep(10);
}
hitCount.Should().BeInRange(3, 6);

View File

@@ -24,10 +24,6 @@ namespace NzbDrone.Common.Test.DiskTests
{
Mocker.GetMock<IDiskProvider>(MockBehavior.Strict);
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.GetMount(It.IsAny<string>()))
.Returns((IMount)null);
WithEmulatedDiskProvider();
WithExistingFile(_sourcePath);

View File

@@ -58,20 +58,18 @@ namespace NzbDrone.Common.Test.Http
var response = Subject.Get<HttpBinResource>(request);
response.Resource.Url.Should().Be(request.Url.FullUri);
response.Resource.Url.Should().Be(request.Url.ToString());
}
[Test]
public void should_execute_simple_post()
{
var message = "{ my: 1 }";
var request = new HttpRequest("http://eu.httpbin.org/post");
request.SetContent(message);
request.Body = "{ my: 1 }";
var response = Subject.Post<HttpBinResource>(request);
response.Resource.Data.Should().Be(message);
response.Resource.Data.Should().Be(request.Body);
}
[TestCase("gzip")]
@@ -164,7 +162,7 @@ namespace NzbDrone.Common.Test.Http
public void should_send_cookie()
{
var request = new HttpRequest("http://eu.httpbin.org/get");
request.Cookies["my"] = "cookie";
request.AddCookie("my", "cookie");
var response = Subject.Get<HttpBinResource>(request);
@@ -178,7 +176,7 @@ namespace NzbDrone.Common.Test.Http
public void GivenOldCookie()
{
var oldRequest = new HttpRequest("http://eu.httpbin.org/get");
oldRequest.Cookies["my"] = "cookie";
oldRequest.AddCookie("my", "cookie");
var oldClient = new HttpClient(new IHttpRequestInterceptor[0], Mocker.Resolve<ICacheManager>(), Mocker.Resolve<IRateLimitService>(), Mocker.Resolve<IHttpDispatcher>(), Mocker.Resolve<Logger>());
@@ -262,7 +260,7 @@ namespace NzbDrone.Common.Test.Http
var requestSet = new HttpRequest("http://eu.httpbin.org/cookies/set?my=cookie");
requestSet.AllowAutoRedirect = false;
requestSet.StoreResponseCookie = true;
requestSet.Cookies["my"] = "oldcookie";
requestSet.AddCookie("my", "oldcookie");
var responseSet = Subject.Get(requestSet);
@@ -324,10 +322,10 @@ namespace NzbDrone.Common.Test.Http
{
// the date is bad in the below - should be 13-Jul-2016
string malformedCookie = @"__cfduid=d29e686a9d65800021c66faca0a29b4261436890790; expires=Wed, 13-Jul-16 16:19:50 GMT; path=/; HttpOnly";
var requestSet = new HttpRequestBuilder("http://eu.httpbin.org/response-headers")
.AddQueryParam("Set-Cookie", malformedCookie)
.Build();
string url = "http://eu.httpbin.org/response-headers?Set-Cookie=" +
System.Uri.EscapeUriString(malformedCookie);
var requestSet = new HttpRequest(url);
requestSet.AllowAutoRedirect = false;
requestSet.StoreResponseCookie = true;
@@ -378,21 +376,6 @@ namespace NzbDrone.Common.Test.Http
{
}
}
public void should_submit_formparameters_in_body()
{
Assert.Fail();
}
public void should_submit_attachments_as_multipart()
{
Assert.Fail();
}
public void should_submit_formparameters_as_multipart_if_attachments_exist()
{
Assert.Fail();
}
}
public class HttpBinResource

View File

@@ -1,5 +1,4 @@
using System;
using FluentAssertions;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Test.Common;
@@ -9,32 +8,14 @@ namespace NzbDrone.Common.Test.Http
[TestFixture]
public class HttpRequestBuilderFixture : TestBase
{
[TestCase("http://host/{seg}/some", "http://host/dir/some")]
[TestCase("http://host/some/{seg}", "http://host/some/dir")]
public void should_add_single_segment_url_segments(string url, string result)
{
var requestBuilder = new HttpRequestBuilder(url);
requestBuilder.SetSegment("seg", "dir");
requestBuilder.Build().Url.Should().Be(result);
}
[Test]
public void shouldnt_add_value_for_nonexisting_segment()
{
var requestBuilder = new HttpRequestBuilder("http://host/{seg}/some");
Assert.Throws<InvalidOperationException>(() => requestBuilder.SetSegment("seg2", "dir"));
}
[Test]
public void should_remove_duplicated_slashes()
{
var builder = new HttpRequestBuilder("http://domain/");
var request = builder.Resource("/v1/").Build();
var request = builder.Build("/v1/");
request.Url.FullUri.Should().Be("http://domain/v1/");
request.Url.ToString().Should().Be("http://domain/v1/");
}
}

View File

@@ -8,5 +8,22 @@ namespace NzbDrone.Common.Test.Http
[TestFixture]
public class HttpRequestFixture
{
[TestCase("http://host/{seg}/some", "http://host/dir/some")]
[TestCase("http://host/some/{seg}", "http://host/some/dir")]
public void should_add_single_segment_url_segments(string url, string result)
{
var request = new HttpRequest(url);
request.AddSegment("seg", "dir");
request.Url.Should().Be(result);
}
[Test]
public void shouldnt_add_value_for_nonexisting_segment()
{
var request = new HttpRequest("http://host/{seg}/some");
Assert.Throws<InvalidOperationException>(() => request.AddSegment("seg2", "dir"));
}
}
}

View File

@@ -1,84 +0,0 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Http;
using NzbDrone.Test.Common;
namespace NzbDrone.Common.Test.Http
{
public class HttpUriFixture : TestBase
{
[TestCase("", "", "")]
[TestCase("/", "", "/")]
[TestCase("base", "", "base")]
[TestCase("/base", "", "/base")]
[TestCase("/base/", "", "/base/")]
[TestCase("", "relative", "relative")]
[TestCase("", "/relative", "/relative")]
[TestCase("/", "relative", "/relative")]
[TestCase("/", "/relative", "/relative")]
[TestCase("base", "relative", "relative")]
[TestCase("base", "/relative", "/relative")]
[TestCase("/base", "relative", "/relative")]
[TestCase("/base", "/relative", "/relative")]
[TestCase("/base/", "relative", "/base/relative")]
[TestCase("/base/", "/relative", "/relative")]
[TestCase("base/sub", "relative", "base/relative")]
[TestCase("base/sub", "/relative", "/relative")]
[TestCase("/base/sub", "relative", "/base/relative")]
[TestCase("/base/sub", "/relative", "/relative")]
[TestCase("/base/sub/", "relative", "/base/sub/relative")]
[TestCase("/base/sub/", "/relative", "/relative")]
[TestCase("abc://host.com:8080/root/file.xml", "relative/path", "abc://host.com:8080/root/relative/path")]
[TestCase("abc://host.com:8080/root/file.xml", "/relative/path", "abc://host.com:8080/relative/path")]
[TestCase("abc://host.com:8080/root/file.xml?query=1#fragment", "relative/path", "abc://host.com:8080/root/relative/path")]
[TestCase("abc://host.com:8080/root/file.xml?query=1#fragment", "/relative/path", "abc://host.com:8080/relative/path")]
[TestCase("abc://host.com:8080/root/api", "relative/path", "abc://host.com:8080/root/relative/path")]
[TestCase("abc://host.com:8080/root/api", "/relative/path", "abc://host.com:8080/relative/path")]
[TestCase("abc://host.com:8080/root/api/", "relative/path", "abc://host.com:8080/root/api/relative/path")]
[TestCase("abc://host.com:8080/root/api/", "/relative/path", "abc://host.com:8080/relative/path")]
[TestCase("abc://host.com:8080/root/api/", "//otherhost.com/path", "abc://otherhost.com/path")]
public void should_combine_uri(string basePath, string relativePath, string expected)
{
var newUri = new HttpUri(basePath) + new HttpUri(relativePath);
newUri.FullUri.Should().Be(expected);
}
[TestCase("", "", "")]
[TestCase("/", "", "/")]
[TestCase("base", "", "base")]
[TestCase("/base", "", "/base")]
[TestCase("/base/", "", "/base/")]
[TestCase("", "relative", "relative")]
[TestCase("", "/relative", "/relative")]
[TestCase("/", "relative", "/relative")]
[TestCase("/", "/relative", "/relative")]
[TestCase("base", "relative", "base/relative")]
[TestCase("base", "/relative", "base/relative")]
[TestCase("/base", "relative", "/base/relative")]
[TestCase("/base", "/relative", "/base/relative")]
[TestCase("/base/", "relative", "/base/relative")]
[TestCase("/base/", "/relative", "/base/relative")]
[TestCase("base/sub", "relative", "base/sub/relative")]
[TestCase("base/sub", "/relative", "base/sub/relative")]
[TestCase("/base/sub", "relative", "/base/sub/relative")]
[TestCase("/base/sub", "/relative", "/base/sub/relative")]
[TestCase("/base/sub/", "relative", "/base/sub/relative")]
[TestCase("/base/sub/", "/relative", "/base/sub/relative")]
[TestCase("/base/sub/", "relative/", "/base/sub/relative/")]
[TestCase("/base/sub/", "/relative/", "/base/sub/relative/")]
[TestCase("abc://host.com:8080/root/file.xml", "relative/path", "abc://host.com:8080/root/file.xml/relative/path")]
[TestCase("abc://host.com:8080/root/file.xml", "/relative/path", "abc://host.com:8080/root/file.xml/relative/path")]
[TestCase("abc://host.com:8080/root/file.xml?query=1#fragment", "relative/path", "abc://host.com:8080/root/file.xml/relative/path?query=1#fragment")]
[TestCase("abc://host.com:8080/root/file.xml?query=1#fragment", "/relative/path", "abc://host.com:8080/root/file.xml/relative/path?query=1#fragment")]
[TestCase("abc://host.com:8080/root/api", "relative/path", "abc://host.com:8080/root/api/relative/path")]
[TestCase("abc://host.com:8080/root/api", "/relative/path", "abc://host.com:8080/root/api/relative/path")]
[TestCase("abc://host.com:8080/root/api/", "relative/path", "abc://host.com:8080/root/api/relative/path")]
[TestCase("abc://host.com:8080/root/api/", "/relative/path", "abc://host.com:8080/root/api/relative/path")]
public void should_combine_relative_path(string basePath, string relativePath, string expected)
{
var newUri = new HttpUri(basePath).CombinePath(relativePath);
newUri.FullUri.Should().Be(expected);
}
}
}

View File

@@ -45,13 +45,9 @@
<HintPath>..\packages\FluentAssertions.4.2.1\lib\net40\FluentAssertions.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.3.0-rc1\lib\net40\NLog.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="nunit.framework, Version=3.2.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>..\packages\NUnit.3.2.0\lib\net40\nunit.framework.dll</HintPath>
<Private>True</Private>
<Reference Include="nunit.framework, Version=2.6.3.13283, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\NUnit.2.6.3\lib\nunit.framework.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
@@ -64,9 +60,11 @@
<Reference Include="Moq">
<HintPath>..\packages\Moq.4.0.10827\lib\NET40\Moq.dll</HintPath>
</Reference>
<Reference Include="NLog">
<HintPath>..\packages\NLog.2.1.0\lib\net40\NLog.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="CacheTests\CachedDictionaryFixture.cs" />
<Compile Include="CacheTests\CachedFixture.cs" />
<Compile Include="CacheTests\CachedManagerFixture.cs" />
<Compile Include="ConfigFileProviderTest.cs" />
@@ -83,7 +81,6 @@
<Compile Include="Http\HttpClientFixture.cs" />
<Compile Include="Http\HttpRequestBuilderFixture.cs" />
<Compile Include="Http\HttpRequestFixture.cs" />
<Compile Include="Http\HttpUriFixture.cs" />
<Compile Include="InstrumentationTests\CleanseLogMessageFixture.cs" />
<Compile Include="LevenshteinDistanceFixture.cs" />
<Compile Include="OsPathFixture.cs" />
@@ -154,4 +151,4 @@
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>

View File

@@ -6,7 +6,6 @@ using NUnit.Framework;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Test.Common;
using NzbDrone.Test.Common.Categories;
namespace NzbDrone.Common.Test
{
@@ -154,7 +153,7 @@ namespace NzbDrone.Common.Test
}
[Test]
[Ignore("Parent, not Grandparent")]
[Ignore]
public void should_not_be_parent_when_it_is_grandparent()
{
var path = Path.Combine(_parent, "parent", "child");
@@ -206,7 +205,7 @@ namespace NzbDrone.Common.Test
public void get_actual_casing_should_return_actual_casing_for_local_dir_in_windows()
{
WindowsOnly();
var path = Directory.GetCurrentDirectory().Replace("c:\\","C:\\").Replace("system32", "System32");
var path = Directory.GetCurrentDirectory().Replace("c:\\","C:\\");
path.ToUpper().GetActualCasing().Should().Be(path);
path.ToLower().GetActualCasing().Should().Be(path);
@@ -223,7 +222,6 @@ namespace NzbDrone.Common.Test
[Test]
[Explicit]
[ManualTest]
public void get_actual_casing_should_return_original_casing_for_shares()
{
var path = @"\\server\Pool\Apps";

View File

@@ -31,20 +31,10 @@ namespace NzbDrone.Common.Test
[TearDown]
public void TearDown()
{
Process.GetProcessesByName(DummyApp.DUMMY_PROCCESS_NAME).ToList().ForEach(c =>
{
try
{
c.Kill();
}
catch (Win32Exception ex)
{
TestLogger.Warn(ex, "{0} when killing process", ex.Message);
}
});
Process.GetProcessesByName(DummyApp.DUMMY_PROCCESS_NAME).ToList().ForEach(c => c.Kill());
}
[Test]
public void GetById_should_return_null_if_process_doesnt_exist()
{

View File

@@ -2,9 +2,7 @@
using System.ServiceProcess;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Test.Common;
using NzbDrone.Test.Common.Categories;
namespace NzbDrone.Common.Test
{
@@ -15,6 +13,7 @@ namespace NzbDrone.Common.Test
private const string ALWAYS_INSTALLED_SERVICE = "SCardSvr"; //Smart Card
private const string TEMP_SERVICE_NAME = "NzbDrone_Nunit";
[SetUp]
public void Setup()
{
@@ -25,10 +24,8 @@ namespace NzbDrone.Common.Test
[TearDown]
public void TearDown()
{
if (OsInfo.IsWindows)
{
CleanupService();
}
WindowsOnly();
CleanupService();
}
@@ -73,7 +70,6 @@ namespace NzbDrone.Common.Test
[Test]
[Explicit]
[ManualTest]
public void UnInstallService()
{
Subject.UnInstall(ServiceProvider.NZBDRONE_SERVICE_NAME);
@@ -82,7 +78,6 @@ namespace NzbDrone.Common.Test
[Test]
[Explicit]
[ManualTest]
public void Should_be_able_to_start_and_stop_service()
{
Subject.GetService(ALWAYS_INSTALLED_SERVICE).Status

View File

@@ -21,7 +21,6 @@ namespace NzbDrone.Common.Test.TPLTests
[Test]
[Retry(3)]
public void should_hold_the_call_for_debounce_duration()
{
var counter = new Counter();
@@ -41,7 +40,6 @@ namespace NzbDrone.Common.Test.TPLTests
}
[Test]
[Retry(3)]
public void should_throttle_calls()
{
var counter = new Counter();
@@ -67,7 +65,6 @@ namespace NzbDrone.Common.Test.TPLTests
}
[Test]
[Retry(3)]
public void should_hold_the_call_while_paused()
{
var counter = new Counter();
@@ -101,7 +98,6 @@ namespace NzbDrone.Common.Test.TPLTests
}
[Test]
[Retry(3)]
public void should_handle_pause_reentrancy()
{
var counter = new Counter();

View File

@@ -62,7 +62,6 @@ namespace NzbDrone.Common.Test.TPLTests
}
[Test]
[Retry(3)]
public void should_wait_for_existing()
{
GivenExisting("me", _epoch + TimeSpan.FromMilliseconds(200));
@@ -71,7 +70,7 @@ namespace NzbDrone.Common.Test.TPLTests
Subject.WaitAndPulse("me", TimeSpan.FromMilliseconds(400));
watch.Stop();
watch.ElapsedMilliseconds.Should().BeInRange(175, 250);
watch.ElapsedMilliseconds.Should().BeInRange(195, 250);
}
[Test]

View File

@@ -2,6 +2,6 @@
<packages>
<package id="FluentAssertions" version="4.2.1" targetFramework="net40" />
<package id="Moq" version="4.0.10827" />
<package id="NLog" version="4.3.0-rc1" targetFramework="net40" />
<package id="NUnit" version="3.2.0" targetFramework="net40" />
<package id="NLog" version="2.1.0" targetFramework="net40" />
<package id="NUnit" version="2.6.3" targetFramework="net40" />
</packages>

View File

@@ -6,9 +6,8 @@ namespace NzbDrone.Common.Cache
{
public interface ICacheManager
{
ICached<T> GetCache<T>(Type host);
ICached<T> GetCache<T>(Type host, string name);
ICachedDictionary<T> GetCacheDictionary<T>(Type host, string name, Func<IDictionary<string, T>> fetchFunc = null, TimeSpan? lifeTime = null);
ICached<T> GetCache<T>(Type host);
void Clear();
ICollection<ICached> Caches { get; }
}
@@ -23,6 +22,12 @@ namespace NzbDrone.Common.Cache
}
public ICached<T> GetCache<T>(Type host)
{
Ensure.That(host, () => host).IsNotNull();
return GetCache<T>(host, host.FullName);
}
public void Clear()
{
_cache.Clear();
@@ -30,12 +35,6 @@ namespace NzbDrone.Common.Cache
public ICollection<ICached> Caches { get { return _cache.Values; } }
public ICached<T> GetCache<T>(Type host)
{
Ensure.That(host, () => host).IsNotNull();
return GetCache<T>(host, host.FullName);
}
public ICached<T> GetCache<T>(Type host, string name)
{
Ensure.That(host, () => host).IsNotNull();
@@ -43,13 +42,5 @@ namespace NzbDrone.Common.Cache
return (ICached<T>)_cache.Get(host.FullName + "_" + name, () => new Cached<T>());
}
public ICachedDictionary<T> GetCacheDictionary<T>(Type host, string name, Func<IDictionary<string, T>> fetchFunc = null, TimeSpan? lifeTime = null)
{
Ensure.That(host, () => host).IsNotNull();
Ensure.That(name, () => name).IsNotNullOrWhiteSpace();
return (ICachedDictionary<T>)_cache.Get("dict_" + host.FullName + "_" + name, () => new CachedDictionary<T>(fetchFunc, lifeTime));
}
}
}

View File

@@ -1,137 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace NzbDrone.Common.Cache
{
public class CachedDictionary<TValue> : ICachedDictionary<TValue>
{
private readonly Func<IDictionary<string, TValue>> _fetchFunc;
private readonly TimeSpan? _ttl;
private DateTime _lastRefreshed = DateTime.MinValue;
private ConcurrentDictionary<string, TValue> _items = new ConcurrentDictionary<string, TValue>();
public CachedDictionary(Func<IDictionary<string, TValue>> fetchFunc = null, TimeSpan? ttl = null)
{
_fetchFunc = fetchFunc;
_ttl = ttl;
}
public bool IsExpired(TimeSpan ttl)
{
return _lastRefreshed.Add(ttl) < DateTime.UtcNow;
}
public void RefreshIfExpired()
{
if (_ttl.HasValue && _fetchFunc != null)
{
RefreshIfExpired(_ttl.Value);
}
}
public void RefreshIfExpired(TimeSpan ttl)
{
if (IsExpired(ttl))
{
Refresh();
}
}
public void Refresh()
{
if (_fetchFunc == null)
{
throw new InvalidOperationException("Cannot update cache without data source.");
}
Update(_fetchFunc());
ExtendTTL();
}
public void Update(IDictionary<string, TValue> items)
{
_items = new ConcurrentDictionary<string, TValue>(items);
ExtendTTL();
}
public void ExtendTTL()
{
_lastRefreshed = DateTime.UtcNow;
}
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
public ICollection<TValue> Values
{
get
{
RefreshIfExpired();
return _items.Values;
}
}
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
public int Count
{
get
{
RefreshIfExpired();
return _items.Count;
}
}
public TValue Get(string key)
{
RefreshIfExpired();
TValue result;
if (!_items.TryGetValue(key, out result))
{
throw new KeyNotFoundException(string.Format("Item {0} not found in cache.", key));
}
return result;
}
public TValue Find(string key)
{
RefreshIfExpired();
TValue result;
_items.TryGetValue(key, out result);
return result;
}
public void Clear()
{
_items.Clear();
_lastRefreshed = DateTime.MinValue;
}
public void ClearExpired()
{
if (!_ttl.HasValue)
{
throw new InvalidOperationException("Checking expiry without ttl not possible.");
}
if (IsExpired(_ttl.Value))
{
Clear();
}
}
public void Remove(string key)
{
TValue item;
_items.TryRemove(key, out item);
}
}
}

View File

@@ -1,18 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace NzbDrone.Common.Cache
{
public interface ICachedDictionary<TValue> : ICached
{
void RefreshIfExpired();
void RefreshIfExpired(TimeSpan ttl);
void Refresh();
void Update(IDictionary<string, TValue> items);
void ExtendTTL();
TValue Get(string key);
TValue Find(string key);
bool IsExpired(TimeSpan ttl);
}
}

View File

@@ -0,0 +1,19 @@
using NzbDrone.Common.Http;
namespace NzbDrone.Common.Cloud
{
public interface IDroneServicesRequestBuilder
{
HttpRequest Build(string path);
}
public class DroneServicesHttpRequestBuilder : HttpRequestBuilder, IDroneServicesRequestBuilder
{
private const string ROOT_URL = "http://services.sonarr.tv/v1/";
public DroneServicesHttpRequestBuilder()
: base(ROOT_URL)
{
}
}
}

View File

@@ -1,28 +0,0 @@
using System;
using NzbDrone.Common.Http;
namespace NzbDrone.Common.Cloud
{
public interface ISonarrCloudRequestBuilder
{
IHttpRequestBuilderFactory Services { get; }
IHttpRequestBuilderFactory SkyHookTvdb { get; }
}
public class SonarrCloudRequestBuilder : ISonarrCloudRequestBuilder
{
public SonarrCloudRequestBuilder()
{
Services = new HttpRequestBuilder("http://services.sonarr.tv/v1/")
.CreateFactory();
SkyHookTvdb = new HttpRequestBuilder("http://skyhook.sonarr.tv/v1/tvdb/{route}/{language}/")
.SetSegment("language", "en")
.CreateFactory();
}
public IHttpRequestBuilderFactory Services { get; private set; }
public IHttpRequestBuilderFactory SkyHookTvdb { get; private set; }
}
}

View File

@@ -10,16 +10,12 @@ namespace NzbDrone.Common.Crypto
public static int GetHashInt31(string target)
{
var hash = GetHash(target);
return BitConverter.ToInt32(hash, 0) & 0x7fffffff;
}
public static byte[] GetHash(string target)
{
byte[] hash;
lock (Sha1)
{
return Sha1.ComputeHash(Encoding.Default.GetBytes(target));
hash = Sha1.ComputeHash(Encoding.Default.GetBytes(target));
}
return BitConverter.ToInt32(hash, 0) & 0x7fffffff;
}
}
}

View File

@@ -293,30 +293,18 @@ namespace NzbDrone.Common.Disk
var sid = new SecurityIdentifier(accountSid, null);
var directoryInfo = new DirectoryInfo(filename);
var directorySecurity = directoryInfo.GetAccessControl(AccessControlSections.Access);
var rules = directorySecurity.GetAccessRules(true, false, typeof(SecurityIdentifier));
if (rules.OfType<FileSystemAccessRule>().Any(acl => acl.AccessControlType == controlType && (acl.FileSystemRights & rights) == rights && acl.IdentityReference.Equals(sid)))
{
return;
}
var directorySecurity = directoryInfo.GetAccessControl();
var accessRule = new FileSystemAccessRule(sid, rights,
InheritanceFlags.ContainerInherit | InheritanceFlags.ObjectInherit,
PropagationFlags.InheritOnly, controlType);
PropagationFlags.None, controlType);
bool modified;
directorySecurity.ModifyAccessRule(AccessControlModification.Add, accessRule, out modified);
if (modified)
{
directoryInfo.SetAccessControl(directorySecurity);
}
directorySecurity.AddAccessRule(accessRule);
directoryInfo.SetAccessControl(directorySecurity);
}
catch (Exception e)
{
Logger.Warn(e, "Couldn't set permission for {0}. account:{1} rights:{2} accessControlType:{3}", filename, accountSid, rights, controlType);
Logger.WarnException(string.Format("Couldn't set permission for {0}. account:{1} rights:{2} accessControlType:{3}", filename, accountSid, rights, controlType), e);
throw;
}
@@ -358,12 +346,12 @@ namespace NzbDrone.Common.Disk
public string[] GetFixedDrives()
{
return GetMounts().Where(x => x.DriveType == DriveType.Fixed).Select(x => x.RootDirectory).ToArray();
return (DriveInfo.GetDrives().Where(x => x.DriveType == DriveType.Fixed).Select(x => x.Name)).ToArray();
}
public string GetVolumeLabel(string path)
{
var driveInfo = GetMounts().SingleOrDefault(d => d.RootDirectory.PathEquals(path));
var driveInfo = DriveInfo.GetDrives().SingleOrDefault(d => d.Name == path);
if (driveInfo == null)
{
@@ -388,36 +376,11 @@ namespace NzbDrone.Common.Disk
return new FileStream(path, FileMode.Create);
}
public virtual List<IMount> GetMounts()
{
return GetDriveInfoMounts();
}
public virtual IMount GetMount(string path)
{
try
{
var mounts = GetMounts();
return mounts.Where(drive => drive.RootDirectory.PathEquals(path) ||
drive.RootDirectory.IsParentPath(path))
.OrderByDescending(drive => drive.RootDirectory.Length)
.FirstOrDefault();
}
catch (Exception ex)
{
Logger.Debug(ex, string.Format("Failed to get mount for path {0}", path));
return null;
}
}
protected List<IMount> GetDriveInfoMounts()
public List<DriveInfo> GetDrives()
{
return DriveInfo.GetDrives()
.Where(d => d.DriveType == DriveType.Fixed || d.DriveType == DriveType.Network || d.DriveType == DriveType.Removable)
.Where(d => d.DriveType == DriveType.Fixed || d.DriveType == DriveType.Network)
.Where(d => d.IsReady)
.Select(d => new DriveInfoMount(d))
.Cast<IMount>()
.ToList();
}
@@ -438,19 +401,5 @@ namespace NzbDrone.Common.Disk
return di.GetFiles().ToList();
}
public void RemoveEmptySubfolders(string path)
{
var subfolders = GetDirectories(path);
var files = GetFiles(path, SearchOption.AllDirectories);
foreach (var subfolder in subfolders)
{
if (files.None(f => subfolder.IsParentPath(f)))
{
DeleteFolder(subfolder, false);
}
}
}
}
}

View File

@@ -45,13 +45,6 @@ namespace NzbDrone.Common.Disk
}
public TransferMode TransferFolder(string sourcePath, string targetPath, TransferMode mode, bool verified = true)
{
var verificationMode = verified ? VerificationMode : DiskTransferVerificationMode.VerifyOnly;
return TransferFolder(sourcePath, targetPath, mode, verificationMode);
}
public TransferMode TransferFolder(string sourcePath, string targetPath, TransferMode mode, DiskTransferVerificationMode verificationMode)
{
Ensure.That(sourcePath, () => sourcePath).IsValidPath();
Ensure.That(targetPath, () => targetPath).IsValidPath();
@@ -65,14 +58,14 @@ namespace NzbDrone.Common.Disk
foreach (var subDir in _diskProvider.GetDirectoryInfos(sourcePath))
{
result &= TransferFolder(subDir.FullName, Path.Combine(targetPath, subDir.Name), mode, verificationMode);
result &= TransferFolder(subDir.FullName, Path.Combine(targetPath, subDir.Name), mode, verified);
}
foreach (var sourceFile in _diskProvider.GetFileInfos(sourcePath))
{
var destFile = Path.Combine(targetPath, sourceFile.Name);
result &= TransferFile(sourceFile.FullName, destFile, mode, true, verificationMode);
result &= TransferFile(sourceFile.FullName, destFile, mode, true, verified);
}
if (mode.HasFlag(TransferMode.Move))
@@ -84,17 +77,15 @@ namespace NzbDrone.Common.Disk
}
public TransferMode TransferFile(string sourcePath, string targetPath, TransferMode mode, bool overwrite = false, bool verified = true)
{
var verificationMode = verified ? VerificationMode : DiskTransferVerificationMode.None;
return TransferFile(sourcePath, targetPath, mode, overwrite, verificationMode);
}
public TransferMode TransferFile(string sourcePath, string targetPath, TransferMode mode, bool overwrite, DiskTransferVerificationMode verificationMode)
{
Ensure.That(sourcePath, () => sourcePath).IsValidPath();
Ensure.That(targetPath, () => targetPath).IsValidPath();
if (VerificationMode != DiskTransferVerificationMode.Transactional && VerificationMode != DiskTransferVerificationMode.TryTransactional)
{
verified = false;
}
_logger.Debug("{0} [{1}] > [{2}]", mode, sourcePath, targetPath);
var originalSize = _diskProvider.GetFileSize(sourcePath);
@@ -163,59 +154,49 @@ namespace NzbDrone.Common.Disk
}
}
// We force a transactional transfer if the transfer occurs between mounts and one of the mounts is cifs, it would be a copy anyway.
if (verificationMode == DiskTransferVerificationMode.TryTransactional && OsInfo.IsNotWindows)
if (verified)
{
var sourceMount = _diskProvider.GetMount(sourcePath);
var targetMount = _diskProvider.GetMount(targetPath);
if (sourceMount != null && targetMount != null && sourceMount.RootDirectory != targetMount.RootDirectory &&
(sourceMount.DriveFormat == "cifs" || targetMount.DriveFormat == "cifs"))
{
verificationMode = DiskTransferVerificationMode.Transactional;
}
}
if (mode.HasFlag(TransferMode.Copy))
{
if (verificationMode == DiskTransferVerificationMode.Transactional || verificationMode == DiskTransferVerificationMode.TryTransactional)
if (mode.HasFlag(TransferMode.Copy))
{
if (TryCopyFileTransactional(sourcePath, targetPath, originalSize))
{
return TransferMode.Copy;
}
throw new IOException(string.Format("Failed to completely transfer [{0}] to [{1}], aborting.", sourcePath, targetPath));
}
else if (verificationMode == DiskTransferVerificationMode.VerifyOnly)
if (mode.HasFlag(TransferMode.Move))
{
if (TryMoveFileTransactional(sourcePath, targetPath, originalSize))
{
return TransferMode.Move;
}
}
throw new IOException(string.Format("Failed to completely transfer [{0}] to [{1}], aborting.", sourcePath, targetPath));
}
else if (VerificationMode != DiskTransferVerificationMode.None)
{
if (mode.HasFlag(TransferMode.Copy))
{
TryCopyFileVerified(sourcePath, targetPath, originalSize);
return TransferMode.Copy;
}
else
{
_diskProvider.CopyFile(sourcePath, targetPath);
return TransferMode.Copy;
}
}
if (mode.HasFlag(TransferMode.Move))
{
if (verificationMode == DiskTransferVerificationMode.Transactional || verificationMode == DiskTransferVerificationMode.TryTransactional)
{
if (TryMoveFileTransactional(sourcePath, targetPath, originalSize, verificationMode))
{
return TransferMode.Move;
}
throw new IOException(string.Format("Failed to completely transfer [{0}] to [{1}], aborting.", sourcePath, targetPath));
}
else if (verificationMode == DiskTransferVerificationMode.VerifyOnly)
if (mode.HasFlag(TransferMode.Move))
{
TryMoveFileVerified(sourcePath, targetPath, originalSize);
return TransferMode.Move;
}
else
}
else
{
if (mode.HasFlag(TransferMode.Copy))
{
_diskProvider.CopyFile(sourcePath, targetPath);
return TransferMode.Copy;
}
if (mode.HasFlag(TransferMode.Move))
{
_diskProvider.MoveFile(sourcePath, targetPath);
return TransferMode.Move;
@@ -259,7 +240,7 @@ namespace NzbDrone.Common.Disk
}
catch (Exception ex)
{
_logger.Error(ex, string.Format("Failed to properly rollback the file move [{0}] to [{1}], incomplete file may be left in target path.", sourcePath, targetPath));
_logger.ErrorException(string.Format("Failed to properly rollback the file move [{0}] to [{1}], incomplete file may be left in target path.", sourcePath, targetPath), ex);
}
}
@@ -275,7 +256,7 @@ namespace NzbDrone.Common.Disk
}
catch (Exception ex)
{
_logger.Error(ex, string.Format("Failed to properly rollback the file move [{0}] to [{1}], file may be left in target path.", sourcePath, targetPath));
_logger.ErrorException(string.Format("Failed to properly rollback the file move [{0}] to [{1}], file may be left in target path.", sourcePath, targetPath), ex);
}
}
@@ -294,7 +275,7 @@ namespace NzbDrone.Common.Disk
}
catch (Exception ex)
{
_logger.Error(ex, string.Format("Failed to properly rollback the file copy [{0}] to [{1}], file may be left in target path.", sourcePath, targetPath));
_logger.ErrorException(string.Format("Failed to properly rollback the file copy [{0}] to [{1}], file may be left in target path.", sourcePath, targetPath), ex);
}
}
@@ -359,7 +340,7 @@ namespace NzbDrone.Common.Disk
return false;
}
private bool TryMoveFileTransactional(string sourcePath, string targetPath, long originalSize, DiskTransferVerificationMode verificationMode)
private bool TryMoveFileTransactional(string sourcePath, string targetPath, long originalSize)
{
var backupPath = sourcePath + ".backup~";
var tempTargetPath = targetPath + ".partial~";
@@ -413,7 +394,7 @@ namespace NzbDrone.Common.Disk
}
}
if (verificationMode == DiskTransferVerificationMode.Transactional)
if (VerificationMode == DiskTransferVerificationMode.Transactional)
{
_logger.Trace("Hardlink move failed, reverting to copy.");
if (TryCopyFileTransactional(sourcePath, targetPath, originalSize))

View File

@@ -1,76 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Common.Disk
{
public class DriveInfoMount : IMount
{
private readonly DriveInfo _driveInfo;
public DriveInfoMount(DriveInfo driveInfo)
{
_driveInfo = driveInfo;
}
public long AvailableFreeSpace
{
get { return _driveInfo.AvailableFreeSpace; }
}
public string DriveFormat
{
get { return _driveInfo.DriveFormat; }
}
public DriveType DriveType
{
get { return _driveInfo.DriveType; }
}
public bool IsReady
{
get { return _driveInfo.IsReady; }
}
public string Name
{
get { return _driveInfo.Name; }
}
public string RootDirectory
{
get { return _driveInfo.RootDirectory.FullName; }
}
public long TotalFreeSpace
{
get { return _driveInfo.TotalFreeSpace; }
}
public long TotalSize
{
get { return _driveInfo.TotalSize; }
}
public string VolumeLabel
{
get { return _driveInfo.VolumeLabel; }
}
public string VolumeName
{
get
{
if (VolumeLabel.IsNullOrWhiteSpace())
{
return Name;
}
return string.Format("{0} ({1})", Name, VolumeLabel);
}
}
}
}

View File

@@ -103,12 +103,12 @@ namespace NzbDrone.Common.Disk
private List<FileSystemModel> GetDrives()
{
return _diskProvider.GetMounts()
return _diskProvider.GetDrives()
.Select(d => new FileSystemModel
{
Type = FileSystemEntityType.Drive,
Name = d.VolumeLabel,
Path = d.RootDirectory,
Name = GetVolumeName(d),
Path = d.Name,
LastModified = null
})
.ToList();
@@ -157,6 +157,16 @@ namespace NzbDrone.Common.Disk
return path;
}
private string GetVolumeName(DriveInfo driveInfo)
{
if (driveInfo.VolumeLabel.IsNullOrWhiteSpace())
{
return driveInfo.Name;
}
return string.Format("{0} ({1})", driveInfo.Name, driveInfo.VolumeLabel);
}
private string GetParent(string path)
{

View File

@@ -40,13 +40,12 @@ namespace NzbDrone.Common.Disk
void SetPermissions(string filename, WellKnownSidType accountSid, FileSystemRights rights, AccessControlType controlType);
FileAttributes GetFileAttributes(string path);
void EmptyFolder(string path);
string[] GetFixedDrives();
string GetVolumeLabel(string path);
FileStream OpenReadStream(string path);
FileStream OpenWriteStream(string path);
List<IMount> GetMounts();
IMount GetMount(string path);
List<DriveInfo> GetDrives();
List<DirectoryInfo> GetDirectoryInfos(string path);
List<FileInfo> GetFileInfos(string path);
void RemoveEmptySubfolders(string path);
}
}

View File

@@ -1,21 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace NzbDrone.Common.Disk
{
public interface IMount
{
long AvailableFreeSpace { get; }
string DriveFormat { get; }
DriveType DriveType { get; }
bool IsReady { get; }
string Name { get; }
string RootDirectory { get; }
long TotalFreeSpace { get; }
long TotalSize { get; }
string VolumeLabel { get; }
}
}

View File

@@ -39,11 +39,11 @@ namespace NzbDrone.Common.EnvironmentInfo
{
try
{
_diskProvider.SetPermissions(_appFolderInfo.AppDataFolder, WellKnownSidType.WorldSid, FileSystemRights.Modify, AccessControlType.Allow);
_diskProvider.SetPermissions(_appFolderInfo.AppDataFolder, WellKnownSidType.WorldSid, FileSystemRights.FullControl, AccessControlType.Allow);
}
catch (Exception ex)
{
_logger.Warn(ex, "Coudn't set app folder permission");
_logger.WarnException("Coudn't set app folder permission", ex);
}
}
}

View File

@@ -60,7 +60,7 @@ namespace NzbDrone.Common.EnvironmentInfo
}
catch (Exception ex)
{
_logger.Warn(ex, "Error checking if the current user is an administrator.");
_logger.WarnException("Error checking if the current user is an administrator.", ex);
return false;
}
}

View File

@@ -26,7 +26,7 @@ namespace NzbDrone.Common.Extensions
public static void Add<TKey, TValue>(this ICollection<KeyValuePair<TKey, TValue>> collection, TKey key, TValue value)
{
collection.Add(new KeyValuePair<TKey, TValue>(key, value));
collection.Add(key, value);
}
}
}

View File

@@ -73,14 +73,8 @@ namespace NzbDrone.Common.Extensions
public static bool IsParentPath(this string parentPath, string childPath)
{
if (parentPath != "/")
{
parentPath = parentPath.TrimEnd(Path.DirectorySeparatorChar);
}
if (childPath != "/")
{
childPath = childPath.TrimEnd(Path.DirectorySeparatorChar);
}
parentPath = parentPath.TrimEnd(Path.DirectorySeparatorChar);
childPath = childPath.TrimEnd(Path.DirectorySeparatorChar);
var parent = new DirectoryInfo(parentPath);
var child = new DirectoryInfo(childPath);

View File

@@ -100,10 +100,5 @@ namespace NzbDrone.Common.Extensions
.Select(x => Convert.ToByte(input.Substring(x, 2), 16))
.ToArray();
}
public static string ToHexString(this byte[] input)
{
return string.Concat(Array.ConvertAll(input, x => x.ToString("X2")));
}
}
}

View File

@@ -12,7 +12,6 @@ using NLog;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Instrumentation;
using System.Reflection;
namespace NzbDrone.Common.Http.Dispatchers
{
@@ -22,21 +21,6 @@ namespace NzbDrone.Common.Http.Dispatchers
private static readonly Logger _logger = NzbDroneLogger.GetLogger(typeof(CurlHttpDispatcher));
private const string _caBundleFileName = "curl-ca-bundle.crt";
private static readonly string _caBundleFilePath;
static CurlHttpDispatcher()
{
if (Assembly.GetExecutingAssembly().Location.IsNotNullOrWhiteSpace())
{
_caBundleFilePath = Path.Combine(Assembly.GetExecutingAssembly().Location, "..", _caBundleFileName);
}
else
{
_caBundleFilePath = _caBundleFileName;
}
}
public static bool CheckAvailability()
{
try
@@ -45,7 +29,7 @@ namespace NzbDrone.Common.Http.Dispatchers
}
catch (Exception ex)
{
_logger.Trace(ex, "Initializing curl failed");
_logger.TraceException("Initializing curl failed", ex);
return false;
}
}
@@ -57,6 +41,11 @@ namespace NzbDrone.Common.Http.Dispatchers
throw new ApplicationException("Curl failed to initialize.");
}
if (request.NetworkCredential != null)
{
throw new NotImplementedException("Credentials not supported for curl dispatcher.");
}
lock (CurlGlobalHandle.Instance)
{
Stream responseStream = new MemoryStream();
@@ -75,8 +64,8 @@ namespace NzbDrone.Common.Http.Dispatchers
headerStream.Write(b, 0, s * n);
return s * n;
};
curlEasy.Url = request.Url.FullUri;
curlEasy.Url = request.Url.AbsoluteUri;
switch (request.Method)
{
case HttpMethod.GET:
@@ -97,25 +86,21 @@ namespace NzbDrone.Common.Http.Dispatchers
curlEasy.UserAgent = UserAgentBuilder.UserAgent;
curlEasy.FollowLocation = request.AllowAutoRedirect;
if (request.RequestTimeout != TimeSpan.Zero)
{
curlEasy.Timeout = (int)Math.Ceiling(request.RequestTimeout.TotalSeconds);
}
if (OsInfo.IsWindows)
{
curlEasy.CaInfo = _caBundleFilePath;
curlEasy.CaInfo = "curl-ca-bundle.crt";
}
if (cookies != null)
{
curlEasy.Cookie = cookies.GetCookieHeader((Uri)request.Url);
curlEasy.Cookie = cookies.GetCookieHeader(request.Url);
}
if (request.ContentData != null)
if (!request.Body.IsNullOrWhiteSpace())
{
curlEasy.PostFieldSize = request.ContentData.Length;
curlEasy.SetOpt(CurlOption.CopyPostFields, new string(Array.ConvertAll(request.ContentData, v => (char)v)));
// TODO: This might not go well with encoding.
curlEasy.PostFieldSize = request.Body.Length;
curlEasy.SetOpt(CurlOption.CopyPostFields, request.Body);
}
// Yes, we have to keep a ref to the object to prevent corrupting the unmanaged state
@@ -127,15 +112,7 @@ namespace NzbDrone.Common.Http.Dispatchers
if (result != CurlCode.Ok)
{
switch (result)
{
case CurlCode.SslCaCert:
case (CurlCode)77:
throw new WebException(string.Format("Curl Error {0} for Url {1}, issues with your operating system SSL Root Certificate Bundle (ca-bundle).", result, curlEasy.Url));
default:
throw new WebException(string.Format("Curl Error {0} for Url {1}", result, curlEasy.Url));
}
throw new WebException(string.Format("Curl Error {0} for Url {1}", result, curlEasy.Url));
}
}
@@ -190,7 +167,7 @@ namespace NzbDrone.Common.Http.Dispatchers
{
try
{
cookies.SetCookies((Uri)request.Url, FixSetCookieHeader(setCookie));
cookies.SetCookies(request.Url, FixSetCookieHeader(setCookie));
}
catch (CookieException ex)
{

View File

@@ -8,35 +8,34 @@ namespace NzbDrone.Common.Http.Dispatchers
{
public HttpResponse GetResponse(HttpRequest request, CookieContainer cookies)
{
var webRequest = (HttpWebRequest)WebRequest.Create((Uri)request.Url);
var webRequest = (HttpWebRequest)WebRequest.Create(request.Url);
// Deflate is not a standard and could break depending on implementation.
// we should just stick with the more compatible Gzip
//http://stackoverflow.com/questions/8490718/how-to-decompress-stream-deflated-with-java-util-zip-deflater-in-net
webRequest.AutomaticDecompression = DecompressionMethods.GZip;
webRequest.Credentials = request.NetworkCredential;
webRequest.Method = request.Method.ToString();
webRequest.UserAgent = UserAgentBuilder.UserAgent;
webRequest.KeepAlive = request.ConnectionKeepAlive;
webRequest.KeepAlive = false;
webRequest.AllowAutoRedirect = request.AllowAutoRedirect;
webRequest.ContentLength = 0;
webRequest.CookieContainer = cookies;
if (request.RequestTimeout != TimeSpan.Zero)
{
webRequest.Timeout = (int)Math.Ceiling(request.RequestTimeout.TotalMilliseconds);
}
if (request.Headers != null)
{
AddRequestHeaders(webRequest, request.Headers);
}
if (request.ContentData != null)
if (!request.Body.IsNullOrWhiteSpace())
{
webRequest.ContentLength = request.ContentData.Length;
var bytes = request.Headers.GetEncodingFromContentType().GetBytes(request.Body.ToCharArray());
webRequest.ContentLength = bytes.Length;
using (var writeStream = webRequest.GetRequestStream())
{
writeStream.Write(request.ContentData, 0, request.ContentData.Length);
writeStream.Write(bytes, 0, bytes.Length);
}
}
@@ -76,43 +75,45 @@ namespace NzbDrone.Common.Http.Dispatchers
switch (header.Key)
{
case "Accept":
webRequest.Accept = header.Value;
webRequest.Accept = header.Value.ToString();
break;
case "Connection":
webRequest.Connection = header.Value;
webRequest.Connection = header.Value.ToString();
break;
case "Content-Length":
webRequest.ContentLength = Convert.ToInt64(header.Value);
break;
case "Content-Type":
webRequest.ContentType = header.Value;
webRequest.ContentType = header.Value.ToString();
break;
case "Date":
webRequest.Date = HttpHeader.ParseDateTime(header.Value);
webRequest.Date = (DateTime)header.Value;
break;
case "Expect":
webRequest.Expect = header.Value;
webRequest.Expect = header.Value.ToString();
break;
case "Host":
webRequest.Host = header.Value;
webRequest.Host = header.Value.ToString();
break;
case "If-Modified-Since":
webRequest.IfModifiedSince = HttpHeader.ParseDateTime(header.Value);
webRequest.IfModifiedSince = (DateTime)header.Value;
break;
case "Range":
throw new NotImplementedException();
break;
case "Referer":
webRequest.Referer = header.Value;
webRequest.Referer = header.Value.ToString();
break;
case "Transfer-Encoding":
webRequest.TransferEncoding = header.Value;
webRequest.TransferEncoding = header.Value.ToString();
break;
case "User-Agent":
throw new NotSupportedException("User-Agent other than Sonarr not allowed.");
case "Proxy-Connection":
throw new NotImplementedException();
break;
default:
webRequest.Headers.Add(header.Key, header.Value);
webRequest.Headers.Add(header.Key, header.Value.ToString());
break;
}
}

View File

@@ -4,7 +4,7 @@ namespace NzbDrone.Common.Http
{
public sealed class HttpAccept
{
public static readonly HttpAccept Rss = new HttpAccept("application/rss+xml, text/rss+xml, application/xml, text/xml");
public static readonly HttpAccept Rss = new HttpAccept("application/rss+xml, text/rss+xml, text/xml");
public static readonly HttpAccept Json = new HttpAccept("application/json");
public static readonly HttpAccept Html = new HttpAccept("text/html");

View File

@@ -4,11 +4,9 @@ using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http.Dispatchers;
using NzbDrone.Common.TPL;
@@ -30,6 +28,7 @@ namespace NzbDrone.Common.Http
private readonly Logger _logger;
private readonly IRateLimitService _rateLimitService;
private readonly ICached<CookieContainer> _cookieContainerCache;
private readonly ICached<bool> _curlTLSFallbackCache;
private readonly List<IHttpRequestInterceptor> _requestInterceptors;
private readonly IHttpDispatcher _httpDispatcher;
@@ -74,18 +73,13 @@ namespace NzbDrone.Common.Http
stopWatch.Stop();
_logger.Trace("{0} ({1} ms)", response, stopWatch.ElapsedMilliseconds);
_logger.Trace("{0} ({1:n0} ms)", response, stopWatch.ElapsedMilliseconds);
foreach (var interceptor in _requestInterceptors)
{
response = interceptor.PostResponse(response);
}
if (request.LogResponseContent)
{
_logger.Trace("Response content ({0} bytes): {1}", response.ResponseData.Length, response.Content);
}
if (!RuntimeInfoBase.IsProduction &&
(response.StatusCode == HttpStatusCode.Moved ||
response.StatusCode == HttpStatusCode.MovedPermanently ||
@@ -130,7 +124,7 @@ namespace NzbDrone.Common.Http
}
}
var requestCookies = persistentCookieContainer.GetCookies((Uri)request.Url);
var requestCookies = persistentCookieContainer.GetCookies(request.Url);
var cookieContainer = new CookieContainer();
@@ -151,7 +145,7 @@ namespace NzbDrone.Common.Http
{
var persistentCookieContainer = _cookieContainerCache.Get("container", () => new CookieContainer());
var cookies = cookieContainer.GetCookies((Uri)request.Url);
var cookies = cookieContainer.GetCookies(request.Url);
persistentCookieContainer.Add(cookies);
}
@@ -183,7 +177,7 @@ namespace NzbDrone.Common.Http
}
catch (Exception e)
{
_logger.Warn(e, "Failed to get response from: " + url);
_logger.WarnException("Failed to get response from: " + url, e);
throw;
}
}

View File

@@ -8,7 +8,7 @@ namespace NzbDrone.Common.Http
public HttpResponse Response { get; private set; }
public HttpException(HttpRequest request, HttpResponse response)
: base(string.Format("HTTP request failed: [{0}:{1}] [{2}] at [{3}]", (int)response.StatusCode, response.StatusCode, request.Method, request.Url))
: base(string.Format("HTTP request failed: [{0}:{1}] [{2}] at [{3}]", (int)response.StatusCode, response.StatusCode, request.Method, request.Url.ToString()))
{
Request = request;
Response = response;

View File

@@ -1,14 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace NzbDrone.Common.Http
{
public class HttpFormData
{
public string Name { get; set; }
public string FileName { get; set; }
public byte[] ContentData { get; set; }
public string ContentType { get; set; }
}
}

View File

@@ -4,92 +4,37 @@ using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using NzbDrone.Common.Extensions;
using System.Collections;
using System.Globalization;
namespace NzbDrone.Common.Http
{
public class HttpHeader : NameValueCollection, IEnumerable<KeyValuePair<string, string>>, IEnumerable
public class HttpHeader : Dictionary<string, object>
{
public HttpHeader(NameValueCollection headers)
: base(headers)
public HttpHeader(NameValueCollection headers) : base(StringComparer.OrdinalIgnoreCase)
{
}
public HttpHeader()
{
}
public bool ContainsKey(string key)
{
key = key.ToLowerInvariant();
return AllKeys.Any(v => v.ToLowerInvariant() == key);
}
public string GetSingleValue(string key)
{
var values = GetValues(key);
if (values == null || values.Length == 0)
foreach (var key in headers.AllKeys)
{
return null;
}
if (values.Length > 1)
{
throw new ApplicationException(string.Format("Expected {0} to occur only once.", key));
}
return values[0];
}
protected T? GetSingleValue<T>(string key, Func<string, T> converter) where T : struct
{
var value = GetSingleValue(key);
if (value == null)
{
return null;
}
return converter(value);
}
protected void SetSingleValue(string key, string value)
{
if (value == null)
{
Remove(key);
}
else
{
Set(key, value);
this[key] = headers[key];
}
}
protected void SetSingleValue<T>(string key, T? value, Func<T, string> converter = null) where T : struct
public HttpHeader() : base(StringComparer.OrdinalIgnoreCase)
{
if (!value.HasValue)
{
Remove(key);
}
else if (converter != null)
{
Set(key, converter(value.Value));
}
else
{
Set(key, value.Value.ToString());
}
}
public long? ContentLength
{
get
{
return GetSingleValue("Content-Length", Convert.ToInt64);
if (!ContainsKey("Content-Length"))
{
return null;
}
return Convert.ToInt64(this["Content-Length"]);
}
set
{
SetSingleValue("Content-Length", value);
this["Content-Length"] = value;
}
}
@@ -97,11 +42,15 @@ namespace NzbDrone.Common.Http
{
get
{
return GetSingleValue("Content-Type");
if (!ContainsKey("Content-Type"))
{
return null;
}
return this["Content-Type"].ToString();
}
set
{
SetSingleValue("Content-Type", value);
this["Content-Type"] = value;
}
}
@@ -109,36 +58,25 @@ namespace NzbDrone.Common.Http
{
get
{
return GetSingleValue("Accept");
if (!ContainsKey("Accept"))
{
return null;
}
return this["Accept"].ToString();
}
set
{
SetSingleValue("Accept", value);
this["Accept"] = value;
}
}
public new IEnumerator<KeyValuePair<string, string>> GetEnumerator()
{
return AllKeys.SelectMany(GetValues, (k, c) => new KeyValuePair<string, string>(k, c)).ToList().GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return base.GetEnumerator();
}
public Encoding GetEncodingFromContentType()
{
return GetEncodingFromContentType(ContentType ?? string.Empty);
}
public static Encoding GetEncodingFromContentType(string contentType)
{
Encoding encoding = null;
if (contentType.IsNotNullOrWhiteSpace())
if (ContentType.IsNotNullOrWhiteSpace())
{
var charset = contentType.ToLowerInvariant()
var charset = ContentType.ToLowerInvariant()
.Split(';', '=', ' ')
.SkipWhile(v => v != "charset")
.Skip(1).FirstOrDefault();
@@ -161,18 +99,5 @@ namespace NzbDrone.Common.Http
return encoding;
}
public static DateTime ParseDateTime(string value)
{
return DateTime.ParseExact(value, "R", CultureInfo.InvariantCulture.DateTimeFormat, DateTimeStyles.AssumeUniversal);
}
public static List<KeyValuePair<string, string>> ParseCookies(string cookies)
{
return cookies.Split(';')
.Select(v => v.Trim().Split('='))
.Select(v => new KeyValuePair<string, string>(v[0], v[1]))
.ToList();
}
}
}

View File

@@ -53,7 +53,7 @@ namespace NzbDrone.Common.Http
}
catch (Exception e)
{
_logger.Warn(e, "Failed to get response from: " + url);
_logger.WarnException("Failed to get response from: " + url, e);
throw;
}
}

View File

@@ -1,19 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Common.Http
{
public class HttpRequest
{
private readonly Dictionary<string, string> _segments;
public HttpRequest(string url, HttpAccept httpAccept = null)
{
Url = new HttpUri(url);
UriBuilder = new UriBuilder(url);
Headers = new HttpHeader();
_segments = new Dictionary<string, string>();
AllowAutoRedirect = true;
Cookies = new Dictionary<string, string>();
@@ -28,54 +28,73 @@ namespace NzbDrone.Common.Http
}
}
public HttpUri Url { get; set; }
public UriBuilder UriBuilder { get; private set; }
public Uri Url
{
get
{
var uri = UriBuilder.Uri.ToString();
foreach (var segment in _segments)
{
uri = uri.Replace(segment.Key, segment.Value);
}
return new Uri(uri);
}
}
public HttpMethod Method { get; set; }
public HttpHeader Headers { get; set; }
public byte[] ContentData { get; set; }
public string ContentSummary { get; set; }
public string Body { get; set; }
public NetworkCredential NetworkCredential { get; set; }
public bool SuppressHttpError { get; set; }
public bool AllowAutoRedirect { get; set; }
public bool ConnectionKeepAlive { get; set; }
public bool LogResponseContent { get; set; }
public Dictionary<string, string> Cookies { get; private set; }
public bool StoreResponseCookie { get; set; }
public TimeSpan RequestTimeout { get; set; }
public TimeSpan RateLimit { get; set; }
public override string ToString()
{
return ToString();
}
public string ToString(bool includeMethod = true, bool includeSummary = true)
{
var builder = new StringBuilder();
if (includeMethod)
if (Body == null)
{
builder.AppendFormat("Req: [{0}] ", Method);
return string.Format("Req: [{0}] {1}", Method, Url);
}
builder.Append(Url);
return string.Format("Req: [{0}] {1} {2} {3}", Method, Url, Environment.NewLine, Body);
}
if (includeSummary && ContentSummary.IsNotNullOrWhiteSpace())
public void AddSegment(string segment, string value)
{
var key = "{" + segment + "}";
if (!UriBuilder.Uri.ToString().Contains(key))
{
builder.Append(": ");
builder.Append(ContentSummary);
throw new InvalidOperationException("Segment " + key +" is not defined in Uri");
}
return builder.ToString();
_segments.Add(key, value);
}
public void SetContent(byte[] data)
public void AddQueryParam(string segment, string value)
{
ContentData = data;
UriBuilder.SetQueryParam(segment, value);
}
public void SetContent(string data)
public void AddCookie(string key, string value)
{
var encoding = HttpHeader.GetEncodingFromContentType(Headers.ContentType);
ContentData = encoding.GetBytes(data);
Cookies[key] = value;
}
public void AddCookie(string cookies)
{
foreach (var pair in cookies.Split(';'))
{
var split = pair.Split('=');
Cookies[split[0].Trim()] = split[1].Trim();
}
}
}
}

View File

@@ -1,133 +1,33 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Common.Http
{
public class HttpRequestBuilder
{
public HttpMethod Method { get; set; }
public HttpAccept HttpAccept { get; set; }
public HttpUri BaseUrl { get; private set; }
public string ResourceUrl { get; set; }
public List<KeyValuePair<string, string>> QueryParams { get; private set; }
public List<KeyValuePair<string, string>> SuffixQueryParams { get; private set; }
public Dictionary<string, string> Segments { get; private set; }
public HttpHeader Headers { get; private set; }
public bool SuppressHttpError { get; set; }
public bool AllowAutoRedirect { get; set; }
public bool ConnectionKeepAlive { get; set; }
public bool LogResponseContent { get; set; }
public Uri BaseUri { get; private set; }
public bool SupressHttpError { get; set; }
public NetworkCredential NetworkCredential { get; set; }
public Dictionary<string, string> Cookies { get; private set; }
public List<HttpFormData> FormData { get; private set; }
public Action<HttpRequest> PostProcess { get; set; }
public HttpRequestBuilder(string baseUrl)
public HttpRequestBuilder(string baseUri)
{
BaseUrl = new HttpUri(baseUrl);
ResourceUrl = string.Empty;
Method = HttpMethod.GET;
QueryParams = new List<KeyValuePair<string, string>>();
SuffixQueryParams = new List<KeyValuePair<string, string>>();
Segments = new Dictionary<string, string>();
Headers = new HttpHeader();
Cookies = new Dictionary<string, string>();
FormData = new List<HttpFormData>();
BaseUri = new Uri(baseUri);
}
public HttpRequestBuilder(bool useHttps, string host, int port, string urlBase = null)
: this(BuildBaseUrl(useHttps, host, port, urlBase))
public virtual HttpRequest Build(string path)
{
}
public static string BuildBaseUrl(bool useHttps, string host, int port, string urlBase = null)
{
var protocol = useHttps ? "https" : "http";
if (urlBase.IsNotNullOrWhiteSpace() && !urlBase.StartsWith("/"))
if (BaseUri.ToString().EndsWith("/"))
{
urlBase = "/" + urlBase;
path = path.TrimStart('/');
}
return string.Format("{0}://{1}:{2}{3}", protocol, host, port, urlBase);
}
public virtual HttpRequestBuilder Clone()
{
var clone = MemberwiseClone() as HttpRequestBuilder;
clone.QueryParams = new List<KeyValuePair<string, string>>(clone.QueryParams);
clone.SuffixQueryParams = new List<KeyValuePair<string, string>>(clone.SuffixQueryParams);
clone.Segments = new Dictionary<string, string>(clone.Segments);
clone.Headers = new HttpHeader(clone.Headers);
clone.Cookies = new Dictionary<string, string>(clone.Cookies);
clone.FormData = new List<HttpFormData>(clone.FormData);
return clone;
}
protected virtual HttpUri CreateUri()
{
var url = BaseUrl.CombinePath(ResourceUrl).AddQueryParams(QueryParams.Concat(SuffixQueryParams));
if (Segments.Any())
var request = new HttpRequest(BaseUri + path)
{
var fullUri = url.FullUri;
foreach (var segment in Segments)
{
fullUri = fullUri.Replace(segment.Key, segment.Value);
}
url = new HttpUri(fullUri);
}
return url;
}
protected virtual HttpRequest CreateRequest()
{
return new HttpRequest(CreateUri().FullUri, HttpAccept);
}
protected virtual void Apply(HttpRequest request)
{
request.Method = Method;
request.SuppressHttpError = SuppressHttpError;
request.AllowAutoRedirect = AllowAutoRedirect;
request.ConnectionKeepAlive = ConnectionKeepAlive;
request.LogResponseContent = LogResponseContent;
if (NetworkCredential != null)
{
var authInfo = NetworkCredential.UserName + ":" + NetworkCredential.Password;
authInfo = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(authInfo));
request.Headers.Set("Authorization", "Basic " + authInfo);
}
foreach (var header in Headers)
{
request.Headers.Set(header.Key, header.Value);
}
foreach (var cookie in Cookies)
{
request.Cookies[cookie.Key] = cookie.Value;
}
ApplyFormData(request);
}
public virtual HttpRequest Build()
{
var request = CreateRequest();
Apply(request);
SuppressHttpError = SupressHttpError,
NetworkCredential = NetworkCredential
};
if (PostProcess != null)
{
@@ -136,237 +36,5 @@ namespace NzbDrone.Common.Http
return request;
}
public IHttpRequestBuilderFactory CreateFactory()
{
return new HttpRequestBuilderFactory(this);
}
protected virtual void ApplyFormData(HttpRequest request)
{
if (FormData.Empty()) return;
if (request.ContentData != null)
{
throw new ApplicationException("Cannot send HttpRequest Body and FormData simultaneously.");
}
var shouldSendAsMultipart = FormData.Any(v => v.ContentType != null || v.FileName != null || v.ContentData.Length > 1024);
if (shouldSendAsMultipart)
{
var boundary = "-----------------------------" + DateTime.Now.Ticks.ToString("x14");
var partBoundary = string.Format("--{0}\r\n", boundary);
var endBoundary = string.Format("--{0}--\r\n", boundary);
var bodyStream = new MemoryStream();
var summary = new StringBuilder();
using (var writer = new StreamWriter(bodyStream, new UTF8Encoding(false)))
{
foreach (var formData in FormData)
{
writer.Write(partBoundary);
writer.Write("Content-Disposition: form-data");
if (formData.Name.IsNotNullOrWhiteSpace()) writer.Write("; name=\"{0}\"", formData.Name);
if (formData.FileName.IsNotNullOrWhiteSpace()) writer.Write("; filename=\"{0}\"", formData.FileName);
writer.Write("\r\n");
if (formData.ContentType.IsNotNullOrWhiteSpace()) writer.Write("Content-Type: {0}\r\n", formData.ContentType);
writer.Write("\r\n");
writer.Flush();
bodyStream.Write(formData.ContentData, 0, formData.ContentData.Length);
writer.Write("\r\n");
if (formData.FileName.IsNotNullOrWhiteSpace())
{
summary.AppendFormat("\r\n{0}={1} ({2} bytes)", formData.Name, formData.FileName, formData.ContentData.Length);
}
else
{
summary.AppendFormat("\r\n{0}={1}", formData.Name, Encoding.UTF8.GetString(formData.ContentData));
}
}
writer.Write(endBoundary);
}
var body = bodyStream.ToArray();
// TODO: Scan through body to see if we have a boundary collision?
request.Headers.ContentType = "multipart/form-data; boundary=" + boundary;
request.SetContent(body);
if (request.ContentSummary == null)
{
request.ContentSummary = summary.ToString();
}
}
else
{
var parameters = FormData.Select(v => string.Format("{0}={1}", v.Name, Uri.EscapeDataString(Encoding.UTF8.GetString(v.ContentData))));
var urlencoded = string.Join("&", parameters);
var body = Encoding.UTF8.GetBytes(urlencoded);
request.Headers.ContentType = "application/x-www-form-urlencoded";
request.SetContent(body);
if (request.ContentSummary == null)
{
request.ContentSummary = urlencoded;
}
}
}
public virtual HttpRequestBuilder Resource(string resourceUrl)
{
if (!ResourceUrl.IsNotNullOrWhiteSpace() || resourceUrl.StartsWith("/"))
{
ResourceUrl = resourceUrl.TrimStart('/');
}
else
{
ResourceUrl = string.Format("{0}/{1}", ResourceUrl.TrimEnd('/'), resourceUrl);
}
return this;
}
public virtual HttpRequestBuilder KeepAlive(bool keepAlive = true)
{
ConnectionKeepAlive = keepAlive;
return this;
}
public virtual HttpRequestBuilder Post()
{
Method = HttpMethod.POST;
return this;
}
public virtual HttpRequestBuilder Accept(HttpAccept accept)
{
HttpAccept = accept;
return this;
}
public virtual HttpRequestBuilder SetHeader(string name, string value)
{
Headers.Set(name, value);
return this;
}
public virtual HttpRequestBuilder AddPrefixQueryParam(string key, object value, bool replace = false)
{
if (replace)
{
QueryParams.RemoveAll(v => v.Key == key);
SuffixQueryParams.RemoveAll(v => v.Key == key);
}
QueryParams.Insert(0, new KeyValuePair<string, string>(key, value.ToString()));
return this;
}
public virtual HttpRequestBuilder AddQueryParam(string key, object value, bool replace = false)
{
if (replace)
{
QueryParams.RemoveAll(v => v.Key == key);
SuffixQueryParams.RemoveAll(v => v.Key == key);
}
QueryParams.Add(key, value.ToString());
return this;
}
public virtual HttpRequestBuilder AddSuffixQueryParam(string key, object value, bool replace = false)
{
if (replace)
{
QueryParams.RemoveAll(v => v.Key == key);
SuffixQueryParams.RemoveAll(v => v.Key == key);
}
SuffixQueryParams.Add(new KeyValuePair<string, string>(key, value.ToString()));
return this;
}
public virtual HttpRequestBuilder SetSegment(string segment, string value, bool dontCheck = false)
{
var key = string.Concat("{", segment, "}");
if (!dontCheck && !CreateUri().ToString().Contains(key))
{
throw new InvalidOperationException(string.Format("Segment {0} is not defined in Uri", segment));
}
Segments[key] = value;
return this;
}
public virtual HttpRequestBuilder SetCookies(IEnumerable<KeyValuePair<string, string>> cookies)
{
foreach (var cookie in cookies)
{
Cookies[cookie.Key] = cookie.Value;
}
return this;
}
public virtual HttpRequestBuilder SetCookie(string key, string value)
{
Cookies[key] = value;
return this;
}
public virtual HttpRequestBuilder AddFormParameter(string key, object value)
{
if (Method != HttpMethod.POST)
{
throw new NotSupportedException("HttpRequest Method must be POST to add FormParameter.");
}
FormData.Add(new HttpFormData
{
Name = key,
ContentData = Encoding.UTF8.GetBytes(value.ToString())
});
return this;
}
public virtual HttpRequestBuilder AddFormUpload(string name, string fileName, byte[] data, string contentType = "application/octet-stream")
{
if (Method != HttpMethod.POST)
{
throw new NotSupportedException("HttpRequest Method must be POST to add FormUpload.");
}
FormData.Add(new HttpFormData
{
Name = name,
FileName = fileName,
ContentData = data,
ContentType = contentType
});
return this;
}
}
}

View File

@@ -1,36 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace NzbDrone.Common.Http
{
public interface IHttpRequestBuilderFactory
{
HttpRequestBuilder Create();
}
public class HttpRequestBuilderFactory : IHttpRequestBuilderFactory
{
private HttpRequestBuilder _rootBuilder;
public HttpRequestBuilderFactory(HttpRequestBuilder rootBuilder)
{
SetRootBuilder(rootBuilder);
}
protected HttpRequestBuilderFactory()
{
}
protected void SetRootBuilder(HttpRequestBuilder rootBuilder)
{
_rootBuilder = rootBuilder.Clone();
}
public HttpRequestBuilder Create()
{
return _rootBuilder.Clone();
}
}
}

View File

@@ -1,16 +1,11 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Text.RegularExpressions;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Serializer;
namespace NzbDrone.Common.Http
{
public class HttpResponse
{
private static readonly Regex RegexSetCookie = new Regex("^(.*?)=(.*?)(?:;|$)", RegexOptions.Compiled);
public HttpResponse(HttpRequest request, HttpHeader headers, byte[] binaryData, HttpStatusCode statusCode = HttpStatusCode.OK)
{
Request = request;
@@ -57,31 +52,11 @@ namespace NzbDrone.Common.Http
}
}
public Dictionary<string, string> GetCookies()
{
var result = new Dictionary<string, string>();
var setCookieHeaders = Headers.GetValues("Set-Cookie");
if (setCookieHeaders != null)
{
foreach (var cookie in setCookieHeaders)
{
var match = RegexSetCookie.Match(cookie);
if (match.Success)
{
result[match.Groups[1].Value] = match.Groups[2].Value;
}
}
}
return result;
}
public override string ToString()
{
var result = string.Format("Res: [{0}] {1}: {2}.{3}", Request.Method, Request.Url, (int)StatusCode, StatusCode);
var result = string.Format("Res: [{0}] {1} : {2}.{3}", Request.Method, Request.Url, (int)StatusCode, StatusCode);
if (HasHttpError && Headers.ContentType.IsNotNullOrWhiteSpace() && !Headers.ContentType.Equals("text/html", StringComparison.InvariantCultureIgnoreCase))
if (HasHttpError && !Headers.ContentType.Equals("text/html", StringComparison.InvariantCultureIgnoreCase))
{
result += Environment.NewLine + Content;
}

View File

@@ -1,272 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Common.Http
{
public class HttpUri : IEquatable<HttpUri>
{
private static readonly Regex RegexUri = new Regex(@"^(?:(?<scheme>[a-z]+):)?(?://(?<host>[-A-Z0-9.]+)(?::(?<port>[0-9]{1,5}))?)?(?<path>(?:(?:(?<=^)|/)[^/?#\r\n]+)+/?|/)?(?:\?(?<query>[^#\r\n]*))?(?:\#(?<fragment>.*))?$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private readonly string _uri;
public string FullUri { get { return _uri; } }
public HttpUri(string uri)
{
_uri = uri ?? string.Empty;
Parse();
}
public HttpUri(string scheme, string host, int? port, string path, string query, string fragment)
{
StringBuilder builder = new StringBuilder();
if (scheme.IsNotNullOrWhiteSpace())
{
builder.Append(scheme);
builder.Append(":");
}
if (host.IsNotNullOrWhiteSpace())
{
builder.Append("//");
builder.Append(host);
if (port.HasValue)
{
builder.Append(":");
builder.Append(port);
}
}
if (path.IsNotNullOrWhiteSpace())
{
if (host.IsNotNullOrWhiteSpace() || path.StartsWith("/"))
{
builder.Append('/');
}
builder.Append(path.TrimStart('/'));
}
if (query.IsNotNullOrWhiteSpace())
{
builder.Append('?');
builder.Append(query);
}
if (fragment.IsNotNullOrWhiteSpace())
{
builder.Append('#');
builder.Append(fragment);
}
_uri = builder.ToString();
Parse();
}
private void Parse()
{
var match = RegexUri.Match(_uri);
var scheme = match.Groups["scheme"];
var host = match.Groups["host"];
var port = match.Groups["port"];
var path = match.Groups["path"];
var query = match.Groups["query"];
var fragment = match.Groups["fragment"];
if (!match.Success || scheme.Success && !host.Success && path.Success)
{
throw new ArgumentException("Uri didn't match expected pattern: " + _uri);
}
Scheme = scheme.Value;
Host = host.Value;
Port = port.Success ? (int?)int.Parse(port.Value) : null;
Path = path.Value;
Query = query.Value;
Fragment = fragment.Value;
}
public string Scheme { get; private set; }
public string Host { get; private set; }
public int? Port { get; private set; }
public string Path { get; private set; }
public string Query { get; private set; }
public string Fragment { get; private set; }
private IList<KeyValuePair<string, string>> _queryParams;
private IList<KeyValuePair<string, string>> QueryParams
{
get
{
if (_queryParams == null)
{
var dict = new List<KeyValuePair<string, string>>();
if (Query.IsNotNullOrWhiteSpace())
{
foreach (var pair in Query.Split('&'))
{
var split = pair.Split(new[] { '=' }, 2);
if (split.Length == 1)
{
dict.Add(new KeyValuePair<string, string>(Uri.UnescapeDataString(split[0]), null));
}
else
{
dict.Add(new KeyValuePair<string, string>(Uri.UnescapeDataString(split[0]), Uri.UnescapeDataString(split[1])));
}
}
}
_queryParams = dict.AsReadOnly();
}
return _queryParams;
}
}
public HttpUri CombinePath(string path)
{
return new HttpUri(Scheme, Host, Port, CombinePath(Path, path), Query, Fragment);
}
private static string CombinePath(string basePath, string relativePath)
{
if (relativePath.IsNullOrWhiteSpace())
{
return basePath;
}
if (basePath.IsNullOrWhiteSpace())
{
return relativePath;
}
return basePath.TrimEnd('/') + "/" + relativePath.TrimStart('/');
}
private static string CombineRelativePath(string basePath, string relativePath)
{
if (relativePath.IsNullOrWhiteSpace())
{
return basePath;
}
if (relativePath.StartsWith("/"))
{
return relativePath;
}
var baseSlashIndex = basePath.LastIndexOf('/');
if (baseSlashIndex >= 0)
{
return basePath.Substring(0, baseSlashIndex) + "/" + relativePath;
}
return relativePath;
}
public HttpUri SetQuery(string query)
{
return new HttpUri(Scheme, Host, Port, Path, query, Fragment);
}
public HttpUri AddQueryParam(string key, object value)
{
var newQuery = string.Concat(Uri.EscapeDataString(key), "=", Uri.EscapeDataString(value.ToString()));
if (Query.IsNotNullOrWhiteSpace())
{
newQuery = string.Concat(Query, "&", newQuery);
}
return SetQuery(newQuery);
}
public HttpUri AddQueryParams(IEnumerable<KeyValuePair<string, string>> queryParams)
{
var builder = new StringBuilder();
builder.Append(Query);
foreach (var pair in queryParams)
{
if (builder.Length != 0)
{
builder.Append("&");
}
builder.Append(Uri.EscapeDataString(pair.Key));
builder.Append("=");
builder.Append(Uri.EscapeDataString(pair.Value));
}
return SetQuery(builder.ToString());
}
public override int GetHashCode()
{
return _uri.GetHashCode();
}
public override string ToString()
{
return _uri;
}
public override bool Equals(object obj)
{
if (obj is string)
{
return _uri.Equals((string)obj);
}
else if (obj is Uri)
{
return _uri.Equals(((Uri)obj).OriginalString);
}
else
{
return Equals(obj as HttpUri);
}
}
public bool Equals(HttpUri other)
{
if (object.ReferenceEquals(other, null)) return false;
return _uri.Equals(other._uri);
}
public static explicit operator Uri(HttpUri url)
{
return new Uri(url.FullUri);
}
public static HttpUri operator +(HttpUri baseUrl, HttpUri relativeUrl)
{
if (relativeUrl.Scheme.IsNotNullOrWhiteSpace())
{
return relativeUrl;
}
if (relativeUrl.Host.IsNotNullOrWhiteSpace())
{
return new HttpUri(baseUrl.Scheme, relativeUrl.Host, relativeUrl.Port, relativeUrl.Path, relativeUrl.Query, relativeUrl.Fragment);
}
if (relativeUrl.Path.IsNotNullOrWhiteSpace())
{
return new HttpUri(baseUrl.Scheme, baseUrl.Host, baseUrl.Port, CombineRelativePath(baseUrl.Path, relativeUrl.Path), relativeUrl.Query, relativeUrl.Fragment);
}
return new HttpUri(baseUrl.Scheme, baseUrl.Host, baseUrl.Port, baseUrl.Path, relativeUrl.Query, relativeUrl.Fragment);
}
}
}

View File

@@ -1,94 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using NzbDrone.Common.Serializer;
namespace NzbDrone.Common.Http
{
public class JsonRpcRequestBuilder : HttpRequestBuilder
{
public static HttpAccept JsonRpcHttpAccept = new HttpAccept("application/json-rpc, application/json");
public static string JsonRpcContentType = "application/json-rpc";
public string Method { get; private set; }
public List<object> Parameters { get; private set; }
public string JsonMethod { get; private set; }
public List<object> JsonParameters { get; private set; }
public JsonRpcRequestBuilder(string baseUrl)
: base(baseUrl)
public JsonRpcRequestBuilder(string baseUri, string method, IEnumerable<object> parameters)
: base (baseUri)
{
Method = HttpMethod.POST;
JsonParameters = new List<object>();
Method = method;
Parameters = parameters.ToList();
}
public JsonRpcRequestBuilder(string baseUrl, string method, IEnumerable<object> parameters)
: base (baseUrl)
public override HttpRequest Build(string path)
{
Method = HttpMethod.POST;
JsonMethod = method;
JsonParameters = parameters.ToList();
}
public override HttpRequestBuilder Clone()
{
var clone = base.Clone() as JsonRpcRequestBuilder;
clone.JsonParameters = new List<object>(JsonParameters);
return clone;
}
public JsonRpcRequestBuilder Call(string method, params object[] parameters)
{
var clone = Clone() as JsonRpcRequestBuilder;
clone.JsonMethod = method;
clone.JsonParameters = parameters.ToList();
return clone;
}
protected override void Apply(HttpRequest request)
{
base.Apply(request);
request.Headers.ContentType = JsonRpcContentType;
var parameterData = new object[JsonParameters.Count];
var parameterSummary = new string[JsonParameters.Count];
for (var i = 0; i < JsonParameters.Count; i++)
{
ConvertParameter(JsonParameters[i], out parameterData[i], out parameterSummary[i]);
}
var request = base.Build(path);
request.Method = HttpMethod.POST;
request.Headers.Accept = "application/json-rpc, application/json";
request.Headers.ContentType = "application/json-rpc";
var message = new Dictionary<string, object>();
message["jsonrpc"] = "2.0";
message["method"] = JsonMethod;
message["params"] = parameterData;
message["method"] = Method;
message["params"] = Parameters;
message["id"] = CreateNextId();
request.SetContent(message.ToJson());
request.Body = message.ToJson();
if (request.ContentSummary == null)
{
request.ContentSummary = string.Format("{0}({1})", JsonMethod, string.Join(", ", parameterSummary));
}
}
private void ConvertParameter(object value, out object data, out string summary)
{
if (value is byte[])
{
data = Convert.ToBase64String(value as byte[]);
summary = string.Format("[blob {0} bytes]", (value as byte[]).Length);
}
else if (value is Array && ((Array)value).Length > 0)
{
data = value;
summary = "[...]";
}
else
{
data = value;
summary = JsonConvert.SerializeObject(data);
}
return request;
}
public string CreateNextId()

View File

@@ -1,5 +1,4 @@
using System;
using Newtonsoft.Json.Linq;
namespace NzbDrone.Common.Http
{
@@ -7,6 +6,6 @@ namespace NzbDrone.Common.Http
{
public string Id { get; set; }
public T Result { get; set; }
public JToken Error { get; set; }
public object Error { get; set; }
}
}

View File

@@ -0,0 +1,20 @@
using System;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Common.Http
{
public static class UriExtensions
{
public static void SetQueryParam(this UriBuilder uriBuilder, string key, object value)
{
var query = uriBuilder.Query;
if (query.IsNotNullOrWhiteSpace())
{
query += "&";
}
uriBuilder.Query = query.Trim('?') + key + "=" + Uri.EscapeDataString(value.ToString());
}
}
}

View File

@@ -50,7 +50,7 @@ namespace NzbDrone.Common.Instrumentation
var value = m.Value;
foreach (var capture in m.Groups["secret"].Captures.OfType<Capture>().Reverse())
{
value = value.Replace(capture.Index - m.Index, capture.Length, "(removed)");
value = value.Replace(capture.Index - m.Index, capture.Length, "<removed>");
}
return value;

View File

@@ -19,7 +19,7 @@ namespace NzbDrone.Common.Instrumentation
var exception = e.Exception;
Console.WriteLine("Task Error: {0}", exception);
Logger.Error(exception, "Task Error: " + exception.Message);
Logger.Error("Task Error: " + exception.Message, exception);
}
private static void HandleAppDomainException(object sender, UnhandledExceptionEventArgs e)
@@ -40,13 +40,13 @@ namespace NzbDrone.Common.Instrumentation
if (exception is TypeInitializationException && exception.InnerException is DllNotFoundException ||
exception is DllNotFoundException)
{
Logger.Debug(exception, "Minor Fail: " + exception.Message);
Logger.DebugException("Minor Fail: " + exception.Message, exception);
return;
}
}
Console.WriteLine("EPIC FAIL: {0}", exception);
Logger.Fatal(exception, "EPIC FAIL: " + exception.Message);
Logger.FatalException("EPIC FAIL: " + exception.Message, exception);
}
}
}

View File

@@ -76,7 +76,7 @@ namespace NzbDrone.Common.Instrumentation
{
DebuggerTarget target = new DebuggerTarget();
target.Name = "debuggerLogger";
target.Layout = "[${level}] [${threadid}] ${logger}: ${message} ${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}}";
target.Layout = "[${level}] [${threadid}] ${logger}: ${message} ${onexception:inner=${newline}${newline}${exception:format=ToString}${newline}}";
var loggingRule = new LoggingRule("*", LogLevel.Trace, target);
LogManager.Configuration.AddTarget("debugger", target);
@@ -91,7 +91,7 @@ namespace NzbDrone.Common.Instrumentation
var coloredConsoleTarget = new ColoredConsoleTarget();
coloredConsoleTarget.Name = "consoleLogger";
coloredConsoleTarget.Layout = "[${level}] ${logger}: ${message} ${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}}";
coloredConsoleTarget.Layout = "[${level}] ${logger}: ${message} ${onexception:inner=${newline}${newline}${exception:format=ToString}${newline}}";
var loggingRule = new LoggingRule("*", level, coloredConsoleTarget);
@@ -99,38 +99,29 @@ namespace NzbDrone.Common.Instrumentation
LogManager.Configuration.LoggingRules.Add(loggingRule);
}
const string FILE_LOG_LAYOUT = @"${date:format=yy-M-d HH\:mm\:ss.f}|${level}|${logger}|${message}${onexception:inner=${newline}${newline}[v${assembly-version}] ${exception:format=ToString}${newline}}";
const string FILE_LOG_LAYOUT = @"${date:format=yy-M-d HH\:mm\:ss.f}|${level}|${logger}|${message}${onexception:inner=${newline}${newline}${exception:format=ToString}${newline}}";
private static void RegisterAppFile(IAppFolderInfo appFolderInfo)
{
RegisterAppFile(appFolderInfo, "appFileInfo", "sonarr.txt", 5);
RegisterAppFile(appFolderInfo, "appFileDebug", "sonarr.debug.txt", 50);
RegisterAppFile(appFolderInfo, "appFileTrace", "sonarr.trace.txt", 50);
}
private static LoggingRule RegisterAppFile(IAppFolderInfo appFolderInfo, string name, string fileName, int maxArchiveFiles)
{
var fileTarget = new NzbDroneFileTarget();
fileTarget.Name = name;
fileTarget.FileName = Path.Combine(appFolderInfo.GetLogFolder(), fileName);
fileTarget.Name = "rollingFileLogger";
fileTarget.FileName = Path.Combine(appFolderInfo.GetLogFolder(), "nzbdrone.txt");
fileTarget.AutoFlush = true;
fileTarget.KeepFileOpen = false;
fileTarget.ConcurrentWrites = false;
fileTarget.ConcurrentWriteAttemptDelay = 50;
fileTarget.ConcurrentWriteAttempts = 10;
fileTarget.ArchiveAboveSize = 1024000;
fileTarget.MaxArchiveFiles = maxArchiveFiles;
fileTarget.MaxArchiveFiles = 5;
fileTarget.EnableFileDelete = true;
fileTarget.ArchiveNumbering = ArchiveNumberingMode.Rolling;
fileTarget.Layout = FILE_LOG_LAYOUT;
var loggingRule = new LoggingRule("*", LogLevel.Trace, fileTarget);
LogManager.Configuration.AddTarget(name, fileTarget);
LogManager.Configuration.AddTarget("appfile", fileTarget);
LogManager.Configuration.LoggingRules.Add(loggingRule);
return loggingRule;
}
private static void RegisterUpdateFile(IAppFolderInfo appFolderInfo)

View File

@@ -43,10 +43,6 @@
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Newtonsoft.Json.6.0.6\lib\net40\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.3.0-rc1\lib\net40\NLog.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Configuration.Install" />
@@ -57,6 +53,9 @@
<Reference Include="ICSharpCode.SharpZipLib">
<HintPath>..\packages\ICSharpCode.SharpZipLib.Patched.0.86.5\lib\net20\ICSharpCode.SharpZipLib.dll</HintPath>
</Reference>
<Reference Include="NLog">
<HintPath>..\packages\NLog.2.1.0\lib\net40\NLog.dll</HintPath>
</Reference>
<Reference Include="System.Xml" />
<Reference Include="System.Xml.Linq" />
</ItemGroup>
@@ -64,10 +63,8 @@
<Compile Include="ArchiveService.cs" />
<Compile Include="Cache\Cached.cs" />
<Compile Include="Cache\CacheManager.cs" />
<Compile Include="Cache\CachedDictionary.cs" />
<Compile Include="Cache\ICached.cs" />
<Compile Include="Cache\ICachedDictionary.cs" />
<Compile Include="Cloud\SonarrCloudRequestBuilder.cs" />
<Compile Include="Cloud\CloudClient.cs" />
<Compile Include="Composition\Container.cs" />
<Compile Include="Composition\ContainerBuilderBase.cs" />
<Compile Include="Composition\IContainer.cs" />
@@ -75,8 +72,6 @@
<Compile Include="ConvertBase32.cs" />
<Compile Include="Crypto\HashProvider.cs" />
<Compile Include="Disk\FileSystemLookupService.cs" />
<Compile Include="Disk\DriveInfoMount.cs" />
<Compile Include="Disk\IMount.cs" />
<Compile Include="Disk\RelativeFileSystemModel.cs" />
<Compile Include="Disk\FileSystemModel.cs" />
<Compile Include="Disk\FileSystemResult.cs" />
@@ -156,13 +151,11 @@
<Compile Include="Http\HttpAccept.cs" />
<Compile Include="Http\HttpClient.cs" />
<Compile Include="Http\HttpException.cs" />
<Compile Include="Http\HttpFormData.cs" />
<Compile Include="Http\HttpHeader.cs" />
<Compile Include="Http\HttpMethod.cs" />
<Compile Include="Http\HttpProvider.cs" />
<Compile Include="Http\HttpRequest.cs" />
<Compile Include="Http\HttpResponse.cs" />
<Compile Include="Http\HttpUri.cs" />
<Compile Include="Http\IHttpRequestInterceptor.cs" />
<Compile Include="Http\JsonRpcRequestBuilder.cs" />
<Compile Include="Http\JsonRpcResponse.cs" />
@@ -170,8 +163,8 @@
<SubType>Component</SubType>
</Compile>
<Compile Include="Http\HttpRequestBuilder.cs" />
<Compile Include="Http\HttpRequestBuilderFactory.cs" />
<Compile Include="Http\TooManyRequestsException.cs" />
<Compile Include="Http\UriExtensions.cs" />
<Compile Include="Extensions\IEnumerableExtensions.cs" />
<Compile Include="Http\UserAgentBuilder.cs" />
<Compile Include="Instrumentation\CleanseLogMessage.cs" />
@@ -197,7 +190,6 @@
<Compile Include="Reflection\ReflectionExtensions.cs" />
<Compile Include="Extensions\ResourceExtensions.cs" />
<Compile Include="Security\X509CertificateValidationPolicy.cs" />
<Compile Include="Serializer\HttpUriConverter.cs" />
<Compile Include="Serializer\IntConverter.cs" />
<Compile Include="Serializer\Json.cs" />
<Compile Include="ServiceFactory.cs" />

View File

@@ -37,7 +37,7 @@ namespace NzbDrone.Common.Processes
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to write PID file: " + filename);
_logger.Error("Unable to write PID file: " + filename, ex);
throw;
}
}

View File

@@ -292,7 +292,7 @@ namespace NzbDrone.Common.Processes
}
catch (Win32Exception e)
{
_logger.Warn(e, "Couldn't get process info for " + process.ProcessName);
_logger.WarnException("Couldn't get process info for " + process.ProcessName, e);
}
return processInfo;

View File

@@ -1,31 +0,0 @@
using System;
using Newtonsoft.Json;
using NzbDrone.Common.Http;
namespace NzbDrone.Common.Serializer
{
public class HttpUriConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value == null)
{
writer.WriteNull();
}
else if (value is HttpUri)
{
writer.WriteValue((value as HttpUri).FullUri);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
return new HttpUri(reader.ReadAsString());
}
public override bool CanConvert(Type objectType)
{
return objectType == typeof(HttpUri);
}
}
}

View File

@@ -26,7 +26,6 @@ namespace NzbDrone.Common.Serializer
SerializerSetting.Converters.Add(new StringEnumConverter { CamelCaseText = true });
//SerializerSetting.Converters.Add(new IntConverter());
SerializerSetting.Converters.Add(new VersionConverter());
SerializerSetting.Converters.Add(new HttpUriConverter());
Serializer = JsonSerializer.Create(SerializerSetting);

View File

@@ -17,7 +17,7 @@ namespace NzbDrone.Common.TPL
var aggregateException = t.Exception.Flatten();
foreach (var exception in aggregateException.InnerExceptions)
{
Logger.Error(exception, "Task Error");
Logger.ErrorException("Task Error", exception);
}
}
}, TaskContinuationOptions.OnlyOnFaulted);

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="ICSharpCode.SharpZipLib.Patched" version="0.86.5" targetFramework="net40" />
<package id="Newtonsoft.Json" version="6.0.6" targetFramework="net40" />
<package id="NLog" version="4.3.0-rc1" targetFramework="net40" />
<package id="NLog" version="2.1.0" targetFramework="net40" />
<package id="ICSharpCode.SharpZipLib.Patched" version="0.86.5" targetFramework="net40" />
</packages>

View File

@@ -32,7 +32,7 @@ namespace NzbDrone.Console
{
System.Console.WriteLine("");
System.Console.WriteLine("");
Logger.Fatal(e, "EPIC FAIL!");
Logger.FatalException("EPIC FAIL!", e);
System.Console.WriteLine("Press any key to exit...");
System.Console.ReadLine();
Environment.Exit(1);

View File

@@ -75,12 +75,11 @@
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Newtonsoft.Json.6.0.6\lib\net40\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.3.0-rc1\lib\net40\NLog.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="NLog">
<HintPath>..\packages\NLog.2.1.0\lib\net40\NLog.dll</HintPath>
</Reference>
<Reference Include="Owin">
<HintPath>..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
</Reference>
@@ -151,4 +150,4 @@
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>

View File

@@ -3,6 +3,6 @@
<package id="Microsoft.Owin" version="2.1.0" targetFramework="net40" />
<package id="Microsoft.Owin.Hosting" version="2.1.0" targetFramework="net40" />
<package id="Newtonsoft.Json" version="6.0.6" targetFramework="net40" />
<package id="NLog" version="4.3.0-rc1" targetFramework="net40" />
<package id="NLog" version="2.1.0" targetFramework="net40" />
<package id="Owin" version="1.0" targetFramework="net40" />
</packages>
</packages>

View File

@@ -124,7 +124,7 @@ namespace NzbDrone.Core.Test.DataAugmentation.Scene
.Returns(Builder<SceneMapping>.CreateListOfSize(1).Build());
Subject.Execute(new UpdateSceneMappingCommand());
Subject.HandleAsync(new ApplicationStartedEvent());
Mocker.GetMock<ISceneMappingRepository>()
.Verify(v => v.All(), Times.Once());
@@ -187,15 +187,16 @@ namespace NzbDrone.Core.Test.DataAugmentation.Scene
{
var mappings = new List<SceneMapping>
{
new SceneMapping { Title = "Working!!", ParseTerm = "working", SearchTerm = "Working!!", TvdbId = 100, SceneSeasonNumber = 1 },
new SceneMapping { Title = "Working`!!", ParseTerm = "working", SearchTerm = "Working`!!", TvdbId = 100, SceneSeasonNumber = 2 },
new SceneMapping { Title = "Working!!!", ParseTerm = "working", SearchTerm = "Working!!!", TvdbId = 100, SceneSeasonNumber = 3 },
new SceneMapping { Title = "Working!!", ParseTerm = "working", SearchTerm = "Working!!", TvdbId = 100, SeasonNumber = -1 },
new SceneMapping { Title = "Working!!", ParseTerm = "working", SearchTerm = "Working!!", TvdbId = 100, SeasonNumber = 1 },
new SceneMapping { Title = "Working`!!", ParseTerm = "working", SearchTerm = "Working`!!", TvdbId = 100, SeasonNumber = 2 },
new SceneMapping { Title = "Working!!!", ParseTerm = "working", SearchTerm = "Working!!!", TvdbId = 100, SeasonNumber = 3 },
};
Mocker.GetMock<ISceneMappingRepository>().Setup(c => c.All()).Returns(mappings);
var tvdbId = Subject.FindTvdbId(parseTitle);
var seasonNumber = Subject.GetSceneSeasonNumber(parseTitle);
var seasonNumber = Subject.GetSeasonNumber(parseTitle);
tvdbId.Should().Be(100);
seasonNumber.Should().Be(expectedSeasonNumber);
@@ -216,7 +217,7 @@ namespace NzbDrone.Core.Test.DataAugmentation.Scene
foreach (var sceneMapping in _fakeMappings)
{
Subject.GetSceneNamesBySeasonNumbers(sceneMapping.TvdbId, _fakeMappings.Select(m => m.SeasonNumber.Value)).Should().Contain(sceneMapping.SearchTerm);
Subject.GetSceneNames(sceneMapping.TvdbId, _fakeMappings.Select(m => m.SeasonNumber)).Should().Contain(sceneMapping.SearchTerm);
Subject.FindTvdbId(sceneMapping.ParseTerm).Should().Be(sceneMapping.TvdbId);
}
}

View File

@@ -11,7 +11,6 @@ using NzbDrone.Core.DataAugmentation.Xem.Model;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Tv.Events;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.DataAugmentation.SceneNumbering
{
@@ -145,25 +144,6 @@ namespace NzbDrone.Core.Test.DataAugmentation.SceneNumbering
Mocker.GetMock<ISeriesService>()
.Verify(v => v.UpdateSeries(It.IsAny<Series>()), Times.Never());
ExceptionVerification.ExpectedWarns(1);
}
[Test]
public void should_not_clear_scenenumbering_if_thexem_throws()
{
GivenExistingMapping();
Mocker.GetMock<IXemProxy>()
.Setup(v => v.GetXemSeriesIds())
.Throws(new InvalidOperationException());
Subject.Handle(new SeriesUpdatedEvent(_series));
Mocker.GetMock<ISeriesService>()
.Verify(v => v.UpdateSeries(It.IsAny<Series>()), Times.Never());
ExceptionVerification.ExpectedWarns(1);
}
[Test]

View File

@@ -2,7 +2,11 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Datastore.Migration;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Profiles.Delay;
using NzbDrone.Core.Tags;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Test.Datastore.Migration
{
@@ -12,7 +16,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration
[Test]
public void should_migrate_old_delays()
{
var db = WithMigrationTestDb(c =>
WithTestDb(c =>
{
c.Insert.IntoTable("Profiles").Row(new
{
@@ -31,10 +35,10 @@ namespace NzbDrone.Core.Test.Datastore.Migration
});
});
var allProfiles = db.Query<DelayProfile70>("SELECT * FROM DelayProfiles");
var allProfiles = Mocker.Resolve<DelayProfileRepository>().All().ToList();
allProfiles.Should().HaveCount(3);
allProfiles.Should().OnlyContain(c => c.PreferredProtocol == 1);
allProfiles.Should().OnlyContain(c => c.PreferredProtocol == DownloadProtocol.Usenet);
allProfiles.Should().OnlyContain(c => c.TorrentDelay == 0);
allProfiles.Should().Contain(c => c.UsenetDelay == 60);
allProfiles.Should().Contain(c => c.UsenetDelay == 120);
@@ -43,18 +47,17 @@ namespace NzbDrone.Core.Test.Datastore.Migration
[Test]
public void should_create_tag_for_delay_profile()
{
var db = WithMigrationTestDb(c =>
{
WithTestDb(c =>
c.Insert.IntoTable("Profiles").Row(new
{
GrabDelay = 1,
Name = "OneHour",
Cutoff = 0,
Items = "[]"
});
});
})
);
var tags = db.Query<Tag69>("SELECT * FROM Tags");
var tags = Mocker.Resolve<TagRepository>().All().ToList();
tags.Should().HaveCount(1);
tags.First().Label.Should().Be("delay-60");
@@ -63,7 +66,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration
[Test]
public void should_add_tag_to_series_that_had_a_profile_with_delay_attached()
{
var db = WithMigrationTestDb(c =>
WithTestDb(c =>
{
c.Insert.IntoTable("Profiles").Row(new
{
@@ -92,11 +95,12 @@ namespace NzbDrone.Core.Test.Datastore.Migration
});
});
var tag = db.Query<Tag69>("SELECT Id, Label FROM Tags").Single();
var series = db.Query<Series69>("SELECT Tags FROM Series");
var tag = Mocker.Resolve<TagRepository>().All().ToList().First();
var series = Mocker.Resolve<SeriesRepository>().All().ToList();
series.Should().HaveCount(1);
series.First().Tags.Should().BeEquivalentTo(tag.Id);
series.First().Tags.Should().HaveCount(1);
series.First().Tags.First().Should().Be(tag.Id);
}
}
}

View File

@@ -2,7 +2,12 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Datastore.Migration;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Profiles.Delay;
using NzbDrone.Core.Tags;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Test.Datastore.Migration
{
@@ -12,11 +17,10 @@ namespace NzbDrone.Core.Test.Datastore.Migration
[Test]
public void should_add_unknown_to_old_profile()
{
var db = WithMigrationTestDb(c =>
WithTestDb(c =>
{
c.Insert.IntoTable("Profiles").Row(new
{
Id = 0,
Name = "SDTV",
Cutoff = 1,
Items = "[ { \"quality\": 1, \"allowed\": true } ]",
@@ -24,12 +28,11 @@ namespace NzbDrone.Core.Test.Datastore.Migration
});
});
var profiles = db.Query<Profile70>("SELECT Items FROM Profiles LIMIT 1");
var allProfiles = Mocker.Resolve<ProfileRepository>().All().ToList();
var items = profiles.First().Items;
items.Should().HaveCount(2);
items.First().Quality.Should().Be(0);
items.First().Allowed.Should().Be(false);
allProfiles.Should().HaveCount(1);
allProfiles.First().Items.Should().HaveCount(2);
allProfiles.First().Items.Should().Contain(i => i.Quality.Id == 0 && i.Allowed == false);
}
}
}

View File

@@ -6,6 +6,7 @@ using FluentMigrator;
using NUnit.Framework;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Datastore.Migration;
using NzbDrone.Core.History;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Datastore.Migration
@@ -16,7 +17,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration
[Test]
public void should_move_grab_id_from_date_to_columns()
{
var db = WithMigrationTestDb(c =>
WithTestDb(c =>
{
InsertHistory(c, new Dictionary<string, string>
{
@@ -32,19 +33,19 @@ namespace NzbDrone.Core.Test.Datastore.Migration
});
var history = db.Query<History72>("SELECT DownloadId, Data FROM History");
var allProfiles = Mocker.Resolve<HistoryRepository>().All().ToList();
history.Should().HaveCount(2);
history.Should().NotContain(c => c.Data.ContainsKey("downloadClientId"));
history.Should().Contain(c => c.DownloadId == "123");
history.Should().Contain(c => c.DownloadId == "abc");
allProfiles.Should().HaveCount(2);
allProfiles.Should().NotContain(c => c.Data.ContainsKey("downloadClientId"));
allProfiles.Should().Contain(c => c.DownloadId == "123");
allProfiles.Should().Contain(c => c.DownloadId == "abc");
}
[Test]
public void should_leave_items_with_no_grabid()
{
var db = WithMigrationTestDb(c =>
WithTestDb(c =>
{
InsertHistory(c, new Dictionary<string, string>
{
@@ -59,18 +60,18 @@ namespace NzbDrone.Core.Test.Datastore.Migration
});
var history = db.Query<History72>("SELECT DownloadId, Data FROM History");
var allProfiles = Mocker.Resolve<HistoryRepository>().All().ToList();
history.Should().HaveCount(2);
history.Should().NotContain(c => c.Data.ContainsKey("downloadClientId"));
history.Should().Contain(c => c.DownloadId == "123");
history.Should().Contain(c => c.DownloadId == null);
allProfiles.Should().HaveCount(2);
allProfiles.Should().NotContain(c => c.Data.ContainsKey("downloadClientId"));
allProfiles.Should().Contain(c => c.DownloadId == "123");
allProfiles.Should().Contain(c => c.DownloadId == null);
}
[Test]
public void should_leave_other_data()
{
var db = WithMigrationTestDb(c =>
WithTestDb(c =>
{
InsertHistory(c, new Dictionary<string, string>
{
@@ -80,15 +81,16 @@ namespace NzbDrone.Core.Test.Datastore.Migration
});
});
var history = db.Query<History72>("SELECT DownloadId, Data FROM History").Single();
var allProfiles = Mocker.Resolve<HistoryRepository>().All().Single();
history.Data.Should().NotContainKey("downloadClientId");
history.Data.Should().Contain(new KeyValuePair<string, string>("indexer", "test"));
history.Data.Should().Contain(new KeyValuePair<string, string>("group", "test2"));
allProfiles.Data.Should().NotContainKey("downloadClientId");
allProfiles.Data.Should().Contain(new KeyValuePair<string, string>("indexer", "test"));
allProfiles.Data.Should().Contain(new KeyValuePair<string, string>("group", "test2"));
history.DownloadId.Should().Be("123");
allProfiles.DownloadId.Should().Be("123");
}
private void InsertHistory(MigrationBase migrationBase, Dictionary<string, string> data)
{
migrationBase.Insert.IntoTable("History").Row(new

View File

@@ -1,28 +1,29 @@
using System.Linq;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Jobs;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Datastore.Migration;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Test.Datastore.Migration
{
[TestFixture]
public class force_lib_updateFixture : MigrationTest<force_lib_update>
public class force_lib_updateFixture : MigrationTest<Core.Datastore.Migration.force_lib_update>
{
[Test]
public void should_not_fail_on_empty_db()
{
var db = WithMigrationTestDb();
WithTestDb(c => { });
db.Query("SELECT * FROM ScheduledTasks").Should().BeEmpty();
db.Query("SELECT * FROM Series").Should().BeEmpty();
Mocker.Resolve<ScheduledTaskRepository>().All().Should().BeEmpty();
Mocker.Resolve<SeriesRepository>().All().Should().BeEmpty();
}
[Test]
public void should_reset_job_last_execution_time()
{
var db = WithMigrationTestDb(c =>
WithTestDb(c =>
{
c.Insert.IntoTable("ScheduledTasks").Row(new
{
@@ -39,7 +40,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration
});
});
var jobs = db.Query<ScheduledTasks75>("SELECT TypeName, LastExecution FROM ScheduledTasks");
var jobs = Mocker.Resolve<ScheduledTaskRepository>().All().ToList();
jobs.Single(c => c.TypeName == "NzbDrone.Core.Tv.Commands.RefreshSeriesCommand")
.LastExecution.Year.Should()
@@ -50,10 +51,11 @@ namespace NzbDrone.Core.Test.Datastore.Migration
.Be(2000);
}
[Test]
public void should_reset_series_last_sync_time()
{
var db = WithMigrationTestDb(c =>
WithTestDb(c =>
{
c.Insert.IntoTable("Series").Row(new
{
@@ -90,9 +92,9 @@ namespace NzbDrone.Core.Test.Datastore.Migration
});
});
var series = db.Query<Series69>("SELECT LastInfoSync FROM Series");
var jobs = Mocker.Resolve<SeriesRepository>().All().ToList();
series.Should().OnlyContain(c => c.LastInfoSync.Value.Year == 2014);
jobs.Should().OnlyContain(c => c.LastInfoSync.Value.Year == 2014);
}
}
}

View File

@@ -1,18 +1,21 @@
using System.Linq;
using System;
using System.Linq;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Datastore.Migration;
using NzbDrone.Core.Jobs;
using NzbDrone.Core.Tags;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Test.Datastore.Migration
{
[TestFixture]
public class dedupe_tagsFixture : MigrationTest<dedupe_tags>
public class dedupe_tagsFixture : MigrationTest<Core.Datastore.Migration.dedupe_tags>
{
[Test]
public void should_not_fail_if_series_tags_are_null()
{
var db = WithMigrationTestDb(c =>
WithTestDb(c =>
{
c.Insert.IntoTable("Series").Row(new
{
@@ -37,14 +40,13 @@ namespace NzbDrone.Core.Test.Datastore.Migration
});
});
var tags = db.Query<Tag69>("SELECT * FROM Tags");
tags.Should().HaveCount(1);
Mocker.Resolve<TagRepository>().All().Should().HaveCount(1);
}
[Test]
public void should_not_fail_if_series_tags_are_empty()
{
var db = WithMigrationTestDb(c =>
WithTestDb(c =>
{
c.Insert.IntoTable("Series").Row(new
{
@@ -70,14 +72,13 @@ namespace NzbDrone.Core.Test.Datastore.Migration
});
});
var tags = db.Query<Tag69>("SELECT * FROM Tags");
tags.Should().HaveCount(1);
Mocker.Resolve<TagRepository>().All().Should().HaveCount(1);
}
[Test]
public void should_remove_duplicate_labels_from_tags()
{
var db = WithMigrationTestDb(c =>
WithTestDb(c =>
{
c.Insert.IntoTable("Tags").Row(new
{
@@ -90,14 +91,13 @@ namespace NzbDrone.Core.Test.Datastore.Migration
});
});
var tags = db.Query<Tag69>("SELECT * FROM Tags");
tags.Should().HaveCount(1);
Mocker.Resolve<TagRepository>().All().Should().HaveCount(1);
}
[Test]
public void should_not_allow_duplicate_tag_to_be_inserted()
{
var db = WithMigrationTestDb(c =>
WithTestDb(c =>
{
c.Insert.IntoTable("Tags").Row(new
{
@@ -105,13 +105,13 @@ namespace NzbDrone.Core.Test.Datastore.Migration
});
});
Assert.That(() => db.Query("INSERT INTO Tags (Label) VALUES ('test')"), Throws.Exception);
Assert.That(() => Mocker.Resolve<TagRepository>().Insert(new Tag { Label = "test" }), Throws.Exception);
}
[Test]
public void should_replace_duplicated_tag_with_proper_tag()
{
var db = WithMigrationTestDb(c =>
WithTestDb(c =>
{
c.Insert.IntoTable("Series").Row(new
{
@@ -142,14 +142,13 @@ namespace NzbDrone.Core.Test.Datastore.Migration
});
});
var series = db.Query<Series69>("SELECT Tags FROM Series WHERE Id = 1").Single();
series.Tags.First().Should().Be(1);
Mocker.Resolve<SeriesRepository>().Get(1).Tags.First().Should().Be(1);
}
[Test]
public void should_only_update_affected_series()
{
var db = WithMigrationTestDb(c =>
WithTestDb(c =>
{
c.Insert.IntoTable("Series").Row(new
{
@@ -198,8 +197,7 @@ namespace NzbDrone.Core.Test.Datastore.Migration
});
});
var series = db.Query<Series69>("SELECT Tags FROM Series WHERE Id = 2").Single();
series.Tags.Should().BeEmpty();
Mocker.Resolve<SeriesRepository>().Get(2).Tags.Should().BeEmpty();
}
}
}

View File

@@ -3,25 +3,27 @@ using System.Linq;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Clients.Sabnzbd;
using NzbDrone.Core.Download.Clients.Transmission;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Datastore.Migration;
namespace NzbDrone.Core.Test.Datastore.Migration
{
[TestFixture]
public class move_dot_prefix_to_transmission_categoryFixture : MigrationTest<move_dot_prefix_to_transmission_category>
public class move_dot_prefix_to_transmission_categoryFixture : MigrationTest<Core.Datastore.Migration.move_dot_prefix_to_transmission_category>
{
[Test]
public void should_not_fail_if_no_transmission()
{
var db = WithMigrationTestDb(c =>
WithTestDb(c =>
{
c.Insert.IntoTable("DownloadClients").Row(new
c.Insert.IntoTable("DownloadClients").Row(new
{
Enable = 1,
Name = "Sab",
Implementation = "Sabnzbd",
Settings = new
Settings = new SabnzbdSettings
{
Host = "127.0.0.1",
TvCategory = "abc"
@@ -30,23 +32,24 @@ namespace NzbDrone.Core.Test.Datastore.Migration
});
});
var downloadClients = db.Query<DownloadClientDefinition81>("SELECT Settings FROM DownloadClients");
var items = Mocker.Resolve<DownloadClientRepository>().All();
downloadClients.Should().HaveCount(1);
downloadClients.First().Settings.ToObject<SabnzbdSettings81>().TvCategory.Should().Be("abc");
items.Should().HaveCount(1);
items.First().Settings.As<SabnzbdSettings>().TvCategory.Should().Be("abc");
}
[Test]
public void should_be_updated_for_transmission()
{
var db = WithMigrationTestDb(c =>
WithTestDb(c =>
{
c.Insert.IntoTable("DownloadClients").Row(new
{
Enable = 1,
Name = "Trans",
Implementation = "Transmission",
Settings = new
Settings = new TransmissionSettings
{
Host = "127.0.0.1",
TvCategory = "abc"
@@ -55,23 +58,24 @@ namespace NzbDrone.Core.Test.Datastore.Migration
});
});
var downloadClients = db.Query<DownloadClientDefinition81>("SELECT Settings FROM DownloadClients");
var items = Mocker.Resolve<DownloadClientRepository>().All();
downloadClients.Should().HaveCount(1);
downloadClients.First().Settings.ToObject<TransmissionSettings81>().TvCategory.Should().Be(".abc");
items.Should().HaveCount(1);
items.First().Settings.As<TransmissionSettings>().TvCategory.Should().Be(".abc");
}
[Test]
public void should_leave_empty_category_untouched()
{
var db = WithMigrationTestDb(c =>
WithTestDb(c =>
{
c.Insert.IntoTable("DownloadClients").Row(new
{
Enable = 1,
Name = "Trans",
Implementation = "Transmission",
Settings = new
Settings = new TransmissionSettings
{
Host = "127.0.0.1",
TvCategory = ""
@@ -80,10 +84,11 @@ namespace NzbDrone.Core.Test.Datastore.Migration
});
});
var downloadClients = db.Query<DownloadClientDefinition81>("SELECT Settings FROM DownloadClients");
var items = Mocker.Resolve<DownloadClientRepository>().All();
downloadClients.Should().HaveCount(1);
downloadClients.First().Settings.ToObject<TransmissionSettings81>().TvCategory.Should().Be("");
items.Should().HaveCount(1);
items.First().Settings.As<TransmissionSettings>().TvCategory.Should().Be("");
}
}
}

View File

@@ -1,28 +1,32 @@
using System.Linq;
using System;
using System.Linq;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Datastore.Migration;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Datastore.Migration
{
[TestFixture]
public class update_quality_minmax_sizeFixture : MigrationTest<update_quality_minmax_size>
public class update_quality_minmax_sizeFixture : MigrationTest<Core.Datastore.Migration.update_quality_minmax_size>
{
[Test]
public void should_not_fail_if_empty()
{
var db = WithMigrationTestDb();
WithTestDb(c =>
{
var qualityDefinitions = db.Query<QualityDefinition84>("SELECT * FROM QualityDefinitions");
});
qualityDefinitions.Should().BeEmpty();
var items = Mocker.Resolve<QualityDefinitionRepository>().All();
items.Should().HaveCount(0);
}
[Test]
public void should_set_rawhd_to_null()
{
var db = WithMigrationTestDb(c =>
WithTestDb(c =>
{
c.Insert.IntoTable("QualityDefinitions").Row(new
{
@@ -40,16 +44,17 @@ namespace NzbDrone.Core.Test.Datastore.Migration
});
});
var qualityDefinitions = db.Query<QualityDefinition84>("SELECT * FROM QualityDefinitions");
var items = Mocker.Resolve<QualityDefinitionRepository>().All();
qualityDefinitions.Should().HaveCount(2);
qualityDefinitions.First(v => v.Quality == 10).MaxSize.Should().NotHaveValue();
items.Should().HaveCount(2);
items.First(v => v.Quality.Id == 10).MaxSize.Should().NotHaveValue();
}
[Test]
public void should_set_zero_maxsize_to_null()
{
var db = WithMigrationTestDb(c =>
WithTestDb(c =>
{
c.Insert.IntoTable("QualityDefinitions").Row(new
{
@@ -60,16 +65,17 @@ namespace NzbDrone.Core.Test.Datastore.Migration
});
});
var qualityDefinitions = db.Query<QualityDefinition84>("SELECT * FROM QualityDefinitions");
var items = Mocker.Resolve<QualityDefinitionRepository>().All();
qualityDefinitions.Should().HaveCount(1);
qualityDefinitions.First(v => v.Quality == 1).MaxSize.Should().NotHaveValue();
items.Should().HaveCount(1);
items.First(v => v.Quality.Id == 1).MaxSize.Should().NotHaveValue();
}
[Test]
public void should_preserve_values()
{
var db = WithMigrationTestDb(c =>
WithTestDb(c =>
{
c.Insert.IntoTable("QualityDefinitions").Row(new
{
@@ -87,10 +93,11 @@ namespace NzbDrone.Core.Test.Datastore.Migration
});
});
var qualityDefinitions = db.Query<QualityDefinition84>("SELECT * FROM QualityDefinitions");
var items = Mocker.Resolve<QualityDefinitionRepository>().All();
qualityDefinitions.Should().HaveCount(2);
qualityDefinitions.First(v => v.Quality == 1).MaxSize.Should().Be(100);
items.Should().HaveCount(2);
items.First(v => v.Quality.Id == 1).MaxSize.Should().Be(100);
}
}
}

Some files were not shown because too many files have changed in this diff Show More