1
0
mirror of https://github.com/Sonarr/Sonarr.git synced 2026-04-18 21:35:27 -04:00

Compare commits

..

9 Commits

451 changed files with 2380 additions and 9802 deletions

View File

@@ -1,41 +1,5 @@
<!--
Before opening a new issue, please ensure:
- You use the forums for support/questions
- You search for existing bugs/feature requests
- Remove extraneous template details
- Do not prefix title with type of issue (Feature Request, Bug, etc.) The appropriate labels will be added during triage.
-->
## Support / Questions
Please use https://forums.sonarr.tv/ for support. Support requests or questions will be redirected to the forums and the issue will be closed.
<!-- Provide a description of the feature request or bug, the more details the better.
Remove if not opening a bug report Please use https://forums.sonarr.tv/ for support or other questions. (When in doubt, use the forums)
-->
## Bug Report
### System Information/Logs
**Sonarr Version:**
**Operating System:**
**.net Framework (Windows) or mono (macOS/Linux) Version:**
**Link to Log Files (debug or trace):**
**Browser (for UI bugs):**
### Additional Information
<!--
Remove if not opening a feature request
-->
## Feature Request
### What problem are you looking to solve?
### Other Information

View File

@@ -1,28 +0,0 @@
---
name: Bug report
about: Create a report to help us improve Sonarr
---
**Describe the bug**
A clear and concise description of what the bug is.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Logs**
Link to debug or trace log files.
**System Information**
- Sonarr Version: [e.g. 2.0.0.1]
- Operating System: [e.g. Windows 10]
- .net Framework (Windows) or mono (macOS/Linux) Version: [e.g. 4.5 or 5.12]
**UI Bugs:**
- OS: [e.g. Windows]
- Browser: [e.g. chrome, firefox]
- Version: [e.g. 22]
**Additional context**
Add any other context about the problem here.

View File

@@ -1,14 +0,0 @@
---
name: Feature request
about: Suggest an idea for Sonarr
---
**Describe the problem**
A clear and concise description of the problem you're looking to solve.
**Describe any solutions you think might work**
A clear and concise description of any solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -1,7 +0,0 @@
---
name: Other issues
about: How to get support or ask questions
---
Please use https://forums.sonarr.tv/ for support. Support requests or questions will be redirected to the forums and the issue will be closed.

7
.github/SUPPORT.md vendored
View File

@@ -1,7 +0,0 @@
## Support
There are a number of frequently asked questions that have been answered in our [FAQ](https://github.com/Sonarr/Sonarr/wiki/FAQ)
The [wiki](https://github.com/Sonarr/Sonarr/wiki) contains other information and guides
If you have a support question, please use the [support forums](https://forums.sonarr.tv/).

1
.gitignore vendored
View File

@@ -130,7 +130,6 @@ output/*
#OS X metadata files #OS X metadata files
._* ._*
.DS_Store
_start _start
_temp_*/**/* _temp_*/**/*

View File

@@ -52,6 +52,15 @@ CleanFolder()
find $path -depth -empty -type d -exec rm -r "{}" \; find $path -depth -empty -type d -exec rm -r "{}" \;
} }
AddJsonNet()
{
rm $outputFolder/Newtonsoft.Json.*
cp $sourceFolder/packages/Newtonsoft.Json.*/lib/net35/*.dll $outputFolder
cp $sourceFolder/packages/Newtonsoft.Json.*/lib/net35/*.dll $outputFolder/NzbDrone.Update
}
BuildWithMSBuild() BuildWithMSBuild()
{ {
export PATH=$msBuild:$PATH export PATH=$msBuild:$PATH
@@ -82,6 +91,8 @@ Build()
CleanFolder $outputFolder false CleanFolder $outputFolder false
AddJsonNet
echo "Removing Mono.Posix.dll" echo "Removing Mono.Posix.dll"
rm $outputFolder/Mono.Posix.dll rm $outputFolder/Mono.Posix.dll

View File

@@ -9,11 +9,7 @@ APPNAME="Sonarr"
#set up environment #set up environment
if [[ -x '/opt/local/bin/mono' ]]; then if [[ -x '/opt/local/bin/mono' ]]; then
# Macports and mono-supplied installer path
export PATH="/opt/local/bin:$PATH" export PATH="/opt/local/bin:$PATH"
elif [[ -x '/usr/local/bin/mono' ]]; then
# Homebrew-supplied path to mono
export PATH="/usr/local/bin:$PATH"
fi fi
export DYLD_FALLBACK_LIBRARY_PATH="$DIR" export DYLD_FALLBACK_LIBRARY_PATH="$DIR"

View File

@@ -52,14 +52,10 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL"> <Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.5.3\lib\net40-client\NLog.dll</HintPath> <HintPath>..\packages\NLog.4.4.3\lib\net40\NLog.dll</HintPath>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.ServiceModel" />
<Reference Include="System.Transactions" />
<Reference Include="System.Xml.Linq" /> <Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" /> <Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" /> <Reference Include="Microsoft.CSharp" />

View File

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

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Data.Common; using System.Data.Common;
@@ -91,11 +91,9 @@ namespace Marr.Data.Mapping
Type entType = ent.GetType(); Type entType = ent.GetType();
if (_repos.Relationships.ContainsKey(entType)) if (_repos.Relationships.ContainsKey(entType))
{ {
var provider = _db.ProviderFactory;
var connectionString = _db.ConnectionString;
Func<IDataMapper> dbCreate = () => Func<IDataMapper> dbCreate = () =>
{ {
var db = new DataMapper(provider, connectionString); var db = new DataMapper(_db.ProviderFactory, _db.ConnectionString);
db.SqlMode = SqlModes.Text; db.SqlMode = SqlModes.Text;
return db; return db;
}; };

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Reflection; using System.Reflection;

View File

@@ -21,33 +21,20 @@ namespace NzbDrone.Api.Test.ClientSchemaTests
public void schema_should_have_proper_fields() public void schema_should_have_proper_fields()
{ {
var model = new TestModel var model = new TestModel
{ {
FirstName = "Bob", FirstName = "Bob",
LastName = "Poop" LastName = "Poop"
}; };
var schema = SchemaBuilder.ToSchema(model); var schema = SchemaBuilder.ToSchema(model);
schema.Should().Contain(c => c.Order == 1 && c.Name == "LastName" && c.Label == "Last Name" && c.HelpText == "Your Last Name" && (string)c.Value == "Poop"); schema.Should().Contain(c => c.Order == 1 && c.Name == "LastName" && c.Label == "Last Name" && c.HelpText == "Your Last Name" && (string) c.Value == "Poop");
schema.Should().Contain(c => c.Order == 0 && c.Name == "FirstName" && c.Label == "First Name" && c.HelpText == "Your First Name" && (string)c.Value == "Bob"); schema.Should().Contain(c => c.Order == 0 && c.Name == "FirstName" && c.Label == "First Name" && c.HelpText == "Your First Name" && (string) c.Value == "Bob");
} }
[Test]
public void schema_should_have_nested_fields()
{
var model = new NestedTestModel();
model.Name.FirstName = "Bob";
model.Name.LastName = "Poop";
var schema = SchemaBuilder.ToSchema(model);
schema.Should().Contain(c => c.Order == 0 && c.Name == "Name.FirstName" && c.Label == "First Name" && c.HelpText == "Your First Name" && (string)c.Value == "Bob");
schema.Should().Contain(c => c.Order == 1 && c.Name == "Name.LastName" && c.Label == "Last Name" && c.HelpText == "Your Last Name" && (string)c.Value == "Poop");
schema.Should().Contain(c => c.Order == 2 && c.Name == "Quote" && c.Label == "Quote" && c.HelpText == "Your Favorite Quote");
}
} }
public class TestModel public class TestModel
{ {
[FieldDefinition(0, Label = "First Name", HelpText = "Your First Name")] [FieldDefinition(0, Label = "First Name", HelpText = "Your First Name")]
@@ -58,13 +45,4 @@ namespace NzbDrone.Api.Test.ClientSchemaTests
public string Other { get; set; } public string Other { get; set; }
} }
}
public class NestedTestModel
{
[FieldDefinition(0)]
public TestModel Name { get; set; } = new TestModel();
[FieldDefinition(1, Label = "Quote", HelpText = "Your Favorite Quote")]
public string Quote { get; set; }
}
}

View File

@@ -48,8 +48,8 @@
<Reference Include="FluentAssertions.Core, Version=4.19.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL"> <Reference Include="FluentAssertions.Core, Version=4.19.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
<HintPath>..\packages\FluentAssertions.4.19.0\lib\net40\FluentAssertions.Core.dll</HintPath> <HintPath>..\packages\FluentAssertions.4.19.0\lib\net40\FluentAssertions.Core.dll</HintPath>
</Reference> </Reference>
<Reference Include="Moq, Version=4.0.10827.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL"> <Reference Include="Moq, Version=4.2.1510.2205, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
<Private>True</Private> <HintPath>..\packages\Moq.4.2.1510.2205\lib\net40\Moq.dll</HintPath>
</Reference> </Reference>
<Reference Include="nunit.framework, Version=3.6.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL"> <Reference Include="nunit.framework, Version=3.6.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>..\packages\NUnit.3.6.0\lib\net40\nunit.framework.dll</HintPath> <HintPath>..\packages\NUnit.3.6.0\lib\net40\nunit.framework.dll</HintPath>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="FluentAssertions" version="4.19.0" targetFramework="net40" /> <package id="FluentAssertions" version="4.19.0" targetFramework="net40" />
<package id="Moq" version="4.0.10827" targetFramework="net40" /> <package id="Moq" version="4.0.10827" />
<package id="NBuilder" version="4.0.0" targetFramework="net40" /> <package id="NBuilder" version="4.0.0" targetFramework="net40" />
<package id="NUnit" version="3.6.0" targetFramework="net40" /> <package id="NUnit" version="3.6.0" targetFramework="net40" />
</packages> </packages>

View File

@@ -1,10 +1,9 @@
using Nancy; using Nancy;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Ical.Net; using Ical.Net;
using Ical.Net.DataTypes; using Ical.Net.DataTypes;
using Ical.Net.General;
using Ical.Net.Interfaces.Serialization; using Ical.Net.Interfaces.Serialization;
using Ical.Net.Serialization; using Ical.Net.Serialization;
using Ical.Net.Serialization.iCalendar.Factory; using Ical.Net.Serialization.iCalendar.Factory;
@@ -93,9 +92,7 @@ namespace NzbDrone.Api.Calendar
ProductId = "-//sonarr.tv//Sonarr//EN" ProductId = "-//sonarr.tv//Sonarr//EN"
}; };
var calendarName = "Sonarr TV Schedule";
calendar.AddProperty(new CalendarProperty("NAME", calendarName));
calendar.AddProperty(new CalendarProperty("X-WR-CALNAME", calendarName));
foreach (var episode in episodes.OrderBy(v => v.AirDateUtc.Value)) foreach (var episode in episodes.OrderBy(v => v.AirDateUtc.Value))
{ {
@@ -117,7 +114,7 @@ namespace NzbDrone.Api.Calendar
if (asAllDay) if (asAllDay)
{ {
occurrence.Start = new CalDateTime(episode.AirDateUtc.Value.ToLocalTime()) { HasTime = false }; occurrence.Start = new CalDateTime(episode.AirDateUtc.Value) { HasTime = false };
} }
else else
{ {

View File

@@ -7,17 +7,11 @@ namespace NzbDrone.Api.ClientSchema
public int Order { get; set; } public int Order { get; set; }
public string Name { get; set; } public string Name { get; set; }
public string Label { get; set; } public string Label { get; set; }
public string Unit { get; set; }
public string HelpText { get; set; } public string HelpText { get; set; }
public string HelpLink { get; set; } public string HelpLink { get; set; }
public object Value { get; set; } public object Value { get; set; }
public string Type { get; set; } public string Type { get; set; }
public bool Advanced { get; set; } public bool Advanced { get; set; }
public List<SelectOption> SelectOptions { get; set; } public List<SelectOption> SelectOptions { get; set; }
public Field Clone()
{
return (Field)MemberwiseClone();
}
} }
} }

View File

@@ -1,15 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Api.ClientSchema
{
public class FieldMapping
{
public Field Field { get; set; }
public Type PropertyType { get; set; }
public Func<object, object> GetterFunc { get; set; }
public Action<object, object> SetterFunc { get; set; }
}
}

View File

@@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using NzbDrone.Common.EnsureThat; using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
@@ -12,22 +11,45 @@ namespace NzbDrone.Api.ClientSchema
{ {
public static class SchemaBuilder public static class SchemaBuilder
{ {
private static Dictionary<Type, FieldMapping[]> _mappings = new Dictionary<Type, FieldMapping[]>();
public static List<Field> ToSchema(object model) public static List<Field> ToSchema(object model)
{ {
Ensure.That(model, () => model).IsNotNull(); Ensure.That(model, () => model).IsNotNull();
var mappings = GetFieldMappings(model.GetType()); var properties = model.GetType().GetSimpleProperties();
var result = new List<Field>(mappings.Length); var result = new List<Field>(properties.Count);
foreach (var mapping in mappings) foreach (var propertyInfo in properties)
{ {
var field = mapping.Field.Clone(); var fieldAttribute = propertyInfo.GetAttribute<FieldDefinitionAttribute>(false);
field.Value = mapping.GetterFunc(model);
result.Add(field); if (fieldAttribute != null)
{
var field = new Field
{
Name = propertyInfo.Name,
Label = fieldAttribute.Label,
HelpText = fieldAttribute.HelpText,
HelpLink = fieldAttribute.HelpLink,
Order = fieldAttribute.Order,
Advanced = fieldAttribute.Advanced,
Type = fieldAttribute.Type.ToString().ToLowerInvariant()
};
var value = propertyInfo.GetValue(model, null);
if (value != null)
{
field.Value = value;
}
if (fieldAttribute.Type == FieldType.Select)
{
field.SelectOptions = GetSelectOptions(fieldAttribute.SelectOptions);
}
result.Add(field);
}
} }
return result.OrderBy(r => r.Order).ToList(); return result.OrderBy(r => r.Order).ToList();
@@ -37,16 +59,81 @@ namespace NzbDrone.Api.ClientSchema
{ {
Ensure.That(targetType, () => targetType).IsNotNull(); Ensure.That(targetType, () => targetType).IsNotNull();
var mappings = GetFieldMappings(targetType); var properties = targetType.GetSimpleProperties();
var target = Activator.CreateInstance(targetType); var target = Activator.CreateInstance(targetType);
foreach (var mapping in mappings) foreach (var propertyInfo in properties)
{ {
var propertyType = mapping.PropertyType; var fieldAttribute = propertyInfo.GetAttribute<FieldDefinitionAttribute>(false);
var field = fields.Find(f => f.Name == mapping.Field.Name);
mapping.SetterFunc(target, field.Value); if (fieldAttribute != null)
{
var field = fields.Find(f => f.Name == propertyInfo.Name);
if (propertyInfo.PropertyType == typeof(int))
{
var value = field.Value.ToString().ParseInt32();
propertyInfo.SetValue(target, value ?? 0, null);
}
else if (propertyInfo.PropertyType == typeof(long))
{
var value = field.Value.ToString().ParseInt64();
propertyInfo.SetValue(target, value ?? 0, null);
}
else if (propertyInfo.PropertyType == typeof(int?))
{
var value = field.Value.ToString().ParseInt32();
propertyInfo.SetValue(target, value, null);
}
else if (propertyInfo.PropertyType == typeof(Nullable<Int64>))
{
var value = field.Value.ToString().ParseInt64();
propertyInfo.SetValue(target, value, null);
}
else if (propertyInfo.PropertyType == typeof(IEnumerable<int>))
{
IEnumerable<int> value;
if (field.Value.GetType() == typeof(JArray))
{
value = ((JArray)field.Value).Select(s => s.Value<int>());
}
else
{
value = field.Value.ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(s => Convert.ToInt32(s));
}
propertyInfo.SetValue(target, value, null);
}
else if (propertyInfo.PropertyType == typeof(IEnumerable<string>))
{
IEnumerable<string> value;
if (field.Value.GetType() == typeof(JArray))
{
value = ((JArray)field.Value).Select(s => s.Value<string>());
}
else
{
value = field.Value.ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
}
propertyInfo.SetValue(target, value, null);
}
else
{
propertyInfo.SetValue(target, field.Value, null);
}
}
} }
return target; return target;
@@ -58,84 +145,6 @@ namespace NzbDrone.Api.ClientSchema
return (T)ReadFromSchema(fields, typeof(T)); return (T)ReadFromSchema(fields, typeof(T));
} }
// Ideally this function should begin a System.Linq.Expression expression tree since it's faster.
// But it's probably not needed till performance issues pop up.
public static FieldMapping[] GetFieldMappings(Type type)
{
lock (_mappings)
{
FieldMapping[] result;
if (!_mappings.TryGetValue(type, out result))
{
result = GetFieldMapping(type, "", v => v);
// Renumber al the field Orders since nested settings will have dupe Orders.
for (int i = 0; i < result.Length; i++)
{
result[i].Field.Order = i;
}
_mappings[type] = result;
}
return result;
}
}
private static FieldMapping[] GetFieldMapping(Type type, string prefix, Func<object, object> targetSelector)
{
var result = new List<FieldMapping>();
foreach (var property in GetProperties(type))
{
var propertyInfo = property.Item1;
if (propertyInfo.PropertyType.IsSimpleType())
{
var fieldAttribute = property.Item2;
var field = new Field
{
Name = prefix + propertyInfo.Name,
Label = fieldAttribute.Label,
Unit = fieldAttribute.Unit,
HelpText = fieldAttribute.HelpText,
HelpLink = fieldAttribute.HelpLink,
Order = fieldAttribute.Order,
Advanced = fieldAttribute.Advanced,
Type = fieldAttribute.Type.ToString().ToLowerInvariant()
};
if (fieldAttribute.Type == FieldType.Select)
{
field.SelectOptions = GetSelectOptions(fieldAttribute.SelectOptions);
}
var valueConverter = GetValueConverter(propertyInfo.PropertyType);
result.Add(new FieldMapping
{
Field = field,
PropertyType = propertyInfo.PropertyType,
GetterFunc = t => propertyInfo.GetValue(targetSelector(t), null),
SetterFunc = (t, v) => propertyInfo.SetValue(targetSelector(t), valueConverter(v), null)
});
}
else
{
result.AddRange(GetFieldMapping(propertyInfo.PropertyType, propertyInfo.Name + ".", t => propertyInfo.GetValue(targetSelector(t), null)));
}
}
return result.ToArray();
}
private static Tuple<PropertyInfo, FieldDefinitionAttribute>[] GetProperties(Type type)
{
return type.GetProperties()
.Select(v => Tuple.Create(v, v.GetAttribute<FieldDefinitionAttribute>(false)))
.Where(v => v.Item2 != null)
.OrderBy(v => v.Item2.Order)
.ToArray();
}
private static List<SelectOption> GetSelectOptions(Type selectOptions) private static List<SelectOption> GetSelectOptions(Type selectOptions)
{ {
var options = from Enum e in Enum.GetValues(selectOptions) var options = from Enum e in Enum.GetValues(selectOptions)
@@ -143,73 +152,5 @@ namespace NzbDrone.Api.ClientSchema
return options.OrderBy(o => o.Value).ToList(); return options.OrderBy(o => o.Value).ToList();
} }
private static Func<object, object> GetValueConverter(Type propertyType)
{
if (propertyType == typeof(int))
{
return fieldValue => fieldValue?.ToString().ParseInt32() ?? 0;
}
else if (propertyType == typeof(long))
{
return fieldValue => fieldValue?.ToString().ParseInt64() ?? 0;
}
else if (propertyType == typeof(double))
{
return fieldValue => fieldValue?.ToString().ParseDouble() ?? 0.0;
}
else if (propertyType == typeof(int?))
{
return fieldValue => fieldValue?.ToString().ParseInt32();
}
else if (propertyType == typeof(Int64?))
{
return fieldValue => fieldValue?.ToString().ParseInt64();
}
else if (propertyType == typeof(double?))
{
return fieldValue => fieldValue?.ToString().ParseDouble();
}
else if (propertyType == typeof(IEnumerable<int>))
{
return fieldValue =>
{
if (fieldValue.GetType() == typeof(JArray))
{
return ((JArray)fieldValue).Select(s => s.Value<int>());
}
else
{
return fieldValue.ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(s => Convert.ToInt32(s));
}
};
}
else if (propertyType == typeof(IEnumerable<string>))
{
return fieldValue =>
{
if (fieldValue.GetType() == typeof(JArray))
{
return ((JArray)fieldValue).Select(s => s.Value<string>());
}
else
{
return fieldValue.ToString().Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
}
};
}
else
{
return fieldValue => fieldValue;
}
}
} }
} }

View File

@@ -4,7 +4,6 @@ using System.Linq;
using NzbDrone.Api.Extensions; using NzbDrone.Api.Extensions;
using NzbDrone.Api.Validation; using NzbDrone.Api.Validation;
using NzbDrone.Common; using NzbDrone.Common;
using NzbDrone.Common.TPL;
using NzbDrone.Core.Datastore.Events; using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.Messaging.Commands; using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Events; using NzbDrone.Core.Messaging.Events;
@@ -18,8 +17,6 @@ namespace NzbDrone.Api.Commands
{ {
private readonly IManageCommandQueue _commandQueueManager; private readonly IManageCommandQueue _commandQueueManager;
private readonly IServiceFactory _serviceFactory; private readonly IServiceFactory _serviceFactory;
private readonly Debouncer _debouncer;
private readonly Dictionary<int, CommandResource> _pendingUpdates;
public CommandModule(IManageCommandQueue commandQueueManager, public CommandModule(IManageCommandQueue commandQueueManager,
IBroadcastSignalRMessage signalRBroadcaster, IBroadcastSignalRMessage signalRBroadcaster,
@@ -34,10 +31,6 @@ namespace NzbDrone.Api.Commands
GetResourceAll = GetStartedCommands; GetResourceAll = GetStartedCommands;
PostValidator.RuleFor(c => c.Name).NotBlank(); PostValidator.RuleFor(c => c.Name).NotBlank();
_debouncer = new Debouncer(SendUpdates, TimeSpan.FromSeconds(0.1));
_pendingUpdates = new Dictionary<int, CommandResource>();
} }
private CommandResource GetCommand(int id) private CommandResource GetCommand(int id)
@@ -66,26 +59,8 @@ namespace NzbDrone.Api.Commands
{ {
if (message.Command.Body.SendUpdatesToClient) if (message.Command.Body.SendUpdatesToClient)
{ {
lock (_pendingUpdates) BroadcastResourceChange(ModelAction.Updated, message.Command.ToResource());
{
_pendingUpdates[message.Command.Id] = message.Command.ToResource();
}
_debouncer.Execute();
}
}
private void SendUpdates()
{
lock (_pendingUpdates)
{
var pendingUpdates = _pendingUpdates.Values.ToArray();
_pendingUpdates.Clear();
foreach (var pendingUpdate in pendingUpdates)
{
BroadcastResourceChange(ModelAction.Updated, pendingUpdate);
}
} }
} }
} }
} }

View File

@@ -13,9 +13,6 @@ namespace NzbDrone.Api.Config
SharedValidator.RuleFor(c => c.MinimumAge) SharedValidator.RuleFor(c => c.MinimumAge)
.GreaterThanOrEqualTo(0); .GreaterThanOrEqualTo(0);
SharedValidator.RuleFor(c => c.MaximumSize)
.GreaterThanOrEqualTo(0);
SharedValidator.RuleFor(c => c.Retention) SharedValidator.RuleFor(c => c.Retention)
.GreaterThanOrEqualTo(0); .GreaterThanOrEqualTo(0);
@@ -28,4 +25,4 @@ namespace NzbDrone.Api.Config
return IndexerConfigResourceMapper.ToResource(model); return IndexerConfigResourceMapper.ToResource(model);
} }
} }
} }

View File

@@ -6,7 +6,6 @@ namespace NzbDrone.Api.Config
public class IndexerConfigResource : RestResource public class IndexerConfigResource : RestResource
{ {
public int MinimumAge { get; set; } public int MinimumAge { get; set; }
public int MaximumSize { get; set; }
public int Retention { get; set; } public int Retention { get; set; }
public int RssSyncInterval { get; set; } public int RssSyncInterval { get; set; }
} }
@@ -18,7 +17,6 @@ namespace NzbDrone.Api.Config
return new IndexerConfigResource return new IndexerConfigResource
{ {
MinimumAge = model.MinimumAge, MinimumAge = model.MinimumAge,
MaximumSize = model.MaximumSize,
Retention = model.Retention, Retention = model.Retention,
RssSyncInterval = model.RssSyncInterval, RssSyncInterval = model.RssSyncInterval,
}; };

View File

@@ -1,4 +1,4 @@
using NzbDrone.Api.REST; using NzbDrone.Api.REST;
using NzbDrone.Core.Configuration; using NzbDrone.Core.Configuration;
using NzbDrone.Core.MediaFiles; using NzbDrone.Core.MediaFiles;
@@ -10,7 +10,6 @@ namespace NzbDrone.Api.Config
public string RecycleBin { get; set; } public string RecycleBin { get; set; }
public bool AutoDownloadPropers { get; set; } public bool AutoDownloadPropers { get; set; }
public bool CreateEmptySeriesFolders { get; set; } public bool CreateEmptySeriesFolders { get; set; }
public bool DeleteEmptyFolders { get; set; }
public FileDateType FileDate { get; set; } public FileDateType FileDate { get; set; }
public bool SetPermissionsLinux { get; set; } public bool SetPermissionsLinux { get; set; }
@@ -36,7 +35,6 @@ namespace NzbDrone.Api.Config
RecycleBin = model.RecycleBin, RecycleBin = model.RecycleBin,
AutoDownloadPropers = model.AutoDownloadPropers, AutoDownloadPropers = model.AutoDownloadPropers,
CreateEmptySeriesFolders = model.CreateEmptySeriesFolders, CreateEmptySeriesFolders = model.CreateEmptySeriesFolders,
DeleteEmptyFolders = model.DeleteEmptyFolders,
FileDate = model.FileDate, FileDate = model.FileDate,
SetPermissionsLinux = model.SetPermissionsLinux, SetPermissionsLinux = model.SetPermissionsLinux,

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.IO; using System.IO;
using NzbDrone.Api.REST; using NzbDrone.Api.REST;
using NzbDrone.Core.Qualities; using NzbDrone.Core.Qualities;
@@ -15,8 +15,6 @@ namespace NzbDrone.Api.EpisodeFiles
public DateTime DateAdded { get; set; } public DateTime DateAdded { get; set; }
public string SceneName { get; set; } public string SceneName { get; set; }
public QualityModel Quality { get; set; } public QualityModel Quality { get; set; }
public MediaInfoResource MediaInfo { get; set; }
public string OriginalFilePath { get; set; }
public bool QualityCutoffNotMet { get; set; } public bool QualityCutoffNotMet { get; set; }
} }
@@ -39,8 +37,7 @@ namespace NzbDrone.Api.EpisodeFiles
DateAdded = model.DateAdded, DateAdded = model.DateAdded,
SceneName = model.SceneName, SceneName = model.SceneName,
Quality = model.Quality, Quality = model.Quality,
MediaInfo = model.MediaInfo.ToResource(model.SceneName), //QualityCutoffNotMet
OriginalFilePath = model.OriginalFilePath
}; };
} }
@@ -60,9 +57,7 @@ namespace NzbDrone.Api.EpisodeFiles
DateAdded = model.DateAdded, DateAdded = model.DateAdded,
SceneName = model.SceneName, SceneName = model.SceneName,
Quality = model.Quality, Quality = model.Quality,
QualityCutoffNotMet = qualityUpgradableSpecification.CutoffNotMet(series.Profile.Value, model.Quality), QualityCutoffNotMet = qualityUpgradableSpecification.CutoffNotMet(series.Profile.Value, model.Quality)
MediaInfo = model.MediaInfo.ToResource(model.SceneName),
OriginalFilePath = model.OriginalFilePath
}; };
} }
} }

View File

@@ -1,30 +0,0 @@
using NzbDrone.Api.REST;
using NzbDrone.Core.MediaFiles.MediaInfo;
namespace NzbDrone.Api.EpisodeFiles
{
public class MediaInfoResource : RestResource
{
public decimal AudioChannels { get; set; }
public string AudioCodec { get; set; }
public string VideoCodec { get; set; }
}
public static class MediaInfoResourceMapper
{
public static MediaInfoResource ToResource(this MediaInfoModel model, string sceneName)
{
if (model == null)
{
return null;
}
return new MediaInfoResource
{
AudioChannels = MediaInfoFormatter.FormatAudioChannels(model),
AudioCodec = MediaInfoFormatter.FormatAudioCodec(model, sceneName),
VideoCodec = MediaInfoFormatter.FormatVideoCodec(model, sceneName)
};
}
}
}

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Newtonsoft.Json; using Newtonsoft.Json;
@@ -30,7 +30,6 @@ namespace NzbDrone.Api.Episodes
public bool UnverifiedSceneNumbering { get; set; } public bool UnverifiedSceneNumbering { get; set; }
public string SeriesTitle { get; set; } public string SeriesTitle { get; set; }
public SeriesResource Series { get; set; } public SeriesResource Series { get; set; }
public DateTime? LastSearchTime { get; set; }
//Hiding this so people don't think its usable (only used to set the initial state) //Hiding this so people don't think its usable (only used to set the initial state)
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
@@ -66,7 +65,6 @@ namespace NzbDrone.Api.Episodes
UnverifiedSceneNumbering = model.UnverifiedSceneNumbering, UnverifiedSceneNumbering = model.UnverifiedSceneNumbering,
SeriesTitle = model.SeriesTitle, SeriesTitle = model.SeriesTitle,
//Series = model.Series.MapToResource(), //Series = model.Series.MapToResource(),
LastSearchTime = model.LastSearchTime
}; };
} }

View File

@@ -23,8 +23,6 @@ namespace NzbDrone.Api.Extensions.Pipelines
private void Handle(NancyContext context) private void Handle(NancyContext context)
{ {
if (context.Request.Method == "OPTIONS") return;
if (_cacheableSpecification.IsCacheable(context)) if (_cacheableSpecification.IsCacheable(context))
{ {
context.Response.Headers.EnableCache(); context.Response.Headers.EnableCache();
@@ -35,4 +33,4 @@ namespace NzbDrone.Api.Extensions.Pipelines
} }
} }
} }
} }

View File

@@ -2,7 +2,6 @@
using System.Linq; using System.Linq;
using Nancy; using Nancy;
using Nancy.Bootstrapper; using Nancy.Bootstrapper;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Api.Extensions.Pipelines namespace NzbDrone.Api.Extensions.Pipelines
{ {
@@ -12,25 +11,10 @@ namespace NzbDrone.Api.Extensions.Pipelines
public void Register(IPipelines pipelines) public void Register(IPipelines pipelines)
{ {
pipelines.BeforeRequest.AddItemToEndOfPipeline(HandleRequest); pipelines.AfterRequest.AddItemToEndOfPipeline((Action<NancyContext>) Handle);
pipelines.AfterRequest.AddItemToEndOfPipeline(HandleResponse);
} }
private Response HandleRequest(NancyContext context) private void Handle(NancyContext context)
{
if (context == null || context.Request.Method != "OPTIONS")
{
return null;
}
var response = new Response()
.WithStatusCode(HttpStatusCode.OK)
.WithContentType("");
ApplyResponseHeaders(response, context.Request);
return response;
}
private void HandleResponse(NancyContext context)
{ {
if (context == null || context.Response.Headers.ContainsKey(AccessControlHeaders.AllowOrigin)) if (context == null || context.Response.Headers.ContainsKey(AccessControlHeaders.AllowOrigin))
{ {
@@ -42,39 +26,21 @@ namespace NzbDrone.Api.Extensions.Pipelines
private static void ApplyResponseHeaders(Response response, Request request) private static void ApplyResponseHeaders(Response response, Request request)
{ {
if (request.IsApiRequest()) var allowedMethods = "GET, OPTIONS, PATCH, POST, PUT, DELETE";
if (response.Headers.ContainsKey("Allow"))
{ {
// Allow Cross-Origin access to the API since it's protected with the apikey, and nothing else. allowedMethods = response.Headers["Allow"];
ApplyCorsResponseHeaders(response, request, "*", "GET, OPTIONS, PATCH, POST, PUT, DELETE");
} }
else if (request.IsSharedContentRequest())
var requestedHeaders = string.Join(", ", request.Headers[AccessControlHeaders.RequestHeaders]);
response.Headers.Add(AccessControlHeaders.AllowOrigin, "*");
response.Headers.Add(AccessControlHeaders.AllowMethods, allowedMethods);
if (request.Headers[AccessControlHeaders.RequestHeaders].Any())
{ {
// Allow Cross-Origin access to specific shared content such as mediacovers and images. response.Headers.Add(AccessControlHeaders.AllowHeaders, requestedHeaders);
ApplyCorsResponseHeaders(response, request, "*", "GET, OPTIONS");
}
// Disallow Cross-Origin access for any other route.
}
private static void ApplyCorsResponseHeaders(Response response, Request request, string allowOrigin, string allowedMethods)
{
response.Headers.Add(AccessControlHeaders.AllowOrigin, allowOrigin);
if (request.Method == "OPTIONS")
{
if (response.Headers.ContainsKey("Allow"))
{
allowedMethods = response.Headers["Allow"];
}
response.Headers.Add(AccessControlHeaders.AllowMethods, allowedMethods);
if (request.Headers[AccessControlHeaders.RequestHeaders].Any())
{
var requestedHeaders = request.Headers[AccessControlHeaders.RequestHeaders].Join(", ");
response.Headers.Add(AccessControlHeaders.AllowHeaders, requestedHeaders);
}
} }
} }
} }

View File

@@ -5,7 +5,6 @@ using System.Linq;
using Nancy; using Nancy;
using Nancy.Bootstrapper; using Nancy.Bootstrapper;
using NLog; using NLog;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
namespace NzbDrone.Api.Extensions.Pipelines namespace NzbDrone.Api.Extensions.Pipelines
@@ -16,14 +15,9 @@ namespace NzbDrone.Api.Extensions.Pipelines
public int Order => 0; public int Order => 0;
private readonly Action<Action<Stream>, Stream> _writeGZipStream;
public GzipCompressionPipeline(Logger logger) public GzipCompressionPipeline(Logger logger)
{ {
_logger = logger; _logger = logger;
// On Mono GZipStream/DeflateStream leaks memory if an exception is thrown, use an intermediate buffer in that case.
_writeGZipStream = PlatformInfo.IsMono ? WriteGZipStreamMono : (Action<Action<Stream>, Stream>)WriteGZipStream;
} }
public void Register(IPipelines pipelines) public void Register(IPipelines pipelines)
@@ -39,8 +33,7 @@ namespace NzbDrone.Api.Extensions.Pipelines
try try
{ {
if ( if (
response.Contents != Response.NoBody !response.ContentType.Contains("image")
&& !response.ContentType.Contains("image")
&& !response.ContentType.Contains("font") && !response.ContentType.Contains("font")
&& request.Headers.AcceptEncoding.Any(x => x.Contains("gzip")) && request.Headers.AcceptEncoding.Any(x => x.Contains("gzip"))
&& !AlreadyGzipEncoded(response) && !AlreadyGzipEncoded(response)
@@ -49,7 +42,14 @@ namespace NzbDrone.Api.Extensions.Pipelines
var contents = response.Contents; var contents = response.Contents;
response.Headers["Content-Encoding"] = "gzip"; response.Headers["Content-Encoding"] = "gzip";
response.Contents = responseStream => _writeGZipStream(contents, responseStream); response.Contents = responseStream =>
{
using (var gzip = new GZipStream(responseStream, CompressionMode.Compress, true))
using (var buffered = new BufferedStream(gzip, 8192))
{
contents.Invoke(buffered);
}
};
} }
} }
@@ -60,25 +60,6 @@ namespace NzbDrone.Api.Extensions.Pipelines
} }
} }
private static void WriteGZipStreamMono(Action<Stream> innerContent, Stream targetStream)
{
using (var membuffer = new MemoryStream())
{
WriteGZipStream(innerContent, membuffer);
membuffer.Position = 0;
membuffer.CopyTo(targetStream);
}
}
private static void WriteGZipStream(Action<Stream> innerContent, Stream targetStream)
{
using (var gzip = new GZipStream(targetStream, CompressionMode.Compress, true))
using (var buffered = new BufferedStream(gzip, 8192))
{
innerContent.Invoke(buffered);
}
}
private static bool ContentLengthIsTooSmall(Response response) private static bool ContentLengthIsTooSmall(Response response)
{ {
var contentLength = response.Headers.GetValueOrDefault("Content-Length"); var contentLength = response.Headers.GetValueOrDefault("Content-Length");
@@ -99,4 +80,4 @@ namespace NzbDrone.Api.Extensions.Pipelines
return false; return false;
} }
} }
} }

View File

@@ -1,4 +1,4 @@
using System; using System;
using Nancy; using Nancy;
namespace NzbDrone.Api.Extensions namespace NzbDrone.Api.Extensions
@@ -36,23 +36,5 @@ namespace NzbDrone.Api.Extensions
{ {
return request.Path.StartsWith("/Content/", StringComparison.InvariantCultureIgnoreCase); return request.Path.StartsWith("/Content/", StringComparison.InvariantCultureIgnoreCase);
} }
public static bool IsSharedContentRequest(this Request request)
{
return request.Path.StartsWith("/MediaCover/", StringComparison.InvariantCultureIgnoreCase) ||
request.Path.StartsWith("/Content/Images/", StringComparison.InvariantCultureIgnoreCase);
}
public static bool GetBooleanQueryParameter(this Request request, string parameter, bool defaultValue = false)
{
var parameterValue = request.Query[parameter];
if (parameterValue.HasValue)
{
return bool.Parse(parameterValue.Value);
}
return defaultValue;
}
} }
} }

View File

@@ -49,12 +49,7 @@ namespace NzbDrone.Api.Frontend.Mappers
public override bool CanHandle(string resourceUrl) public override bool CanHandle(string resourceUrl)
{ {
resourceUrl = resourceUrl.ToLowerInvariant(); return !resourceUrl.Contains(".") && !resourceUrl.StartsWith("/login");
return !resourceUrl.StartsWith("/content") &&
!resourceUrl.StartsWith("/mediacover") &&
!resourceUrl.Contains(".") &&
!resourceUrl.StartsWith("/login");
} }
public override Response GetResponse(string resourceUrl) public override Response GetResponse(string resourceUrl)
@@ -118,4 +113,4 @@ namespace NzbDrone.Api.Frontend.Mappers
return _generatedContent; return _generatedContent;
} }
} }
} }

View File

@@ -1,4 +1,3 @@
using System;
using System.IO; using System.IO;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using NLog; using NLog;
@@ -43,7 +42,7 @@ namespace NzbDrone.Api.Frontend.Mappers
public override bool CanHandle(string resourceUrl) public override bool CanHandle(string resourceUrl)
{ {
return resourceUrl.StartsWith("/MediaCover", StringComparison.InvariantCultureIgnoreCase); return resourceUrl.StartsWith("/MediaCover");
} }
} }
} }

View File

@@ -1,4 +1,3 @@
using System;
using System.IO; using System.IO;
using NLog; using NLog;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
@@ -29,9 +28,7 @@ namespace NzbDrone.Api.Frontend.Mappers
public override bool CanHandle(string resourceUrl) public override bool CanHandle(string resourceUrl)
{ {
resourceUrl = resourceUrl.ToLowerInvariant(); return resourceUrl.StartsWith("/Content") ||
return resourceUrl.StartsWith("/content") ||
resourceUrl.EndsWith(".js") || resourceUrl.EndsWith(".js") ||
resourceUrl.EndsWith(".map") || resourceUrl.EndsWith(".map") ||
resourceUrl.EndsWith(".css") || resourceUrl.EndsWith(".css") ||
@@ -40,4 +37,4 @@ namespace NzbDrone.Api.Frontend.Mappers
resourceUrl.EndsWith("oauth.html"); resourceUrl.EndsWith("oauth.html");
} }
} }
} }

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Nancy.Responses;
using NLog; using NLog;
using Nancy; using Nancy;
using NzbDrone.Api.Frontend.Mappers; using NzbDrone.Api.Frontend.Mappers;

View File

@@ -1,10 +1,7 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using Nancy; using Nancy;
using NzbDrone.Api.Episodes; using NzbDrone.Api.Episodes;
using NzbDrone.Api.Extensions; using NzbDrone.Api.Extensions;
using NzbDrone.Api.REST;
using NzbDrone.Api.Series; using NzbDrone.Api.Series;
using NzbDrone.Core.Datastore; using NzbDrone.Core.Datastore;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
@@ -28,7 +25,6 @@ namespace NzbDrone.Api.History
_failedDownloadService = failedDownloadService; _failedDownloadService = failedDownloadService;
GetResourcePaged = GetHistory; GetResourcePaged = GetHistory;
Get["/since"] = x => GetHistorySince();
Post["/failed"] = x => MarkAsFailed(); Post["/failed"] = x => MarkAsFailed();
} }
@@ -68,27 +64,6 @@ namespace NzbDrone.Api.History
return ApplyToPage(_historyService.Paged, pagingSpec, MapToResource); return ApplyToPage(_historyService.Paged, pagingSpec, MapToResource);
} }
private List<HistoryResource> GetHistorySince()
{
var queryDate = Request.Query.Date;
var queryEventType = Request.Query.EventType;
if (!queryDate.HasValue)
{
throw new BadRequestException("date is missing");
}
DateTime date = DateTime.Parse(queryDate.Value);
HistoryEventType? eventType = null;
if (queryEventType.HasValue)
{
eventType = (HistoryEventType)Convert.ToInt32(queryEventType.Value);
}
return _historyService.Since(date, eventType).Select(MapToResource).ToList();
}
private Response MarkAsFailed() private Response MarkAsFailed()
{ {
var id = (int)Request.Form.Id; var id = (int)Request.Form.Id;

View File

@@ -1,16 +1,13 @@
using System.Collections.Generic; using Nancy;
using System.Linq;
using FluentValidation;
using Nancy;
using Nancy.ModelBinding; using Nancy.ModelBinding;
using NLog; using FluentValidation;
using NzbDrone.Api.Extensions;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.DecisionEngine; using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download; using NzbDrone.Core.Download;
using NzbDrone.Core.Indexers; using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Parser.Model; using NzbDrone.Core.Parser.Model;
using NzbDrone.Api.Extensions;
using NLog;
namespace NzbDrone.Api.Indexers namespace NzbDrone.Api.Indexers
{ {
@@ -18,17 +15,14 @@ namespace NzbDrone.Api.Indexers
{ {
private readonly IMakeDownloadDecision _downloadDecisionMaker; private readonly IMakeDownloadDecision _downloadDecisionMaker;
private readonly IProcessDownloadDecisions _downloadDecisionProcessor; private readonly IProcessDownloadDecisions _downloadDecisionProcessor;
private readonly IIndexerFactory _indexerFactory;
private readonly Logger _logger; private readonly Logger _logger;
public ReleasePushModule(IMakeDownloadDecision downloadDecisionMaker, public ReleasePushModule(IMakeDownloadDecision downloadDecisionMaker,
IProcessDownloadDecisions downloadDecisionProcessor, IProcessDownloadDecisions downloadDecisionProcessor,
IIndexerFactory indexerFactory,
Logger logger) Logger logger)
{ {
_downloadDecisionMaker = downloadDecisionMaker; _downloadDecisionMaker = downloadDecisionMaker;
_downloadDecisionProcessor = downloadDecisionProcessor; _downloadDecisionProcessor = downloadDecisionProcessor;
_indexerFactory = indexerFactory;
_logger = logger; _logger = logger;
Post["/push"] = x => ProcessRelease(this.Bind<ReleaseResource>()); Post["/push"] = x => ProcessRelease(this.Bind<ReleaseResource>());
@@ -47,47 +41,10 @@ namespace NzbDrone.Api.Indexers
info.Guid = "PUSH-" + info.DownloadUrl; info.Guid = "PUSH-" + info.DownloadUrl;
ResolveIndexer(info);
var decisions = _downloadDecisionMaker.GetRssDecision(new List<ReleaseInfo> { info }); var decisions = _downloadDecisionMaker.GetRssDecision(new List<ReleaseInfo> { info });
_downloadDecisionProcessor.ProcessDecisions(decisions); _downloadDecisionProcessor.ProcessDecisions(decisions);
return MapDecisions(decisions).First().AsResponse(); return MapDecisions(decisions).First().AsResponse();
} }
private void ResolveIndexer(ReleaseInfo release)
{
if (release.IndexerId == 0 && release.Indexer.IsNotNullOrWhiteSpace())
{
var indexer = _indexerFactory.All().FirstOrDefault(v => v.Name == release.Indexer);
if (indexer != null)
{
release.IndexerId = indexer.Id;
_logger.Debug("Push Release {0} associated with indexer {1} - {2}.", release.Title, release.IndexerId, release.Indexer);
}
else
{
_logger.Debug("Push Release {0} not associated with unknown indexer {1}.", release.Title, release.Indexer);
}
}
else if (release.IndexerId != 0 && release.Indexer.IsNullOrWhiteSpace())
{
try
{
var indexer = _indexerFactory.Get(release.IndexerId);
release.Indexer = indexer.Name;
_logger.Debug("Push Release {0} associated with indexer {1} - {2}.", release.Title, release.IndexerId, release.Indexer);
}
catch (ModelNotFoundException)
{
_logger.Debug("Push Release {0} not associated with unknown indexer {0}.", release.Title, release.IndexerId);
release.IndexerId = 0;
}
}
else
{
_logger.Debug("Push Release {0} not associated with an indexer.", release.Title);
}
}
} }
} }

View File

@@ -13,7 +13,6 @@ namespace NzbDrone.Api.ManualImport
{ {
public string Path { get; set; } public string Path { get; set; }
public string RelativePath { get; set; } public string RelativePath { get; set; }
public string FolderName { get; set; }
public string Name { get; set; } public string Name { get; set; }
public long Size { get; set; } public long Size { get; set; }
public SeriesResource Series { get; set; } public SeriesResource Series { get; set; }
@@ -37,7 +36,6 @@ namespace NzbDrone.Api.ManualImport
Path = model.Path, Path = model.Path,
RelativePath = model.RelativePath, RelativePath = model.RelativePath,
FolderName = model.FolderName,
Name = model.Name, Name = model.Name,
Size = model.Size, Size = model.Size,
Series = model.Series.ToResource(), Series = model.Series.ToResource(),

View File

@@ -34,6 +34,7 @@ namespace NzbDrone.Api
RegisterPipelines(pipelines); RegisterPipelines(pipelines);
container.Resolve<DatabaseTarget>().Register(); container.Resolve<DatabaseTarget>().Register();
container.Resolve<IEventAggregator>().PublishEvent(new ApplicationStartedEvent());
} }
private void RegisterPipelines(IPipelines pipelines) private void RegisterPipelines(IPipelines pipelines)
@@ -55,4 +56,4 @@ namespace NzbDrone.Api
protected override byte[] FavIcon => null; protected override byte[] FavIcon => null;
} }
} }

View File

@@ -70,13 +70,12 @@
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL"> <Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.5.3\lib\net40-client\NLog.dll</HintPath> <HintPath>..\packages\NLog.4.4.3\lib\net40\NLog.dll</HintPath>
</Reference> </Reference>
<Reference Include="NodaTime, Version=1.3.0.0, Culture=neutral, PublicKeyToken=4226afe0d9b296d1, processorArchitecture=MSIL"> <Reference Include="NodaTime, Version=1.3.0.0, Culture=neutral, PublicKeyToken=4226afe0d9b296d1, processorArchitecture=MSIL">
<HintPath>..\packages\Ical.Net.2.2.32\lib\net40\NodaTime.dll</HintPath> <HintPath>..\packages\Ical.Net.2.2.32\lib\net40\NodaTime.dll</HintPath>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Data" /> <Reference Include="System.Data" />
<Reference Include="Microsoft.CSharp" /> <Reference Include="Microsoft.CSharp" />
@@ -84,10 +83,6 @@
<SpecificVersion>False</SpecificVersion> <SpecificVersion>False</SpecificVersion>
<HintPath>..\Libraries\Sqlite\System.Data.SQLite.dll</HintPath> <HintPath>..\Libraries\Sqlite\System.Data.SQLite.dll</HintPath>
</Reference> </Reference>
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.ServiceModel" />
<Reference Include="System.Transactions" />
<Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="..\NzbDrone.Common\Properties\SharedAssemblyInfo.cs"> <Compile Include="..\NzbDrone.Common\Properties\SharedAssemblyInfo.cs">
@@ -104,13 +99,11 @@
<Compile Include="Calendar\CalendarModule.cs" /> <Compile Include="Calendar\CalendarModule.cs" />
<Compile Include="ClientSchema\Field.cs" /> <Compile Include="ClientSchema\Field.cs" />
<Compile Include="ClientSchema\FieldDefinitionAttribute.cs" /> <Compile Include="ClientSchema\FieldDefinitionAttribute.cs" />
<Compile Include="ClientSchema\FieldMapping.cs" />
<Compile Include="ClientSchema\SchemaBuilder.cs" /> <Compile Include="ClientSchema\SchemaBuilder.cs" />
<Compile Include="ClientSchema\SchemaDeserializer.cs" /> <Compile Include="ClientSchema\SchemaDeserializer.cs" />
<Compile Include="ClientSchema\SelectOption.cs" /> <Compile Include="ClientSchema\SelectOption.cs" />
<Compile Include="Commands\CommandModule.cs" /> <Compile Include="Commands\CommandModule.cs" />
<Compile Include="Commands\CommandResource.cs" /> <Compile Include="Commands\CommandResource.cs" />
<Compile Include="EpisodeFiles\MediaInfoResource.cs" />
<Compile Include="Extensions\AccessControlHeaders.cs" /> <Compile Include="Extensions\AccessControlHeaders.cs" />
<Compile Include="Extensions\Pipelines\CorsPipeline.cs" /> <Compile Include="Extensions\Pipelines\CorsPipeline.cs" />
<Compile Include="Extensions\Pipelines\UrlBasePipeline.cs" /> <Compile Include="Extensions\Pipelines\UrlBasePipeline.cs" />

View File

@@ -210,12 +210,7 @@ namespace NzbDrone.Api
protected void VerifyValidationResult(ValidationResult validationResult, bool includeWarnings) protected void VerifyValidationResult(ValidationResult validationResult, bool includeWarnings)
{ {
var result = validationResult as NzbDroneValidationResult; var result = new NzbDroneValidationResult(validationResult.Errors);
if (result == null)
{
result = new NzbDroneValidationResult(validationResult.Errors);
}
if (includeWarnings && (!result.IsValid || result.HasWarnings)) if (includeWarnings && (!result.IsValid || result.HasWarnings))
{ {

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using FluentValidation; using FluentValidation;
using NzbDrone.Core.RootFolders; using NzbDrone.Core.RootFolders;
using NzbDrone.Core.Validation.Paths; using NzbDrone.Core.Validation.Paths;
@@ -17,9 +17,7 @@ namespace NzbDrone.Api.RootFolders
DroneFactoryValidator droneFactoryValidator, DroneFactoryValidator droneFactoryValidator,
MappedNetworkDriveValidator mappedNetworkDriveValidator, MappedNetworkDriveValidator mappedNetworkDriveValidator,
StartupFolderValidator startupFolderValidator, StartupFolderValidator startupFolderValidator,
SystemFolderValidator systemFolderValidator, FolderWritableValidator folderWritableValidator)
FolderWritableValidator folderWritableValidator
)
: base(signalRBroadcaster) : base(signalRBroadcaster)
{ {
_rootFolderService = rootFolderService; _rootFolderService = rootFolderService;
@@ -37,7 +35,6 @@ namespace NzbDrone.Api.RootFolders
.SetValidator(mappedNetworkDriveValidator) .SetValidator(mappedNetworkDriveValidator)
.SetValidator(startupFolderValidator) .SetValidator(startupFolderValidator)
.SetValidator(pathExistsValidator) .SetValidator(pathExistsValidator)
.SetValidator(systemFolderValidator)
.SetValidator(folderWritableValidator); .SetValidator(folderWritableValidator);
} }
@@ -63,4 +60,4 @@ namespace NzbDrone.Api.RootFolders
_rootFolderService.Remove(id); _rootFolderService.Remove(id);
} }
} }
} }

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NzbDrone.Api.REST; using NzbDrone.Api.REST;
using NzbDrone.Core.RootFolders; using NzbDrone.Core.RootFolders;
@@ -9,7 +9,6 @@ namespace NzbDrone.Api.RootFolders
{ {
public string Path { get; set; } public string Path { get; set; }
public long? FreeSpace { get; set; } public long? FreeSpace { get; set; }
public long? TotalSpace { get; set; }
public List<UnmappedFolder> UnmappedFolders { get; set; } public List<UnmappedFolder> UnmappedFolders { get; set; }
} }
@@ -26,7 +25,6 @@ namespace NzbDrone.Api.RootFolders
Path = model.Path, Path = model.Path,
FreeSpace = model.FreeSpace, FreeSpace = model.FreeSpace,
TotalSpace = model.TotalSpace,
UnmappedFolders = model.UnmappedFolders UnmappedFolders = model.UnmappedFolders
}; };
} }
@@ -50,4 +48,4 @@ namespace NzbDrone.Api.RootFolders
return models.Select(ToResource).ToList(); return models.Select(ToResource).ToList();
} }
} }
} }

View File

@@ -1,6 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.Tv; using NzbDrone.Core.Tv;
namespace NzbDrone.Api.Series namespace NzbDrone.Api.Series
{ {
@@ -9,20 +8,18 @@ namespace NzbDrone.Api.Series
public int SeasonNumber { get; set; } public int SeasonNumber { get; set; }
public bool Monitored { get; set; } public bool Monitored { get; set; }
public SeasonStatisticsResource Statistics { get; set; } public SeasonStatisticsResource Statistics { get; set; }
public List<MediaCover> Images { get; set; }
} }
public static class SeasonResourceMapper public static class SeasonResourceMapper
{ {
public static SeasonResource ToResource(this Season model, bool includeImages = false) public static SeasonResource ToResource(this Season model)
{ {
if (model == null) return null; if (model == null) return null;
return new SeasonResource return new SeasonResource
{ {
SeasonNumber = model.SeasonNumber, SeasonNumber = model.SeasonNumber,
Monitored = model.Monitored, Monitored = model.Monitored
Images = includeImages ? model.Images : null
}; };
} }
@@ -33,14 +30,13 @@ namespace NzbDrone.Api.Series
return new Season return new Season
{ {
SeasonNumber = resource.SeasonNumber, SeasonNumber = resource.SeasonNumber,
Monitored = resource.Monitored, Monitored = resource.Monitored
Images = resource.Images
}; };
} }
public static List<SeasonResource> ToResource(this IEnumerable<Season> models, bool includeImages = false) public static List<SeasonResource> ToResource(this IEnumerable<Season> models)
{ {
return models.Select(s => ToResource(s, includeImages)).ToList(); return models.Select(ToResource).ToList();
} }
public static List<Season> ToModel(this IEnumerable<SeasonResource> resources) public static List<Season> ToModel(this IEnumerable<SeasonResource> resources)

View File

@@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Nancy; using Nancy;
using NzbDrone.Api.Extensions; using NzbDrone.Api.Extensions;
@@ -24,7 +24,7 @@ namespace NzbDrone.Api.Series
var series = resources.Select(seriesResource => seriesResource.ToModel(_seriesService.GetSeries(seriesResource.Id))).ToList(); var series = resources.Select(seriesResource => seriesResource.ToModel(_seriesService.GetSeries(seriesResource.Id))).ToList();
return _seriesService.UpdateSeries(series) return _seriesService.UpdateSeries(series)
.ToResource(false) .ToResource()
.AsResponse(HttpStatusCode.Accepted); .AsResponse(HttpStatusCode.Accepted);
} }
} }

View File

@@ -1,8 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using FluentValidation; using FluentValidation;
using NzbDrone.Api.Extensions;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore.Events; using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.MediaCover; using NzbDrone.Core.MediaCover;
@@ -46,7 +45,6 @@ namespace NzbDrone.Api.Series
SeriesExistsValidator seriesExistsValidator, SeriesExistsValidator seriesExistsValidator,
DroneFactoryValidator droneFactoryValidator, DroneFactoryValidator droneFactoryValidator,
SeriesAncestorValidator seriesAncestorValidator, SeriesAncestorValidator seriesAncestorValidator,
SystemFolderValidator systemFolderValidator,
ProfileExistsValidator profileExistsValidator ProfileExistsValidator profileExistsValidator
) )
: base(signalRBroadcaster) : base(signalRBroadcaster)
@@ -73,7 +71,6 @@ namespace NzbDrone.Api.Series
.SetValidator(seriesPathValidator) .SetValidator(seriesPathValidator)
.SetValidator(droneFactoryValidator) .SetValidator(droneFactoryValidator)
.SetValidator(seriesAncestorValidator) .SetValidator(seriesAncestorValidator)
.SetValidator(systemFolderValidator)
.When(s => !s.Path.IsNullOrWhiteSpace()); .When(s => !s.Path.IsNullOrWhiteSpace());
SharedValidator.RuleFor(s => s.ProfileId).SetValidator(profileExistsValidator); SharedValidator.RuleFor(s => s.ProfileId).SetValidator(profileExistsValidator);
@@ -87,17 +84,26 @@ namespace NzbDrone.Api.Series
private SeriesResource GetSeries(int id) private SeriesResource GetSeries(int id)
{ {
var includeSeasonImages = Context != null && Request.GetBooleanQueryParameter("includeSeasonImages");
var series = _seriesService.GetSeries(id); var series = _seriesService.GetSeries(id);
return MapToResource(series, includeSeasonImages); return MapToResource(series);
}
private SeriesResource MapToResource(Core.Tv.Series series)
{
if (series == null) return null;
var resource = series.ToResource();
MapCoversToLocal(resource);
FetchAndLinkSeriesStatistics(resource);
PopulateAlternateTitles(resource);
return resource;
} }
private List<SeriesResource> AllSeries() private List<SeriesResource> AllSeries()
{ {
var includeSeasonImages = Request.GetBooleanQueryParameter("includeSeasonImages");
var seriesStats = _seriesStatisticsService.SeriesStatistics(); var seriesStats = _seriesStatisticsService.SeriesStatistics();
var seriesResources = _seriesService.GetAllSeries().Select(s => s.ToResource(includeSeasonImages)).ToList(); var seriesResources = _seriesService.GetAllSeries().ToResource();
MapCoversToLocal(seriesResources.ToArray()); MapCoversToLocal(seriesResources.ToArray());
LinkSeriesStatistics(seriesResources, seriesStats); LinkSeriesStatistics(seriesResources, seriesStats);
@@ -135,18 +141,6 @@ namespace NzbDrone.Api.Series
_seriesService.DeleteSeries(id, deleteFiles); _seriesService.DeleteSeries(id, deleteFiles);
} }
private SeriesResource MapToResource(Core.Tv.Series series, bool includeSeasonImages)
{
if (series == null) return null;
var resource = series.ToResource(includeSeasonImages);
MapCoversToLocal(resource);
FetchAndLinkSeriesStatistics(resource);
PopulateAlternateTitles(resource);
return resource;
}
private void MapCoversToLocal(params SeriesResource[] series) private void MapCoversToLocal(params SeriesResource[] series)
{ {
foreach (var seriesResource in series) foreach (var seriesResource in series)

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NzbDrone.Api.REST; using NzbDrone.Api.REST;
@@ -97,7 +97,7 @@ namespace NzbDrone.Api.Series
public static class SeriesResourceMapper public static class SeriesResourceMapper
{ {
public static SeriesResource ToResource(this Core.Tv.Series model, bool includeSeasonImages = false) public static SeriesResource ToResource(this Core.Tv.Series model)
{ {
if (model == null) return null; if (model == null) return null;
@@ -121,7 +121,7 @@ namespace NzbDrone.Api.Series
AirTime = model.AirTime, AirTime = model.AirTime,
Images = model.Images, Images = model.Images,
Seasons = model.Seasons.ToResource(includeSeasonImages), Seasons = model.Seasons.ToResource(),
Year = model.Year, Year = model.Year,
Path = model.Path, Path = model.Path,
@@ -214,9 +214,9 @@ namespace NzbDrone.Api.Series
return series; return series;
} }
public static List<SeriesResource> ToResource(this IEnumerable<Core.Tv.Series> series, bool includeSeasonImages) public static List<SeriesResource> ToResource(this IEnumerable<Core.Tv.Series> series)
{ {
return series.Select(s => ToResource(s, includeSeasonImages)).ToList(); return series.Select(ToResource).ToList();
} }
} }
} }

View File

@@ -6,5 +6,5 @@
<package id="Nancy.Authentication.Basic" version="1.4.1" targetFramework="net40" /> <package id="Nancy.Authentication.Basic" version="1.4.1" targetFramework="net40" />
<package id="Nancy.Authentication.Forms" version="1.4.1" targetFramework="net40" /> <package id="Nancy.Authentication.Forms" version="1.4.1" targetFramework="net40" />
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net40" /> <package id="Newtonsoft.Json" version="9.0.1" targetFramework="net40" />
<package id="NLog" version="4.5.3" targetFramework="net40" /> <package id="NLog" version="4.4.3" targetFramework="net40" />
</packages> </packages>

View File

@@ -47,27 +47,20 @@
<Reference Include="FluentAssertions.Core, Version=4.19.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL"> <Reference Include="FluentAssertions.Core, Version=4.19.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
<HintPath>..\packages\FluentAssertions.4.19.0\lib\net40\FluentAssertions.Core.dll</HintPath> <HintPath>..\packages\FluentAssertions.4.19.0\lib\net40\FluentAssertions.Core.dll</HintPath>
</Reference> </Reference>
<Reference Include="Microsoft.CSharp" />
<Reference Include="Moq, Version=4.0.10827.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
<HintPath>..\packages\Moq.4.0.10827\lib\NET40\Moq.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL"> <Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.5.3\lib\net40-client\NLog.dll</HintPath> <HintPath>..\packages\NLog.4.4.3\lib\net40\NLog.dll</HintPath>
</Reference> </Reference>
<Reference Include="nunit.framework, Version=3.6.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL"> <Reference Include="nunit.framework, Version=3.6.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>..\packages\NUnit.3.6.0\lib\net40\nunit.framework.dll</HintPath> <HintPath>..\packages\NUnit.3.6.0\lib\net40\nunit.framework.dll</HintPath>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Data" />
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.ServiceModel" />
<Reference Include="System.ServiceProcess" /> <Reference Include="System.ServiceProcess" />
<Reference Include="System.Transactions" />
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
<Reference Include="System.Xml.Linq" /> <Reference Include="System.Xml.Linq" />
<Reference Include="Moq">
<HintPath>..\packages\Moq.4.0.10827\lib\NET40\Moq.dll</HintPath>
</Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="ContainerFixture.cs" /> <Compile Include="ContainerFixture.cs" />

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="FluentAssertions" version="4.19.0" targetFramework="net40" /> <package id="FluentAssertions" version="4.19.0" targetFramework="net40" />
<package id="Moq" version="4.0.10827" targetFramework="net40" /> <package id="Moq" version="4.0.10827" />
<package id="NBuilder" version="4.0.0" targetFramework="net40" /> <package id="NBuilder" version="4.0.0" targetFramework="net40" />
<package id="NLog" version="4.5.3" targetFramework="net40" /> <package id="NLog" version="4.4.3" targetFramework="net40" />
<package id="NUnit" version="3.6.0" targetFramework="net40" /> <package id="NUnit" version="3.6.0" targetFramework="net40" />
</packages> </packages>

View File

@@ -45,20 +45,16 @@
<HintPath>..\packages\FluentAssertions.4.19.0\lib\net40\FluentAssertions.Core.dll</HintPath> <HintPath>..\packages\FluentAssertions.4.19.0\lib\net40\FluentAssertions.Core.dll</HintPath>
</Reference> </Reference>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL"> <Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.5.3\lib\net40-client\NLog.dll</HintPath> <HintPath>..\packages\NLog.4.4.3\lib\net40\NLog.dll</HintPath>
</Reference> </Reference>
<Reference Include="nunit.framework, Version=3.6.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL"> <Reference Include="nunit.framework, Version=3.6.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>..\packages\NUnit.3.6.0\lib\net40\nunit.framework.dll</HintPath> <HintPath>..\packages\NUnit.3.6.0\lib\net40\nunit.framework.dll</HintPath>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Data" /> <Reference Include="System.Data" />
<Reference Include="System.Data.DataSetExtensions" /> <Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Drawing" /> <Reference Include="System.Drawing" />
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.ServiceModel" />
<Reference Include="System.Transactions" />
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
<Reference Include="System.Xml.Linq" /> <Reference Include="System.Xml.Linq" />
<Reference Include="Microsoft.CSharp" /> <Reference Include="Microsoft.CSharp" />

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="FluentAssertions" version="4.19.0" targetFramework="net40" /> <package id="FluentAssertions" version="4.19.0" targetFramework="net40" />
<package id="NLog" version="4.5.3" targetFramework="net40" /> <package id="NLog" version="4.4.3" targetFramework="net40" />
<package id="NUnit" version="3.6.0" targetFramework="net40" /> <package id="NUnit" version="3.6.0" targetFramework="net40" />
<package id="Selenium.Support" version="3.2.0" targetFramework="net40" /> <package id="Selenium.Support" version="3.2.0" targetFramework="net40" />
<package id="Selenium.WebDriver" version="3.2.0" targetFramework="net40" /> <package id="Selenium.WebDriver" version="3.2.0" targetFramework="net40" />

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using Moq; using Moq;
@@ -238,7 +238,7 @@ namespace NzbDrone.Common.Test.DiskTests
WithExistingFile(_targetPath); WithExistingFile(_targetPath);
Assert.Throws<DestinationAlreadyExistsException>(() => Subject.TransferFile(_sourcePath, _targetPath, TransferMode.Move, false)); Assert.Throws<IOException>(() => Subject.TransferFile(_sourcePath, _targetPath, TransferMode.Move, false));
Mocker.GetMock<IDiskProvider>() Mocker.GetMock<IDiskProvider>()
.Verify(v => v.DeleteFile(_targetPath), Times.Never()); .Verify(v => v.DeleteFile(_targetPath), Times.Never());

View File

@@ -1,8 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq;
using System.Net; using System.Net;
using System.Threading; using System.Threading;
using FluentAssertions; using FluentAssertions;
@@ -25,60 +24,13 @@ namespace NzbDrone.Common.Test.Http
[TestFixture(typeof(CurlHttpDispatcher))] [TestFixture(typeof(CurlHttpDispatcher))]
public class HttpClientFixture<TDispatcher> : TestBase<HttpClient> where TDispatcher : IHttpDispatcher public class HttpClientFixture<TDispatcher> : TestBase<HttpClient> where TDispatcher : IHttpDispatcher
{ {
private string[] _httpBinHosts; private static string[] _httpBinHosts = new[] { "eu.httpbin.org", "httpbin.org" };
private int _httpBinSleep; private static int _httpBinRandom;
private int _httpBinRandom;
private string _httpBinHost; private string _httpBinHost;
private string _httpBinHost2;
[OneTimeSetUp]
public void FixtureSetUp()
{
var candidates = new[] { "eu.httpbin.org", /*"httpbin.org",*/ "www.httpbin.org" };
// httpbin.org is broken right now, occassionally redirecting to https if it's unavailable.
_httpBinHosts = candidates.Where(IsTestSiteAvailable).ToArray();
TestLogger.Info($"{candidates.Length} TestSites available.");
_httpBinSleep = _httpBinHosts.Count() < 2 ? 100 : 10;
}
private bool IsTestSiteAvailable(string site)
{
try
{
var req = WebRequest.Create($"http://{site}/get") as HttpWebRequest;
var res = req.GetResponse() as HttpWebResponse;
if (res.StatusCode != HttpStatusCode.OK) return false;
try
{
req = WebRequest.Create($"http://{site}/status/429") as HttpWebRequest;
res = req.GetResponse() as HttpWebResponse;
}
catch (WebException ex)
{
res = ex.Response as HttpWebResponse;
}
if (res == null || res.StatusCode != (HttpStatusCode)429) return false;
return true;
}
catch
{
return false;
}
}
[SetUp] [SetUp]
public void SetUp() public void SetUp()
{ {
if (!_httpBinHosts.Any())
{
Assert.Inconclusive("No TestSites available");
}
Mocker.GetMock<IPlatformInfo>().Setup(c => c.Version).Returns(new Version("1.0.0")); Mocker.GetMock<IPlatformInfo>().Setup(c => c.Version).Returns(new Version("1.0.0"));
Mocker.GetMock<IOsInfo>().Setup(c => c.Name).Returns("TestOS"); Mocker.GetMock<IOsInfo>().Setup(c => c.Name).Returns("TestOS");
Mocker.GetMock<IOsInfo>().Setup(c => c.Version).Returns("9.0.0"); Mocker.GetMock<IOsInfo>().Setup(c => c.Version).Returns("9.0.0");
@@ -98,13 +50,6 @@ namespace NzbDrone.Common.Test.Http
// Roundrobin over the two servers, to reduce the chance of hitting the ratelimiter. // Roundrobin over the two servers, to reduce the chance of hitting the ratelimiter.
_httpBinHost = _httpBinHosts[_httpBinRandom++ % _httpBinHosts.Length]; _httpBinHost = _httpBinHosts[_httpBinRandom++ % _httpBinHosts.Length];
_httpBinHost2 = _httpBinHosts[_httpBinRandom % _httpBinHosts.Length];
}
[TearDown]
public void TearDown()
{
Thread.Sleep(_httpBinSleep);
} }
[Test] [Test]
@@ -130,12 +75,11 @@ namespace NzbDrone.Common.Test.Http
[Test] [Test]
public void should_execute_typed_get() public void should_execute_typed_get()
{ {
var request = new HttpRequest($"http://{_httpBinHost}/get?test=1"); var request = new HttpRequest($"http://{_httpBinHost}/get");
var response = Subject.Get<HttpBinResource>(request); var response = Subject.Get<HttpBinResource>(request);
response.Resource.Url.EndsWith("/get?test=1"); response.Resource.Url.Should().Be(request.Url.FullUri);
response.Resource.Args.Should().Contain("test", "1");
} }
[Test] [Test]
@@ -301,12 +245,7 @@ namespace NzbDrone.Common.Test.Http
public void GivenOldCookie() public void GivenOldCookie()
{ {
if (_httpBinHost == _httpBinHost2) var oldRequest = new HttpRequest("http://eu.httpbin.org/get");
{
Assert.Inconclusive("Need both httpbin.org and eu.httpbin.org to run this test.");
}
var oldRequest = new HttpRequest($"http://{_httpBinHost2}/get");
oldRequest.Cookies["my"] = "cookie"; oldRequest.Cookies["my"] = "cookie";
var oldClient = new HttpClient(new IHttpRequestInterceptor[0], Mocker.Resolve<ICacheManager>(), Mocker.Resolve<IRateLimitService>(), Mocker.Resolve<IHttpDispatcher>(), Mocker.GetMock<IUserAgentBuilder>().Object, Mocker.Resolve<Logger>()); var oldClient = new HttpClient(new IHttpRequestInterceptor[0], Mocker.Resolve<ICacheManager>(), Mocker.Resolve<IRateLimitService>(), Mocker.Resolve<IHttpDispatcher>(), Mocker.GetMock<IUserAgentBuilder>().Object, Mocker.Resolve<Logger>());
@@ -323,7 +262,7 @@ namespace NzbDrone.Common.Test.Http
{ {
GivenOldCookie(); GivenOldCookie();
var request = new HttpRequest($"http://{_httpBinHost2}/get"); var request = new HttpRequest("http://eu.httpbin.org/get");
var response = Subject.Get<HttpBinResource>(request); var response = Subject.Get<HttpBinResource>(request);
@@ -339,103 +278,26 @@ namespace NzbDrone.Common.Test.Http
{ {
GivenOldCookie(); GivenOldCookie();
var request = new HttpRequest($"http://{_httpBinHost}/get"); var request = new HttpRequest("http://httpbin.org/get");
var response = Subject.Get<HttpBinResource>(request); var response = Subject.Get<HttpBinResource>(request);
response.Resource.Headers.Should().NotContainKey("Cookie"); response.Resource.Headers.Should().NotContainKey("Cookie");
} }
[Test]
public void should_not_store_request_cookie()
{
var requestGet = new HttpRequest($"http://{_httpBinHost}/get");
requestGet.Cookies.Add("my", "cookie");
requestGet.AllowAutoRedirect = false;
requestGet.StoreRequestCookie = false;
requestGet.StoreResponseCookie = false;
var responseGet = Subject.Get<HttpBinResource>(requestGet);
var requestCookies = new HttpRequest($"http://{_httpBinHost}/cookies");
requestCookies.AllowAutoRedirect = false;
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
responseCookies.Resource.Cookies.Should().BeEmpty();
ExceptionVerification.IgnoreErrors();
}
[Test]
public void should_store_request_cookie()
{
var requestGet = new HttpRequest($"http://{_httpBinHost}/get");
requestGet.Cookies.Add("my", "cookie");
requestGet.AllowAutoRedirect = false;
requestGet.StoreRequestCookie.Should().BeTrue();
requestGet.StoreResponseCookie = false;
var responseGet = Subject.Get<HttpBinResource>(requestGet);
var requestCookies = new HttpRequest($"http://{_httpBinHost}/cookies");
requestCookies.AllowAutoRedirect = false;
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
ExceptionVerification.IgnoreErrors();
}
[Test]
public void should_delete_request_cookie()
{
var requestDelete = new HttpRequest($"http://{_httpBinHost}/cookies/delete?my");
requestDelete.Cookies.Add("my", "cookie");
requestDelete.AllowAutoRedirect = true;
requestDelete.StoreRequestCookie = false;
requestDelete.StoreResponseCookie = false;
// Delete and redirect since that's the only way to check the internal temporary cookie container
var responseCookies = Subject.Get<HttpCookieResource>(requestDelete);
responseCookies.Resource.Cookies.Should().BeEmpty();
}
[Test]
public void should_clear_request_cookie()
{
var requestSet = new HttpRequest($"http://{_httpBinHost}/cookies");
requestSet.Cookies.Add("my", "cookie");
requestSet.AllowAutoRedirect = false;
requestSet.StoreRequestCookie = true;
requestSet.StoreResponseCookie = false;
var responseSet = Subject.Get<HttpCookieResource>(requestSet);
var requestClear = new HttpRequest($"http://{_httpBinHost}/cookies");
requestClear.Cookies.Add("my", null);
requestClear.AllowAutoRedirect = false;
requestClear.StoreRequestCookie = true;
requestClear.StoreResponseCookie = false;
var responseClear = Subject.Get<HttpCookieResource>(requestClear);
responseClear.Resource.Cookies.Should().BeEmpty();
}
[Test] [Test]
public void should_not_store_response_cookie() public void should_not_store_response_cookie()
{ {
var requestSet = new HttpRequest($"http://{_httpBinHost}/cookies/set?my=cookie"); var requestSet = new HttpRequest($"http://{_httpBinHost}/cookies/set?my=cookie");
requestSet.AllowAutoRedirect = false; requestSet.AllowAutoRedirect = false;
requestSet.StoreRequestCookie = false;
requestSet.StoreResponseCookie.Should().BeFalse();
var responseSet = Subject.Get(requestSet); var responseSet = Subject.Get(requestSet);
var requestCookies = new HttpRequest($"http://{_httpBinHost}/cookies"); var request = new HttpRequest($"http://{_httpBinHost}/get");
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies); var response = Subject.Get<HttpBinResource>(request);
responseCookies.Resource.Cookies.Should().BeEmpty(); response.Resource.Headers.Should().NotContainKey("Cookie");
ExceptionVerification.IgnoreErrors(); ExceptionVerification.IgnoreErrors();
} }
@@ -445,31 +307,19 @@ namespace NzbDrone.Common.Test.Http
{ {
var requestSet = new HttpRequest($"http://{_httpBinHost}/cookies/set?my=cookie"); var requestSet = new HttpRequest($"http://{_httpBinHost}/cookies/set?my=cookie");
requestSet.AllowAutoRedirect = false; requestSet.AllowAutoRedirect = false;
requestSet.StoreRequestCookie = false;
requestSet.StoreResponseCookie = true; requestSet.StoreResponseCookie = true;
var responseSet = Subject.Get(requestSet); var responseSet = Subject.Get(requestSet);
var requestCookies = new HttpRequest($"http://{_httpBinHost}/cookies"); var request = new HttpRequest($"http://{_httpBinHost}/get");
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies); var response = Subject.Get<HttpBinResource>(request);
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie"); response.Resource.Headers.Should().ContainKey("Cookie");
ExceptionVerification.IgnoreErrors(); var cookie = response.Resource.Headers["Cookie"].ToString();
}
[Test] cookie.Should().Contain("my=cookie");
public void should_temp_store_response_cookie()
{
var requestSet = new HttpRequest($"http://{_httpBinHost}/cookies/set?my=cookie");
requestSet.AllowAutoRedirect = true;
requestSet.StoreRequestCookie = false;
requestSet.StoreResponseCookie.Should().BeFalse();
var responseSet = Subject.Get<HttpCookieResource>(requestSet);
// Set and redirect since that's the only way to check the internal temporary cookie container
responseSet.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
ExceptionVerification.IgnoreErrors(); ExceptionVerification.IgnoreErrors();
} }
@@ -478,129 +328,21 @@ namespace NzbDrone.Common.Test.Http
public void should_overwrite_response_cookie() public void should_overwrite_response_cookie()
{ {
var requestSet = new HttpRequest($"http://{_httpBinHost}/cookies/set?my=cookie"); var requestSet = new HttpRequest($"http://{_httpBinHost}/cookies/set?my=cookie");
requestSet.Cookies.Add("my", "oldcookie");
requestSet.AllowAutoRedirect = false; requestSet.AllowAutoRedirect = false;
requestSet.StoreRequestCookie = false;
requestSet.StoreResponseCookie = true; requestSet.StoreResponseCookie = true;
requestSet.Cookies["my"] = "oldcookie";
var responseSet = Subject.Get(requestSet); var responseSet = Subject.Get(requestSet);
var requestCookies = new HttpRequest($"http://{_httpBinHost}/cookies"); var request = new HttpRequest($"http://{_httpBinHost}/get");
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies); var response = Subject.Get<HttpBinResource>(request);
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie"); response.Resource.Headers.Should().ContainKey("Cookie");
ExceptionVerification.IgnoreErrors(); var cookie = response.Resource.Headers["Cookie"].ToString();
}
[Test] cookie.Should().Contain("my=cookie");
public void should_overwrite_temp_response_cookie()
{
var requestSet = new HttpRequest($"http://{_httpBinHost}/cookies/set?my=cookie");
requestSet.Cookies.Add("my", "oldcookie");
requestSet.AllowAutoRedirect = true;
requestSet.StoreRequestCookie = true;
requestSet.StoreResponseCookie = false;
var responseSet = Subject.Get<HttpCookieResource>(requestSet);
responseSet.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
var requestCookies = new HttpRequest($"http://{_httpBinHost}/cookies");
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "oldcookie");
ExceptionVerification.IgnoreErrors();
}
[Test]
public void should_not_delete_response_cookie()
{
var requestCookies = new HttpRequest($"http://{_httpBinHost}/cookies");
requestCookies.Cookies.Add("my", "cookie");
requestCookies.AllowAutoRedirect = false;
requestCookies.StoreRequestCookie = true;
requestCookies.StoreResponseCookie = false;
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
var requestDelete = new HttpRequest($"http://{_httpBinHost}/cookies/delete?my");
requestDelete.AllowAutoRedirect = false;
requestDelete.StoreRequestCookie = false;
requestDelete.StoreResponseCookie = false;
var responseDelete = Subject.Get(requestDelete);
requestCookies = new HttpRequest($"http://{_httpBinHost}/cookies");
requestCookies.StoreRequestCookie = false;
requestCookies.StoreResponseCookie = false;
responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
ExceptionVerification.IgnoreErrors();
}
[Test]
public void should_delete_response_cookie()
{
var requestCookies = new HttpRequest($"http://{_httpBinHost}/cookies");
requestCookies.Cookies.Add("my", "cookie");
requestCookies.AllowAutoRedirect = false;
requestCookies.StoreRequestCookie = true;
requestCookies.StoreResponseCookie = false;
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
var requestDelete = new HttpRequest($"http://{_httpBinHost}/cookies/delete?my");
requestDelete.AllowAutoRedirect = false;
requestDelete.StoreRequestCookie = false;
requestDelete.StoreResponseCookie = true;
var responseDelete = Subject.Get(requestDelete);
requestCookies = new HttpRequest($"http://{_httpBinHost}/cookies");
requestCookies.StoreRequestCookie = false;
requestCookies.StoreResponseCookie = false;
responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
responseCookies.Resource.Cookies.Should().BeEmpty();
ExceptionVerification.IgnoreErrors();
}
[Test]
public void should_delete_temp_response_cookie()
{
var requestCookies = new HttpRequest($"http://{_httpBinHost}/cookies");
requestCookies.Cookies.Add("my", "cookie");
requestCookies.AllowAutoRedirect = false;
requestCookies.StoreRequestCookie = true;
requestCookies.StoreResponseCookie = false;
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
var requestDelete = new HttpRequest($"http://{_httpBinHost}/cookies/delete?my");
requestDelete.AllowAutoRedirect = true;
requestDelete.StoreRequestCookie = false;
requestDelete.StoreResponseCookie = false;
var responseDelete = Subject.Get<HttpCookieResource>(requestDelete);
responseDelete.Resource.Cookies.Should().BeEmpty();
requestCookies = new HttpRequest($"http://{_httpBinHost}/cookies");
requestCookies.StoreRequestCookie = false;
requestCookies.StoreResponseCookie = false;
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
ExceptionVerification.IgnoreErrors(); ExceptionVerification.IgnoreErrors();
} }
@@ -707,15 +449,9 @@ namespace NzbDrone.Common.Test.Http
public class HttpBinResource public class HttpBinResource
{ {
public Dictionary<string, object> Args { get; set; }
public Dictionary<string, object> Headers { get; set; } public Dictionary<string, object> Headers { get; set; }
public string Origin { get; set; } public string Origin { get; set; }
public string Url { get; set; } public string Url { get; set; }
public string Data { get; set; } public string Data { get; set; }
} }
public class HttpCookieResource
{
public Dictionary<string, string> Cookies { get; set; }
}
} }

View File

@@ -43,28 +43,23 @@
<Reference Include="FluentAssertions.Core, Version=4.19.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL"> <Reference Include="FluentAssertions.Core, Version=4.19.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
<HintPath>..\packages\FluentAssertions.4.19.0\lib\net40\FluentAssertions.Core.dll</HintPath> <HintPath>..\packages\FluentAssertions.4.19.0\lib\net40\FluentAssertions.Core.dll</HintPath>
</Reference> </Reference>
<Reference Include="Moq, Version=4.0.10827.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
<HintPath>..\packages\Moq.4.0.10827\lib\NET40\Moq.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL"> <Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.5.3\lib\net40-client\NLog.dll</HintPath> <HintPath>..\packages\NLog.4.4.3\lib\net40\NLog.dll</HintPath>
</Reference> </Reference>
<Reference Include="nunit.framework, Version=3.6.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL"> <Reference Include="nunit.framework, Version=3.6.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>..\packages\NUnit.3.6.0\lib\net40\nunit.framework.dll</HintPath> <HintPath>..\packages\NUnit.3.6.0\lib\net40\nunit.framework.dll</HintPath>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Data" /> <Reference Include="System.Data" />
<Reference Include="System.Data.DataSetExtensions" /> <Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.ServiceModel" />
<Reference Include="System.ServiceProcess" /> <Reference Include="System.ServiceProcess" />
<Reference Include="System.Transactions" />
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
<Reference Include="System.Xml.Linq" /> <Reference Include="System.Xml.Linq" />
<Reference Include="Microsoft.CSharp" /> <Reference Include="Microsoft.CSharp" />
<Reference Include="Moq">
<HintPath>..\packages\Moq.4.0.10827\lib\NET40\Moq.dll</HintPath>
</Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="CacheTests\CachedDictionaryFixture.cs" /> <Compile Include="CacheTests\CachedDictionaryFixture.cs" />

View File

@@ -138,34 +138,18 @@ namespace NzbDrone.Common.Test
} }
[TestCase(@"C:\Test\mydir", @"C:\Test")] [TestCase(@"C:\Test\mydir", @"C:\Test")]
[TestCase(@"C:\Test\", @"C:\")] [TestCase(@"C:\Test\", @"C:")]
[TestCase(@"C:\", null)] [TestCase(@"C:\", null)]
[TestCase(@"\\server\share", null)] public void path_should_return_parent(string path, string parentPath)
[TestCase(@"\\server\share\test", @"\\server\share")]
public void path_should_return_parent_windows(string path, string parentPath)
{ {
WindowsOnly();
path.GetParentPath().Should().Be(parentPath);
}
[TestCase(@"/", null)]
[TestCase(@"/test", "/")]
public void path_should_return_parent_mono(string path, string parentPath)
{
MonoOnly();
path.GetParentPath().Should().Be(parentPath); path.GetParentPath().Should().Be(parentPath);
} }
[Test] [Test]
public void path_should_return_parent_for_oversized_path() public void path_should_return_parent_for_oversized_path()
{ {
MonoOnly(); var path = @"/media/2e168617-f2ae-43fb-b88c-3663af1c8eea/downloads/sabnzbd/nzbdrone/Some.Real.Big.Thing/With.Alot.Of.Nested.Directories/Some.Real.Big.Thing/With.Alot.Of.Nested.Directories/Some.Real.Big.Thing/With.Alot.Of.Nested.Directories/Some.Real.Big.Thing/With.Alot.Of.Nested.Directories/Some.Real.Big.Thing/With.Alot.Of.Nested.Directories";
var parentPath = @"/media/2e168617-f2ae-43fb-b88c-3663af1c8eea/downloads/sabnzbd/nzbdrone/Some.Real.Big.Thing/With.Alot.Of.Nested.Directories/Some.Real.Big.Thing/With.Alot.Of.Nested.Directories/Some.Real.Big.Thing/With.Alot.Of.Nested.Directories/Some.Real.Big.Thing/With.Alot.Of.Nested.Directories/Some.Real.Big.Thing";
// This test will fail on Windows if long path support is not enabled: https://www.howtogeek.com/266621/how-to-make-windows-10-accept-file-paths-over-260-characters/
// It will also fail if the app isn't configured to use long path (such as resharper): https://blogs.msdn.microsoft.com/jeremykuhne/2016/07/30/net-4-6-2-and-long-paths-on-windows-10/
var path = @"C:\media\2e168617-f2ae-43fb-b88c-3663af1c8eea\downloads\sabnzbd\nzbdrone\Some.Real.Big.Thing\With.Alot.Of.Nested.Directories\Some.Real.Big.Thing\With.Alot.Of.Nested.Directories\Some.Real.Big.Thing\With.Alot.Of.Nested.Directories\Some.Real.Big.Thing\With.Alot.Of.Nested.Directories\Some.Real.Big.Thing\With.Alot.Of.Nested.Directories".AsOsAgnostic();
var parentPath = @"C:\media\2e168617-f2ae-43fb-b88c-3663af1c8eea\downloads\sabnzbd\nzbdrone\Some.Real.Big.Thing\With.Alot.Of.Nested.Directories\Some.Real.Big.Thing\With.Alot.Of.Nested.Directories\Some.Real.Big.Thing\With.Alot.Of.Nested.Directories\Some.Real.Big.Thing\With.Alot.Of.Nested.Directories\Some.Real.Big.Thing".AsOsAgnostic();
path.GetParentPath().Should().Be(parentPath); path.GetParentPath().Should().Be(parentPath);
} }

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="FluentAssertions" version="4.19.0" targetFramework="net40" /> <package id="FluentAssertions" version="4.19.0" targetFramework="net40" />
<package id="Moq" version="4.0.10827" targetFramework="net40" /> <package id="Moq" version="4.0.10827" />
<package id="NLog" version="4.5.3" targetFramework="net40" /> <package id="NLog" version="4.4.3" targetFramework="net40" />
<package id="NUnit" version="3.6.0" targetFramework="net40" /> <package id="NUnit" version="3.6.0" targetFramework="net40" />
</packages> </packages>

View File

@@ -1,29 +0,0 @@
using System;
using System.IO;
using System.Runtime.Serialization;
namespace NzbDrone.Common.Disk
{
public class DestinationAlreadyExistsException : IOException
{
public DestinationAlreadyExistsException()
{
}
public DestinationAlreadyExistsException(string message) : base(message)
{
}
public DestinationAlreadyExistsException(string message, int hresult) : base(message, hresult)
{
}
public DestinationAlreadyExistsException(string message, Exception innerException) : base(message, innerException)
{
}
protected DestinationAlreadyExistsException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
}
}

View File

@@ -200,11 +200,6 @@ namespace NzbDrone.Common.Disk
throw new IOException(string.Format("Source and destination can't be the same {0}", source)); throw new IOException(string.Format("Source and destination can't be the same {0}", source));
} }
CopyFileInternal(source, destination, overwrite);
}
protected virtual void CopyFileInternal(string source, string destination, bool overwrite = false)
{
File.Copy(source, destination, overwrite); File.Copy(source, destination, overwrite);
} }
@@ -224,11 +219,6 @@ namespace NzbDrone.Common.Disk
} }
RemoveReadOnly(source); RemoveReadOnly(source);
MoveFileInternal(source, destination);
}
protected virtual void MoveFileInternal(string source, string destination)
{
File.Move(source, destination); File.Move(source, destination);
} }

View File

@@ -1,11 +1,10 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using NLog; using NLog;
using NzbDrone.Common.EnsureThat; using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Exceptions;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
namespace NzbDrone.Common.Disk namespace NzbDrone.Common.Disk
@@ -341,7 +340,7 @@ namespace NzbDrone.Common.Disk
} }
else else
{ {
throw new DestinationAlreadyExistsException($"Destination {targetPath} already exists."); throw new IOException(string.Format("Destination already exists. [{0}] to [{1}]", sourcePath, targetPath));
} }
} }
} }

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Security.AccessControl; using System.Security.AccessControl;
using System.Security.Principal; using System.Security.Principal;
using NLog; using NLog;
@@ -28,15 +28,7 @@ namespace NzbDrone.Common.EnvironmentInfo
public void Register() public void Register()
{ {
try _diskProvider.EnsureFolder(_appFolderInfo.AppDataFolder);
{
_diskProvider.EnsureFolder(_appFolderInfo.AppDataFolder);
}
catch (UnauthorizedAccessException)
{
throw new SonarrStartupException("Cannot create AppFolder, Access to the path {0} is denied", _appFolderInfo.AppDataFolder);
}
if (OsInfo.IsWindows) if (OsInfo.IsWindows)
{ {

View File

@@ -1,10 +1,7 @@
using System;
namespace NzbDrone.Common.EnvironmentInfo namespace NzbDrone.Common.EnvironmentInfo
{ {
public interface IRuntimeInfo public interface IRuntimeInfo
{ {
DateTime StartTime { get; }
bool IsUserInteractive { get; } bool IsUserInteractive { get; }
bool IsAdmin { get; } bool IsAdmin { get; }
bool IsWindowsService { get; } bool IsWindowsService { get; }

View File

@@ -12,7 +12,6 @@ namespace NzbDrone.Common.EnvironmentInfo
public class RuntimeInfo : IRuntimeInfo public class RuntimeInfo : IRuntimeInfo
{ {
private readonly Logger _logger; private readonly Logger _logger;
private readonly DateTime _startTime = DateTime.UtcNow;
public RuntimeInfo(IServiceProvider serviceProvider, Logger logger) public RuntimeInfo(IServiceProvider serviceProvider, Logger logger)
{ {
@@ -38,14 +37,6 @@ namespace NzbDrone.Common.EnvironmentInfo
IsProduction = InternalIsProduction(); IsProduction = InternalIsProduction();
} }
public DateTime StartTime
{
get
{
return _startTime;
}
}
public static bool IsUserInteractive => Environment.UserInteractive; public static bool IsUserInteractive => Environment.UserInteractive;
bool IRuntimeInfo.IsUserInteractive => IsUserInteractive; bool IRuntimeInfo.IsUserInteractive => IsUserInteractive;

View File

@@ -1,6 +1,4 @@
using NzbDrone.Common.Exceptions; namespace NzbDrone.Common.Exceptions
namespace NzbDrone.Common.Disk
{ {
public class NotParentException : NzbDroneException public class NotParentException : NzbDroneException
{ {

View File

@@ -32,19 +32,7 @@ namespace NzbDrone.Common.Extensions
{ {
if (response == null || response.Content == null) return ex; if (response == null || response.Content == null) return ex;
var contentSample = response.Content.Substring(0, Math.Min(response.Content.Length, maxSampleLength)); var contentSample = response.Content.Substring(0, Math.Min(response.Content.Length, 512));
if (response.Request != null)
{
ex.AddData("RequestUri", response.Request.Url.ToString());
if (response.Request.ContentSummary != null)
{
ex.AddData("RequestSummary", response.Request.ContentSummary);
}
}
ex.AddData("StatusCode", response.StatusCode.ToString());
if (response.Headers != null) if (response.Headers != null)
{ {

View File

@@ -51,34 +51,6 @@ namespace NzbDrone.Common.Extensions
} }
} }
public static Dictionary<TKey, TItem> ToDictionaryIgnoreDuplicates<TItem, TKey>(this IEnumerable<TItem> src, Func<TItem, TKey> keySelector)
{
var result = new Dictionary<TKey, TItem>();
foreach (var item in src)
{
var key = keySelector(item);
if (!result.ContainsKey(key))
{
result[key] = item;
}
}
return result;
}
public static Dictionary<TKey, TValue> ToDictionaryIgnoreDuplicates<TItem, TKey, TValue>(this IEnumerable<TItem> src, Func<TItem, TKey> keySelector, Func<TItem, TValue> valueSelector)
{
var result = new Dictionary<TKey, TValue>();
foreach (var item in src)
{
var key = keySelector(item);
if (!result.ContainsKey(key))
{
result[key] = valueSelector(item);
}
}
return result;
}
public static void AddIfNotNull<TSource>(this List<TSource> source, TSource item) public static void AddIfNotNull<TSource>(this List<TSource> source, TSource item)
{ {
if (item == null) if (item == null)
@@ -109,4 +81,4 @@ namespace NzbDrone.Common.Extensions
return source.Select(predicate).ToList(); return source.Select(predicate).ToList();
} }
} }
} }

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
@@ -24,8 +24,6 @@ namespace NzbDrone.Common.Extensions
private static readonly string UPDATE_CLIENT_FOLDER_NAME = "NzbDrone.Update" + Path.DirectorySeparatorChar; private static readonly string UPDATE_CLIENT_FOLDER_NAME = "NzbDrone.Update" + Path.DirectorySeparatorChar;
private static readonly string UPDATE_LOG_FOLDER_NAME = "UpdateLogs" + Path.DirectorySeparatorChar; private static readonly string UPDATE_LOG_FOLDER_NAME = "UpdateLogs" + Path.DirectorySeparatorChar;
private static readonly Regex PARENT_PATH_END_SLASH_REGEX = new Regex(@"(?<!:)\\$", RegexOptions.Compiled);
public static string CleanFilePath(this string path) public static string CleanFilePath(this string path)
{ {
Ensure.That(path, () => path).IsNotNullOrWhiteSpace(); Ensure.That(path, () => path).IsNotNullOrWhiteSpace();
@@ -61,7 +59,7 @@ namespace NzbDrone.Common.Extensions
{ {
if (!parentPath.IsParentPath(childPath)) if (!parentPath.IsParentPath(childPath))
{ {
throw new NotParentException("{0} is not a child of {1}", childPath, parentPath); throw new Exceptions.NotParentException("{0} is not a child of {1}", childPath, parentPath);
} }
return childPath.Substring(parentPath.Length).Trim(Path.DirectorySeparatorChar); return childPath.Substring(parentPath.Length).Trim(Path.DirectorySeparatorChar);
@@ -69,16 +67,15 @@ namespace NzbDrone.Common.Extensions
public static string GetParentPath(this string childPath) public static string GetParentPath(this string childPath)
{ {
var cleanPath = OsInfo.IsWindows var parentPath = childPath.TrimEnd('\\', '/');
? PARENT_PATH_END_SLASH_REGEX.Replace(childPath, "")
: childPath.TrimEnd(Path.DirectorySeparatorChar);
if (cleanPath.IsNullOrWhiteSpace()) var index = parentPath.LastIndexOfAny(new[] { '\\', '/' });
if (index != -1)
{ {
return null; return parentPath.Substring(0, index);
} }
return null;
return Directory.GetParent(cleanPath)?.FullName;
} }
public static bool IsParentPath(this string parentPath, string childPath) public static bool IsParentPath(this string parentPath, string childPath)
@@ -194,24 +191,6 @@ namespace NzbDrone.Common.Extensions
return directories; return directories;
} }
public static string GetAncestorPath(this string path, string ancestorName)
{
var parent = Path.GetDirectoryName(path);
while (parent != null)
{
var currentPath = parent;
parent = Path.GetDirectoryName(parent);
if (Path.GetFileName(currentPath) == ancestorName)
{
return currentPath;
}
}
return null;
}
public static string GetAppDataPath(this IAppFolderInfo appFolderInfo) public static string GetAppDataPath(this IAppFolderInfo appFolderInfo)
{ {
return appFolderInfo.AppDataFolder; return appFolderInfo.AppDataFolder;

View File

@@ -1,5 +1,4 @@
using System; using System;
using System.Globalization;
namespace NzbDrone.Common.Extensions namespace NzbDrone.Common.Extensions
{ {
@@ -7,7 +6,7 @@ namespace NzbDrone.Common.Extensions
{ {
public static int? ParseInt32(this string source) public static int? ParseInt32(this string source)
{ {
int result; int result = 0;
if (int.TryParse(source, out result)) if (int.TryParse(source, out result))
{ {
@@ -17,9 +16,9 @@ namespace NzbDrone.Common.Extensions
return null; return null;
} }
public static long? ParseInt64(this string source) public static Nullable<long> ParseInt64(this string source)
{ {
long result; long result = 0;
if (long.TryParse(source, out result)) if (long.TryParse(source, out result))
{ {
@@ -28,17 +27,5 @@ namespace NzbDrone.Common.Extensions
return null; return null;
} }
public static double? ParseDouble(this string source)
{
double result;
if (double.TryParse(source.Replace(',', '.'), NumberStyles.Number, CultureInfo.InvariantCulture, out result))
{
return result;
}
return null;
}
} }
} }

View File

@@ -1,6 +1,4 @@
using System; using System;
using System.IO;
using System.IO.Compression;
using System.Net; using System.Net;
using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions; using NzbDrone.Common.Extensions;
@@ -26,20 +24,11 @@ namespace NzbDrone.Common.Http.Dispatchers
{ {
var webRequest = (HttpWebRequest)WebRequest.Create((Uri)request.Url); var webRequest = (HttpWebRequest)WebRequest.Create((Uri)request.Url);
if (PlatformInfo.IsMono) // Deflate is not a standard and could break depending on implementation.
{ // we should just stick with the more compatible Gzip
// On Mono GZipStream/DeflateStream leaks memory if an exception is thrown, use an intermediate buffer in that case. //http://stackoverflow.com/questions/8490718/how-to-decompress-stream-deflated-with-java-util-zip-deflater-in-net
webRequest.AutomaticDecompression = DecompressionMethods.None; webRequest.AutomaticDecompression = DecompressionMethods.GZip;
webRequest.Headers.Add("Accept-Encoding", "gzip");
}
else
{
// 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.Method = request.Method.ToString(); webRequest.Method = request.Method.ToString();
webRequest.UserAgent = _userAgentBuilder.GetUserAgent(request.UseSimplifiedUserAgent); webRequest.UserAgent = _userAgentBuilder.GetUserAgent(request.UseSimplifiedUserAgent);
webRequest.KeepAlive = request.ConnectionKeepAlive; webRequest.KeepAlive = request.ConnectionKeepAlive;
@@ -112,24 +101,11 @@ namespace NzbDrone.Common.Http.Dispatchers
using (var responseStream = httpWebResponse.GetResponseStream()) using (var responseStream = httpWebResponse.GetResponseStream())
{ {
if (responseStream != null && responseStream != Stream.Null) if (responseStream != null)
{ {
try try
{ {
data = responseStream.ToBytes(); data = responseStream.ToBytes();
if (PlatformInfo.IsMono && httpWebResponse.ContentEncoding == "gzip")
{
using (var compressedStream = new MemoryStream(data))
using (var gzip = new GZipStream(compressedStream, CompressionMode.Decompress))
using (var decompressedStream = new MemoryStream())
{
gzip.CopyTo(decompressedStream);
data = decompressedStream.ToArray();
}
httpWebResponse.Headers.Remove("Content-Encoding");
}
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -52,9 +52,7 @@ namespace NzbDrone.Common.Http
public HttpResponse Execute(HttpRequest request) public HttpResponse Execute(HttpRequest request)
{ {
var cookieContainer = InitializeRequestCookies(request); var response = ExecuteRequest(request);
var response = ExecuteRequest(request, cookieContainer);
if (request.AllowAutoRedirect && response.HasHttpRedirect) if (request.AllowAutoRedirect && response.HasHttpRedirect)
{ {
@@ -73,7 +71,7 @@ namespace NzbDrone.Common.Http
throw new WebException($"Too many automatic redirections were attempted for {autoRedirectChain.Join(" -> ")}", WebExceptionStatus.ProtocolError); throw new WebException($"Too many automatic redirections were attempted for {autoRedirectChain.Join(" -> ")}", WebExceptionStatus.ProtocolError);
} }
response = ExecuteRequest(request, cookieContainer); response = ExecuteRequest(request);
} }
while (response.HasHttpRedirect); while (response.HasHttpRedirect);
} }
@@ -100,7 +98,7 @@ namespace NzbDrone.Common.Http
return response; return response;
} }
private HttpResponse ExecuteRequest(HttpRequest request, CookieContainer cookieContainer) private HttpResponse ExecuteRequest(HttpRequest request)
{ {
foreach (var interceptor in _requestInterceptors) foreach (var interceptor in _requestInterceptors)
{ {
@@ -116,11 +114,11 @@ namespace NzbDrone.Common.Http
var stopWatch = Stopwatch.StartNew(); var stopWatch = Stopwatch.StartNew();
PrepareRequestCookies(request, cookieContainer); var cookies = PrepareRequestCookies(request);
var response = _httpDispatcher.GetResponse(request, cookieContainer); var response = _httpDispatcher.GetResponse(request, cookies);
HandleResponseCookies(response, cookieContainer); HandleResponseCookies(request, cookies);
stopWatch.Stop(); stopWatch.Stop();
@@ -139,91 +137,49 @@ namespace NzbDrone.Common.Http
return response; return response;
} }
private CookieContainer InitializeRequestCookies(HttpRequest request) private CookieContainer PrepareRequestCookies(HttpRequest request)
{ {
lock (_cookieContainerCache) lock (_cookieContainerCache)
{ {
var sourceContainer = new CookieContainer(); var persistentCookieContainer = _cookieContainerCache.Get("container", () => new CookieContainer());
var presistentContainer = _cookieContainerCache.Get("container", () => new CookieContainer());
var persistentCookies = presistentContainer.GetCookies((Uri)request.Url);
sourceContainer.Add(persistentCookies);
if (request.Cookies.Count != 0) if (request.Cookies.Count != 0)
{ {
foreach (var pair in request.Cookies) foreach (var pair in request.Cookies)
{ {
Cookie cookie; persistentCookieContainer.Add(new Cookie(pair.Key, pair.Value, "/", request.Url.Host)
if (pair.Value == null)
{ {
cookie = new Cookie(pair.Key, "", "/") // Use Now rather than UtcNow to work around Mono cookie expiry bug.
{ // See https://gist.github.com/ta264/7822b1424f72e5b4c961
Expires = DateTime.Now.AddDays(-1) Expires = DateTime.Now.AddHours(1)
}; });
}
else
{
cookie = new Cookie(pair.Key, pair.Value, "/")
{
// Use Now rather than UtcNow to work around Mono cookie expiry bug.
// See https://gist.github.com/ta264/7822b1424f72e5b4c961
Expires = DateTime.Now.AddHours(1)
};
}
sourceContainer.Add((Uri)request.Url, cookie);
if (request.StoreRequestCookie)
{
presistentContainer.Add((Uri)request.Url, cookie);
}
} }
} }
return sourceContainer; var requestCookies = persistentCookieContainer.GetCookies((Uri)request.Url);
var cookieContainer = new CookieContainer();
cookieContainer.Add(requestCookies);
return cookieContainer;
} }
} }
private void PrepareRequestCookies(HttpRequest request, CookieContainer cookieContainer) private void HandleResponseCookies(HttpRequest request, CookieContainer cookieContainer)
{ {
// Don't collect persistnet cookies for intermediate/redirected urls. if (!request.StoreResponseCookie)
/*lock (_cookieContainerCache)
{
var presistentContainer = _cookieContainerCache.Get("container", () => new CookieContainer());
var persistentCookies = presistentContainer.GetCookies((Uri)request.Url);
var existingCookies = cookieContainer.GetCookies((Uri)request.Url);
cookieContainer.Add(persistentCookies);
cookieContainer.Add(existingCookies);
}*/
}
private void HandleResponseCookies(HttpResponse response, CookieContainer cookieContainer)
{
var cookieHeaders = response.GetCookieHeaders();
if (cookieHeaders.Empty())
{ {
return; return;
} }
if (response.Request.StoreResponseCookie) lock (_cookieContainerCache)
{ {
lock (_cookieContainerCache) var persistentCookieContainer = _cookieContainerCache.Get("container", () => new CookieContainer());
{
var persistentCookieContainer = _cookieContainerCache.Get("container", () => new CookieContainer());
foreach (var cookieHeader in cookieHeaders) var cookies = cookieContainer.GetCookies((Uri)request.Url);
{
try persistentCookieContainer.Add(cookies);
{
persistentCookieContainer.SetCookies((Uri)response.Request.Url, cookieHeader);
}
catch (Exception ex)
{
_logger.Debug(ex, "Invalid cookie in {0}", response.Request.Url);
}
}
}
} }
} }
@@ -267,7 +223,6 @@ namespace NzbDrone.Common.Http
public HttpResponse<T> Get<T>(HttpRequest request) where T : new() public HttpResponse<T> Get<T>(HttpRequest request) where T : new()
{ {
var response = Get(request); var response = Get(request);
CheckResponseContentType(response);
return new HttpResponse<T>(response); return new HttpResponse<T>(response);
} }
@@ -286,16 +241,7 @@ namespace NzbDrone.Common.Http
public HttpResponse<T> Post<T>(HttpRequest request) where T : new() public HttpResponse<T> Post<T>(HttpRequest request) where T : new()
{ {
var response = Post(request); var response = Post(request);
CheckResponseContentType(response);
return new HttpResponse<T>(response); return new HttpResponse<T>(response);
} }
private void CheckResponseContentType(HttpResponse response)
{
if (response.Headers.ContentType != null && response.Headers.ContentType.Contains("text/html"))
{
throw new UnexpectedHtmlContentException(response);
}
}
} }
} }

View File

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

View File

@@ -13,10 +13,8 @@ namespace NzbDrone.Common.Http
Url = new HttpUri(url); Url = new HttpUri(url);
Headers = new HttpHeader(); Headers = new HttpHeader();
AllowAutoRedirect = true; AllowAutoRedirect = true;
StoreRequestCookie = true;
Cookies = new Dictionary<string, string>(); Cookies = new Dictionary<string, string>();
if (!RuntimeInfo.IsProduction) if (!RuntimeInfo.IsProduction)
{ {
AllowAutoRedirect = false; AllowAutoRedirect = false;
@@ -39,7 +37,6 @@ namespace NzbDrone.Common.Http
public bool ConnectionKeepAlive { get; set; } public bool ConnectionKeepAlive { get; set; }
public bool LogResponseContent { get; set; } public bool LogResponseContent { get; set; }
public Dictionary<string, string> Cookies { get; private set; } public Dictionary<string, string> Cookies { get; private set; }
public bool StoreRequestCookie { get; set; }
public bool StoreResponseCookie { get; set; } public bool StoreResponseCookie { get; set; }
public TimeSpan RequestTimeout { get; set; } public TimeSpan RequestTimeout { get; set; }
public TimeSpan RateLimit { get; set; } public TimeSpan RateLimit { get; set; }
@@ -79,12 +76,5 @@ namespace NzbDrone.Common.Http
var encoding = HttpHeader.GetEncodingFromContentType(Headers.ContentType); var encoding = HttpHeader.GetEncodingFromContentType(Headers.ContentType);
ContentData = encoding.GetBytes(data); ContentData = encoding.GetBytes(data);
} }
public void AddBasicAuthentication(string username, string password)
{
var authInfo = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes($"{username}:{password}"));
Headers.Set("Authorization", "Basic " + authInfo);
}
} }
} }

View File

@@ -355,7 +355,7 @@ namespace NzbDrone.Common.Http
FormData.Add(new HttpFormData FormData.Add(new HttpFormData
{ {
Name = key, Name = key,
ContentData = Encoding.UTF8.GetBytes(Convert.ToString(value, System.Globalization.CultureInfo.InvariantCulture)) ContentData = Encoding.UTF8.GetBytes(value.ToString())
}); });
return this; return this;

View File

@@ -55,22 +55,20 @@ namespace NzbDrone.Common.Http
StatusCode == HttpStatusCode.MovedPermanently || StatusCode == HttpStatusCode.MovedPermanently ||
StatusCode == HttpStatusCode.Found; StatusCode == HttpStatusCode.Found;
public string[] GetCookieHeaders()
{
return Headers.GetValues("Set-Cookie") ?? new string[0];
}
public Dictionary<string, string> GetCookies() public Dictionary<string, string> GetCookies()
{ {
var result = new Dictionary<string, string>(); var result = new Dictionary<string, string>();
var setCookieHeaders = GetCookieHeaders(); var setCookieHeaders = Headers.GetValues("Set-Cookie");
foreach (var cookie in setCookieHeaders) if (setCookieHeaders != null)
{ {
var match = RegexSetCookie.Match(cookie); foreach (var cookie in setCookieHeaders)
if (match.Success)
{ {
result[match.Groups[1].Value] = match.Groups[2].Value; var match = RegexSetCookie.Match(cookie);
if (match.Success)
{
result[match.Groups[1].Value] = match.Groups[2].Value;
}
} }
} }

View File

@@ -135,7 +135,7 @@ namespace NzbDrone.Common.Http
return new HttpUri(Scheme, Host, Port, CombinePath(Path, path), Query, Fragment); return new HttpUri(Scheme, Host, Port, CombinePath(Path, path), Query, Fragment);
} }
public static string CombinePath(string basePath, string relativePath) private static string CombinePath(string basePath, string relativePath)
{ {
if (relativePath.IsNullOrWhiteSpace()) if (relativePath.IsNullOrWhiteSpace())
{ {

View File

@@ -1,13 +0,0 @@
using System;
namespace NzbDrone.Common.Http
{
public class UnexpectedHtmlContentException : HttpException
{
public UnexpectedHtmlContentException(HttpResponse response)
: base(response.Request, response, $"Site responded with browser content instead of api data. This disruption may be temporary, please try again later. [{response.Request.Url}]")
{
}
}
}

View File

@@ -51,7 +51,7 @@ namespace NzbDrone.Common.Instrumentation.Extensions
return logBuilder.LoggerName(logEvent.LoggerName) return logBuilder.LoggerName(logEvent.LoggerName)
.TimeStamp(logEvent.TimeStamp) .TimeStamp(logEvent.TimeStamp)
.Message(logEvent.Message, logEvent.Parameters) .Message(logEvent.Message, logEvent.Parameters)
.Properties(logEvent.Properties.ToDictionary(v => v.Key, v => v.Value)) .Properties((Dictionary<object, object>)logEvent.Properties)
.Exception(logEvent.Exception); .Exception(logEvent.Exception);
} }
} }

View File

@@ -31,7 +31,7 @@ namespace NzbDrone.Common.Instrumentation
if (exception is NullReferenceException && if (exception is NullReferenceException &&
exception.ToString().Contains("Microsoft.AspNet.SignalR.Transports.TransportHeartbeat.ProcessServerCommand")) exception.ToString().Contains("Microsoft.AspNet.SignalR.Transports.TransportHeartbeat.ProcessServerCommand"))
{ {
Logger.Warn("SignalR Heartbeat interrupted"); Logger.Warn("SignalR Heartbeat interupted");
return; return;
} }
@@ -49,4 +49,4 @@ namespace NzbDrone.Common.Instrumentation
Logger.Fatal(exception, "EPIC FAIL."); Logger.Fatal(exception, "EPIC FAIL.");
} }
} }
} }

View File

@@ -44,7 +44,7 @@
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL"> <Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.5.3\lib\net40-client\NLog.dll</HintPath> <HintPath>..\packages\NLog.4.4.3\lib\net40\NLog.dll</HintPath>
</Reference> </Reference>
<Reference Include="Org.Mentalis, Version=1.0.0.1, Culture=neutral, processorArchitecture=MSIL"> <Reference Include="Org.Mentalis, Version=1.0.0.1, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\DotNet4.SocksProxy.1.3.4.0\lib\net40\Org.Mentalis.dll</HintPath> <HintPath>..\packages\DotNet4.SocksProxy.1.3.4.0\lib\net40\Org.Mentalis.dll</HintPath>
@@ -60,14 +60,11 @@
<Reference Include="System.Configuration.Install" /> <Reference Include="System.Configuration.Install" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Data" /> <Reference Include="System.Data" />
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.ServiceModel" />
<Reference Include="System.ServiceProcess" /> <Reference Include="System.ServiceProcess" />
<Reference Include="Microsoft.CSharp" /> <Reference Include="Microsoft.CSharp" />
<Reference Include="ICSharpCode.SharpZipLib"> <Reference Include="ICSharpCode.SharpZipLib">
<HintPath>..\packages\ICSharpCode.SharpZipLib.Patched.0.86.5\lib\net20\ICSharpCode.SharpZipLib.dll</HintPath> <HintPath>..\packages\ICSharpCode.SharpZipLib.Patched.0.86.5\lib\net20\ICSharpCode.SharpZipLib.dll</HintPath>
</Reference> </Reference>
<Reference Include="System.Transactions" />
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
<Reference Include="System.Xml.Linq" /> <Reference Include="System.Xml.Linq" />
</ItemGroup> </ItemGroup>
@@ -95,7 +92,6 @@
<Compile Include="EnvironmentInfo\IOsVersionAdapter.cs" /> <Compile Include="EnvironmentInfo\IOsVersionAdapter.cs" />
<Compile Include="EnvironmentInfo\IPlatformInfo.cs" /> <Compile Include="EnvironmentInfo\IPlatformInfo.cs" />
<Compile Include="EnvironmentInfo\OsVersionModel.cs" /> <Compile Include="EnvironmentInfo\OsVersionModel.cs" />
<Compile Include="Disk\DestinationAlreadyExistsException.cs" />
<Compile Include="Exceptions\SonarrStartupException.cs" /> <Compile Include="Exceptions\SonarrStartupException.cs" />
<Compile Include="Extensions\DictionaryExtensions.cs" /> <Compile Include="Extensions\DictionaryExtensions.cs" />
<Compile Include="Disk\OsPath.cs" /> <Compile Include="Disk\OsPath.cs" />
@@ -129,7 +125,7 @@
<Compile Include="EnvironmentInfo\IRuntimeInfo.cs" /> <Compile Include="EnvironmentInfo\IRuntimeInfo.cs" />
<Compile Include="EnvironmentInfo\RuntimeInfo.cs" /> <Compile Include="EnvironmentInfo\RuntimeInfo.cs" />
<Compile Include="EnvironmentInfo\StartupContext.cs" /> <Compile Include="EnvironmentInfo\StartupContext.cs" />
<Compile Include="Disk\NotParentException.cs" /> <Compile Include="Exceptions\NotParentException.cs" />
<Compile Include="Exceptions\NzbDroneException.cs" /> <Compile Include="Exceptions\NzbDroneException.cs" />
<Compile Include="Expansive\CircularReferenceException.cs" /> <Compile Include="Expansive\CircularReferenceException.cs" />
<Compile Include="Expansive\Expansive.cs" /> <Compile Include="Expansive\Expansive.cs" />
@@ -177,7 +173,6 @@
<Compile Include="Http\HttpRequestBuilderFactory.cs" /> <Compile Include="Http\HttpRequestBuilderFactory.cs" />
<Compile Include="Http\Proxy\ProxyType.cs" /> <Compile Include="Http\Proxy\ProxyType.cs" />
<Compile Include="Http\TlsFailureException.cs" /> <Compile Include="Http\TlsFailureException.cs" />
<Compile Include="Http\UnexpectedHtmlContentException.cs" />
<Compile Include="Http\TooManyRequestsException.cs" /> <Compile Include="Http\TooManyRequestsException.cs" />
<Compile Include="Extensions\IEnumerableExtensions.cs" /> <Compile Include="Extensions\IEnumerableExtensions.cs" />
<Compile Include="Http\UserAgentBuilder.cs" /> <Compile Include="Http\UserAgentBuilder.cs" />
@@ -216,7 +211,6 @@
<Compile Include="Serializer\IntConverter.cs" /> <Compile Include="Serializer\IntConverter.cs" />
<Compile Include="Serializer\Json.cs" /> <Compile Include="Serializer\Json.cs" />
<Compile Include="Serializer\JsonVisitor.cs" /> <Compile Include="Serializer\JsonVisitor.cs" />
<Compile Include="Serializer\UnderscoreStringEnumConverter.cs" />
<Compile Include="ServiceFactory.cs" /> <Compile Include="ServiceFactory.cs" />
<Compile Include="ServiceProvider.cs" /> <Compile Include="ServiceProvider.cs" />
<Compile Include="Extensions\StringExtensions.cs" /> <Compile Include="Extensions\StringExtensions.cs" />

View File

@@ -1,6 +1,5 @@
using System; using System;
using System.IO; using System.IO;
using System.Reflection;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Converters; using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Serialization; using Newtonsoft.Json.Serialization;
@@ -15,13 +14,13 @@ namespace NzbDrone.Common.Serializer
static Json() static Json()
{ {
SerializerSetting = new JsonSerializerSettings SerializerSetting = new JsonSerializerSettings
{ {
DateTimeZoneHandling = DateTimeZoneHandling.Utc, DateTimeZoneHandling = DateTimeZoneHandling.Utc,
NullValueHandling = NullValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore,
Formatting = Formatting.Indented, Formatting = Formatting.Indented,
DefaultValueHandling = DefaultValueHandling.Include, DefaultValueHandling = DefaultValueHandling.Include,
ContractResolver = new CamelCasePropertyNamesContractResolver() ContractResolver = new CamelCasePropertyNamesContractResolver()
}; };
SerializerSetting.Converters.Add(new StringEnumConverter { CamelCaseText = true }); SerializerSetting.Converters.Add(new StringEnumConverter { CamelCaseText = true });
@@ -35,61 +34,12 @@ namespace NzbDrone.Common.Serializer
public static T Deserialize<T>(string json) where T : new() public static T Deserialize<T>(string json) where T : new()
{ {
try return JsonConvert.DeserializeObject<T>(json, SerializerSetting);
{
return JsonConvert.DeserializeObject<T>(json, SerializerSetting);
}
catch (JsonReaderException ex)
{
throw DetailedJsonReaderException(ex, json);
}
} }
public static object Deserialize(string json, Type type) public static object Deserialize(string json, Type type)
{ {
try return JsonConvert.DeserializeObject(json, type, SerializerSetting);
{
return JsonConvert.DeserializeObject(json, type, SerializerSetting);
}
catch (JsonReaderException ex)
{
throw DetailedJsonReaderException(ex, json);
}
}
private static JsonReaderException DetailedJsonReaderException(JsonReaderException ex, string json)
{
var lineNumber = ex.LineNumber == 0 ? 0 : (ex.LineNumber - 1);
var linePosition = ex.LinePosition;
var lines = json.Split('\n');
if (lineNumber >= 0 && lineNumber < lines.Length &&
linePosition >= 0 && linePosition < lines[lineNumber].Length)
{
var line = lines[lineNumber];
var start = Math.Max(0, linePosition - 20);
var end = Math.Min(line.Length, linePosition + 20);
var snippetBefore = line.Substring(start, linePosition - start);
var snippetAfter = line.Substring(linePosition, end - linePosition);
var message = ex.Message + " (Json snippet '" + snippetBefore + "<--error-->" + snippetAfter + "')";
// Not risking updating JSON.net from 9.x to 10.x just to get this as public ctor.
var ctor = typeof(JsonReaderException).GetConstructor(BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(string), typeof(Exception), typeof(string), typeof(int), typeof(int) }, null);
if (ctor != null)
{
return (JsonReaderException)ctor.Invoke(new object[] { message, ex, ex.Path, ex.LineNumber, linePosition });
}
// JSON.net 10.x ctor in case we update later.
ctor = typeof(JsonReaderException).GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, new Type[] { typeof(string), typeof(string), typeof(int), typeof(int), typeof(Exception) }, null);
if (ctor != null)
{
return (JsonReaderException)ctor.Invoke(new object[] { message, ex.Path, ex.LineNumber, linePosition, ex });
}
}
return ex;
} }
public static bool TryDeserialize<T>(string json, out T result) where T : new() public static bool TryDeserialize<T>(string json, out T result) where T : new()
@@ -128,4 +78,4 @@ namespace NzbDrone.Common.Serializer
Serialize(model, new StreamWriter(outputStream)); Serialize(model, new StreamWriter(outputStream));
} }
} }
} }

View File

@@ -1,58 +0,0 @@
using System;
using System.Text;
using Newtonsoft.Json;
namespace NzbDrone.Common.Serializer
{
public class UnderscoreStringEnumConverter : JsonConverter
{
public object UnknownValue { get; set; }
public UnderscoreStringEnumConverter(object unknownValue)
{
UnknownValue = unknownValue;
}
public override bool CanConvert(Type objectType)
{
return objectType.IsEnum;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var enumString = reader.Value.ToString().Replace("_", string.Empty);
try
{
return Enum.Parse(objectType, enumString, true);
}
catch
{
if (UnknownValue == null)
{
throw;
}
return UnknownValue;
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var enumText = value.ToString();
var builder = new StringBuilder(enumText.Length + 4);
builder.Append(char.ToLower(enumText[0]));
for (int i = 1; i < enumText.Length; i++)
{
if (char.IsUpper(enumText[i]))
{
builder.Append('_');
}
builder.Append(char.ToLower(enumText[i]));
}
enumText = builder.ToString();
writer.WriteValue(enumText);
}
}
}

View File

@@ -3,6 +3,6 @@
<package id="DotNet4.SocksProxy" version="1.3.4.0" targetFramework="net40" /> <package id="DotNet4.SocksProxy" version="1.3.4.0" targetFramework="net40" />
<package id="ICSharpCode.SharpZipLib.Patched" version="0.86.5" targetFramework="net40" /> <package id="ICSharpCode.SharpZipLib.Patched" version="0.86.5" targetFramework="net40" />
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net40" /> <package id="Newtonsoft.Json" version="9.0.1" targetFramework="net40" />
<package id="NLog" version="4.5.3" targetFramework="net40" /> <package id="NLog" version="4.4.3" targetFramework="net40" />
<package id="SharpRaven" version="2.2.0" targetFramework="net40" /> <package id="SharpRaven" version="2.2.0" targetFramework="net40" />
</packages> </packages>

View File

@@ -25,15 +25,7 @@ namespace NzbDrone.Console
try try
{ {
var startupArgs = new StartupContext(args); var startupArgs = new StartupContext(args);
try NzbDroneLogger.Register(startupArgs, false, true);
{
NzbDroneLogger.Register(startupArgs, false, true);
}
catch (Exception ex)
{
System.Console.WriteLine("NLog Exception: " + ex.ToString());
throw;
}
Bootstrap.Start(startupArgs, new ConsoleAlerts()); Bootstrap.Start(startupArgs, new ConsoleAlerts());
} }
catch (SonarrStartupException ex) catch (SonarrStartupException ex)
@@ -65,7 +57,7 @@ namespace NzbDrone.Console
private static void Exit(ExitCodes exitCode) private static void Exit(ExitCodes exitCode)
{ {
LogManager.Shutdown(); LogManager.Flush();
if (exitCode != ExitCodes.Normal) if (exitCode != ExitCodes.Normal)
{ {
@@ -88,6 +80,8 @@ namespace NzbDrone.Console
System.Console.ReadLine(); System.Console.ReadLine();
} }
//Need this to terminate on mono (thanks nlog)
LogManager.Configuration = null;
Environment.Exit((int)exitCode); Environment.Exit((int)exitCode);
} }
} }

View File

@@ -66,7 +66,6 @@
<ApplicationManifest>app.manifest</ApplicationManifest> <ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Microsoft.CSharp" />
<Reference Include="Microsoft.Owin, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> <Reference Include="Microsoft.Owin, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion> <SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Microsoft.Owin.2.1.0\lib\net40\Microsoft.Owin.dll</HintPath> <HintPath>..\packages\Microsoft.Owin.2.1.0\lib\net40\Microsoft.Owin.dll</HintPath>
@@ -80,19 +79,13 @@
<Private>True</Private> <Private>True</Private>
</Reference> </Reference>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL"> <Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.5.3\lib\net40-client\NLog.dll</HintPath> <HintPath>..\packages\NLog.4.4.3\lib\net40\NLog.dll</HintPath>
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Configuration" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="Owin"> <Reference Include="Owin">
<HintPath>..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath> <HintPath>..\packages\Owin.1.0\lib\net40\Owin.dll</HintPath>
</Reference> </Reference>
<Reference Include="System.Data" />
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.ServiceModel" />
<Reference Include="System.Transactions" />
<Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="..\NzbDrone.Common\Properties\SharedAssemblyInfo.cs"> <Compile Include="..\NzbDrone.Common\Properties\SharedAssemblyInfo.cs">

View File

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

View File

@@ -1,59 +0,0 @@
using System;
using FluentAssertions;
using Marr.Data.Converters;
using NUnit.Framework;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Datastore.Converters
{
[TestFixture]
public class BooleanIntConverterFixture : CoreTest<Core.Datastore.Converters.BooleanIntConverter>
{
[TestCase(true, 1)]
[TestCase(false, 0)]
public void should_return_int_when_saving_boolean_to_db(bool input, int expected)
{
Subject.ToDB(input).Should().Be(expected);
}
[Test]
public void should_return_db_null_for_null_value_when_saving_to_db()
{
Subject.ToDB(null).Should().Be(DBNull.Value);
}
[TestCase(1, true)]
[TestCase(0, false)]
public void should_return_bool_when_getting_int_from_db(int input, bool expected)
{
var context = new ConverterContext
{
DbValue = (long)input
};
Subject.FromDB(context).Should().Be(expected);
}
[Test]
public void should_return_db_null_for_null_value_when_getting_from_db()
{
var context = new ConverterContext
{
DbValue = DBNull.Value
};
Subject.FromDB(context).Should().Be(DBNull.Value);
}
[Test]
public void should_throw_for_non_boolean_equivalent_number_value_when_getting_from_db()
{
var context = new ConverterContext
{
DbValue = (long)2
};
Assert.Throws<ConversionException>(() => Subject.FromDB(context));
}
}
}

View File

@@ -1,64 +0,0 @@
using System;
using System.Data;
using FluentAssertions;
using Marr.Data.Converters;
using Moq;
using NUnit.Framework;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Datastore.Converters;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv.Commands;
namespace NzbDrone.Core.Test.Datastore.Converters
{
[TestFixture]
public class CommandConverterFixture : CoreTest<CommandConverter>
{
[Test]
public void should_return_json_string_when_saving_boolean_to_db()
{
var command = new RefreshSeriesCommand();
Subject.ToDB(command).Should().BeOfType<string>();
}
[Test]
public void should_return_null_for_null_value_when_saving_to_db()
{
Subject.ToDB(null).Should().Be(null);
}
[Test]
public void should_return_db_null_for_db_null_value_when_saving_to_db()
{
Subject.ToDB(DBNull.Value).Should().Be(DBNull.Value);
}
[Test]
public void should_return_command_when_getting_json_from_db()
{
var dataRecordMock = new Mock<IDataRecord>();
dataRecordMock.Setup(s => s.GetOrdinal("Name")).Returns(0);
dataRecordMock.Setup(s => s.GetString(0)).Returns("RefreshSeries");
var context = new ConverterContext
{
DataRecord = dataRecordMock.Object,
DbValue = new RefreshSeriesCommand().ToJson()
};
Subject.FromDB(context).Should().BeOfType<RefreshSeriesCommand>();
}
[Test]
public void should_return_null_for_null_value_when_getting_from_db()
{
var context = new ConverterContext
{
DbValue = DBNull.Value
};
Subject.FromDB(context).Should().Be(null);
}
}
}

View File

@@ -1,70 +0,0 @@
using System;
using FluentAssertions;
using Marr.Data.Converters;
using NUnit.Framework;
using NzbDrone.Core.Datastore.Converters;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Datastore.Converters
{
[TestFixture]
public class DoubleConverterFixture : CoreTest<DoubleConverter>
{
[Test]
public void should_return_double_when_saving_double_to_db()
{
var input = 10.5D;
Subject.ToDB(input).Should().Be(input);
}
[Test]
public void should_return_null_for_null_value_when_saving_to_db()
{
Subject.ToDB(null).Should().Be(null);
}
[Test]
public void should_return_db_null_for_db_null_value_when_saving_to_db()
{
Subject.ToDB(DBNull.Value).Should().Be(DBNull.Value);
}
[Test]
public void should_return_double_when_getting_double_from_db()
{
var expected = 10.5D;
var context = new ConverterContext
{
DbValue = expected
};
Subject.FromDB(context).Should().Be(expected);
}
[Test]
public void should_return_double_when_getting_string_from_db()
{
var expected = 10.5D;
var context = new ConverterContext
{
DbValue = $"{expected}"
};
Subject.FromDB(context).Should().Be(expected);
}
[Test]
public void should_return_null_for_null_value_when_getting_from_db()
{
var context = new ConverterContext
{
DbValue = DBNull.Value
};
Subject.FromDB(context).Should().Be(DBNull.Value);
}
}
}

View File

@@ -1,57 +0,0 @@
using System;
using System.Reflection;
using FluentAssertions;
using Marr.Data.Converters;
using Marr.Data.Mapping;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Test.Datastore.Converters
{
[TestFixture]
public class EnumIntConverterFixture : CoreTest<Core.Datastore.Converters.EnumIntConverter>
{
[Test]
public void should_return_int_when_saving_enum_to_db()
{
Subject.ToDB(SeriesTypes.Standard).Should().Be((int)SeriesTypes.Standard);
}
[Test]
public void should_return_db_null_for_null_value_when_saving_to_db()
{
Subject.ToDB(null).Should().Be(DBNull.Value);
}
[Test]
public void should_return_enum_when_getting_int_from_db()
{
var mockMemberInfo = new Mock<MemberInfo>();
mockMemberInfo.SetupGet(s => s.DeclaringType).Returns(typeof(Series));
mockMemberInfo.SetupGet(s => s.Name).Returns("SeriesType");
var expected = SeriesTypes.Standard;
var context = new ConverterContext
{
ColumnMap = new ColumnMap(mockMemberInfo.Object) { FieldType = typeof(SeriesTypes) },
DbValue = (long)expected
};
Subject.FromDB(context).Should().Be(expected);
}
[Test]
public void should_return_null_for_null_value_when_getting_from_db()
{
var context = new ConverterContext
{
DbValue = DBNull.Value
};
Subject.FromDB(context).Should().Be(null);
}
}
}

View File

@@ -1,51 +0,0 @@
using System;
using FluentAssertions;
using Marr.Data.Converters;
using NUnit.Framework;
using NzbDrone.Core.Datastore.Converters;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Datastore.Converters
{
[TestFixture]
public class GuidConverterFixture : CoreTest<GuidConverter>
{
[Test]
public void should_return_string_when_saving_guid_to_db()
{
var guid = Guid.NewGuid();
Subject.ToDB(guid).Should().Be(guid.ToString());
}
[Test]
public void should_return_db_null_for_null_value_when_saving_to_db()
{
Subject.ToDB(null).Should().Be(DBNull.Value);
}
[Test]
public void should_return_guid_when_getting_string_from_db()
{
var guid = Guid.NewGuid();
var context = new ConverterContext
{
DbValue = guid.ToString()
};
Subject.FromDB(context).Should().Be(guid);
}
[Test]
public void should_return_empty_guid_for_db_null_value_when_getting_from_db()
{
var context = new ConverterContext
{
DbValue = DBNull.Value
};
Subject.FromDB(context).Should().Be(Guid.Empty);
}
}
}

View File

@@ -1,58 +0,0 @@
using System;
using FluentAssertions;
using Marr.Data.Converters;
using NUnit.Framework;
using NzbDrone.Core.Datastore.Converters;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Datastore.Converters
{
[TestFixture]
public class Int32ConverterFixture : CoreTest<Int32Converter>
{
[Test]
public void should_return_int_when_saving_int_to_db()
{
var i = 5;
Subject.ToDB(5).Should().Be(5);
}
[Test]
public void should_return_int_when_getting_int_from_db()
{
var i = 5;
var context = new ConverterContext
{
DbValue = i
};
Subject.FromDB(context).Should().Be(i);
}
[Test]
public void should_return_int_when_getting_string_from_db()
{
var i = 5;
var context = new ConverterContext
{
DbValue = i.ToString()
};
Subject.FromDB(context).Should().Be(i);
}
[Test]
public void should_return_db_null_for_db_null_value_when_getting_from_db()
{
var context = new ConverterContext
{
DbValue = DBNull.Value
};
Subject.FromDB(context).Should().Be(DBNull.Value);
}
}
}

View File

@@ -1,49 +0,0 @@
using System;
using FluentAssertions;
using Marr.Data.Converters;
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Core.Datastore.Converters;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.Datastore.Converters
{
[TestFixture]
public class OsPathConverterFixture : CoreTest<OsPathConverter>
{
[Test]
public void should_return_string_when_saving_os_path_to_db()
{
var path = @"C:\Test\TV".AsOsAgnostic();
var osPath = new OsPath(path);
Subject.ToDB(osPath).Should().Be(path);
}
[Test]
public void should_return_os_path_when_getting_string_from_db()
{
var path = @"C:\Test\TV".AsOsAgnostic();
var osPath = new OsPath(path);
var context = new ConverterContext
{
DbValue = path
};
Subject.FromDB(context).Should().Be(osPath);
}
[Test]
public void should_return_db_null_for_db_null_value_when_getting_from_db()
{
var context = new ConverterContext
{
DbValue = DBNull.Value
};
Subject.FromDB(context).Should().Be(DBNull.Value);
}
}
}

View File

@@ -1,58 +0,0 @@
using System;
using FluentAssertions;
using Marr.Data.Converters;
using NUnit.Framework;
using NzbDrone.Core.Datastore.Converters;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Datastore.Converters
{
[TestFixture]
public class QualityIntConverterFixture : CoreTest<QualityIntConverter>
{
[Test]
public void should_return_int_when_saving_quality_to_db()
{
var quality = Quality.Bluray1080p;
Subject.ToDB(quality).Should().Be(quality.Id);
}
[Test]
public void should_return_0_when_saving_db_null_to_db()
{
Subject.ToDB(DBNull.Value).Should().Be(0);
}
[Test]
public void should_throw_when_saving_another_object_to_db()
{
Assert.Throws<InvalidOperationException>(() => Subject.ToDB("Not a quality"));
}
[Test]
public void should_return_quality_when_getting_string_from_db()
{
var quality = Quality.Bluray1080p;
var context = new ConverterContext
{
DbValue = quality.Id
};
Subject.FromDB(context).Should().Be(quality);
}
[Test]
public void should_return_db_null_for_db_null_value_when_getting_from_db()
{
var context = new ConverterContext
{
DbValue = DBNull.Value
};
Subject.FromDB(context).Should().Be(Quality.Unknown);
}
}
}

View File

@@ -1,65 +0,0 @@
using System;
using System.Globalization;
using FluentAssertions;
using Marr.Data.Converters;
using NUnit.Framework;
using NzbDrone.Core.Datastore.Converters;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Datastore.Converters
{
[TestFixture]
public class TimeSpanConverterFixture : CoreTest<TimeSpanConverter>
{
[Test]
public void should_return_string_when_saving_timespan_to_db()
{
var timeSpan = TimeSpan.FromMinutes(5);
Subject.ToDB(timeSpan).Should().Be(timeSpan.ToString("c", CultureInfo.InvariantCulture));
}
[Test]
public void should_return_null_when_saving_empty_string_to_db()
{
Subject.ToDB("").Should().Be(null);
}
[Test]
public void should_return_time_span_when_getting_time_span_from_db()
{
var timeSpan = TimeSpan.FromMinutes(5);
var context = new ConverterContext
{
DbValue = timeSpan
};
Subject.FromDB(context).Should().Be(timeSpan);
}
[Test]
public void should_return_time_span_when_getting_string_from_db()
{
var timeSpan = TimeSpan.FromMinutes(5);
var context = new ConverterContext
{
DbValue = timeSpan.ToString("c", CultureInfo.InvariantCulture)
};
Subject.FromDB(context).Should().Be(timeSpan);
}
[Test]
public void should_return_time_span_zero_for_db_null_value_when_getting_from_db()
{
var context = new ConverterContext
{
DbValue = DBNull.Value
};
Subject.FromDB(context).Should().Be(TimeSpan.Zero);
}
}
}

View File

@@ -1,51 +0,0 @@
using System;
using FluentAssertions;
using Marr.Data.Converters;
using NUnit.Framework;
using NzbDrone.Core.Datastore.Converters;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Datastore.Converters
{
[TestFixture]
public class UtcConverterFixture : CoreTest<UtcConverter>
{
[Test]
public void should_return_date_time_when_saving_date_time_to_db()
{
var dateTime = DateTime.Now;
Subject.ToDB(dateTime).Should().Be(dateTime.ToUniversalTime());
}
[Test]
public void should_return_db_null_when_saving_db_null_to_db()
{
Subject.ToDB(DBNull.Value).Should().Be(DBNull.Value);
}
[Test]
public void should_return_time_span_when_getting_time_span_from_db()
{
var dateTime = DateTime.Now.ToUniversalTime();
var context = new ConverterContext
{
DbValue = dateTime
};
Subject.FromDB(context).Should().Be(dateTime);
}
[Test]
public void should_return_db_null_for_db_null_value_when_getting_from_db()
{
var context = new ConverterContext
{
DbValue = DBNull.Value
};
Subject.FromDB(context).Should().Be(DBNull.Value);
}
}
}

View File

@@ -1,41 +0,0 @@
using System.Linq;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Serializer;
using NzbDrone.Core.Datastore.Migration;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Datastore.Migration
{
[TestFixture]
public class update_animetosho_urlFixture : MigrationTest<update_animetosho_url>
{
[TestCase("Newznab", "https://animetosho.org")]
[TestCase("Newznab", "http://animetosho.org")]
[TestCase("Torznab", "https://animetosho.org")]
[TestCase("Torznab", "http://animetosho.org")]
public void should_replace_old_url(string impl, string baseUrl)
{
var db = WithMigrationTestDb(c =>
{
c.Insert.IntoTable("Indexers").Row(new
{
Name = "AnimeTosho",
Implementation = impl,
Settings = new NewznabSettings121
{
BaseUrl = baseUrl,
ApiPath = "/feed/nabapi"
}.ToJson(),
ConfigContract = impl + "Settings"
});
});
var items = db.Query<IndexerDefinition90>("SELECT * FROM Indexers");
items.Should().HaveCount(1);
items.First().Settings.ToObject<NewznabSettings121>().BaseUrl.Should().Be(baseUrl.Replace("animetosho", "feed.animetosho"));
}
}
}

View File

@@ -1,163 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.History;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Tv;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.DecisionEngineTests
{
[TestFixture]
public class AlreadyImportedSpecificationFixture : CoreTest<AlreadyImportedSpecification>
{
private const int FIRST_EPISODE_ID = 1;
private const string TITLE = "Series.Title.S01E01.720p.HDTV.x264-Sonarr";
private Series _series;
private QualityModel _hdtv720p;
private QualityModel _hdtv1080p;
private RemoteEpisode _remoteEpisode;
private List<History.History> _history;
[SetUp]
public void Setup()
{
var singleEpisodeList = new List<Episode>
{
new Episode
{
Id = FIRST_EPISODE_ID,
SeasonNumber = 12,
EpisodeNumber = 3,
EpisodeFileId = 1
}
};
_series = Builder<Series>.CreateNew()
.Build();
_hdtv720p = new QualityModel(Quality.HDTV720p, new Revision(version: 1));
_hdtv1080p = new QualityModel(Quality.HDTV1080p, new Revision(version: 1));
_remoteEpisode = new RemoteEpisode
{
Series = _series,
ParsedEpisodeInfo = new ParsedEpisodeInfo { Quality = _hdtv720p },
Episodes = singleEpisodeList,
Release = Builder<ReleaseInfo>.CreateNew()
.Build()
};
_history = new List<History.History>();
Mocker.GetMock<IConfigService>()
.SetupGet(s => s.EnableCompletedDownloadHandling)
.Returns(true);
Mocker.GetMock<IHistoryService>()
.Setup(s => s.FindByEpisodeId(It.IsAny<int>()))
.Returns(_history);
}
private void GivenCdhDisabled()
{
Mocker.GetMock<IConfigService>()
.SetupGet(s => s.EnableCompletedDownloadHandling)
.Returns(false);
}
private void GivenHistoryItem(string downloadId, string sourceTitle, QualityModel quality, HistoryEventType eventType)
{
_history.Add(new History.History
{
DownloadId = downloadId,
SourceTitle = sourceTitle,
Quality = quality,
Date = DateTime.UtcNow,
EventType = eventType
});
}
[Test]
public void should_be_accepted_if_CDH_is_disabled()
{
GivenCdhDisabled();
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
}
[Test]
public void should_be_accepted_if_episode_does_not_have_a_file()
{
_remoteEpisode.Episodes.First().EpisodeFileId = 0;
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
}
[Test]
public void should_be_accepted_if_episode_does_not_have_grabbed_event()
{
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
}
[Test]
public void should_be_accepted_if_episode_does_not_have_imported_event()
{
GivenHistoryItem(Guid.NewGuid().ToString().ToUpper(), TITLE, _hdtv720p, HistoryEventType.Grabbed);
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
}
[Test]
public void should_be_accepted_if_grabbed_and_imported_quality_is_the_same()
{
var downloadId = Guid.NewGuid().ToString().ToUpper();
GivenHistoryItem(downloadId, TITLE, _hdtv720p, HistoryEventType.Grabbed);
GivenHistoryItem(downloadId, TITLE, _hdtv720p, HistoryEventType.DownloadFolderImported);
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
}
[Test]
public void should_be_rejected_if_grabbed_download_id_matches_release_torrent_hash()
{
var downloadId = Guid.NewGuid().ToString().ToUpper();
GivenHistoryItem(downloadId, TITLE, _hdtv720p, HistoryEventType.Grabbed);
GivenHistoryItem(downloadId, TITLE, _hdtv1080p, HistoryEventType.DownloadFolderImported);
_remoteEpisode.Release = Builder<TorrentInfo>.CreateNew()
.With(t => t.DownloadProtocol = DownloadProtocol.Torrent)
.With(t => t.InfoHash = downloadId)
.Build();
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeFalse();
}
[Test]
public void should_be_rejected_if_release_title_matches_grabbed_event_source_title()
{
var downloadId = Guid.NewGuid().ToString().ToUpper();
GivenHistoryItem(downloadId, TITLE, _hdtv720p, HistoryEventType.Grabbed);
GivenHistoryItem(downloadId, TITLE, _hdtv1080p, HistoryEventType.DownloadFolderImported);
_remoteEpisode.Release = Builder<TorrentInfo>.CreateNew()
.With(t => t.DownloadProtocol = DownloadProtocol.Torrent)
.With(t => t.InfoHash = downloadId)
.Build();
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeFalse();
}
}
}

View File

@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using FizzWare.NBuilder; using FizzWare.NBuilder;
using FluentAssertions; using FluentAssertions;
@@ -16,7 +16,7 @@ using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Test.Framework; using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.DecisionEngineTests.RssSync namespace NzbDrone.Core.Test.DecisionEngineTests
{ {
[TestFixture] [TestFixture]
public class HistorySpecificationFixture : CoreTest<HistorySpecification> public class HistorySpecificationFixture : CoreTest<HistorySpecification>

View File

@@ -1,75 +0,0 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.DecisionEngine.Specifications;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.DecisionEngineTests
{
public class MaximumSizeSpecificationFixture : CoreTest<MaximumSizeSpecification>
{
private RemoteEpisode _remoteEpisode;
[SetUp]
public void Setup()
{
_remoteEpisode = new RemoteEpisode() { Release = new ReleaseInfo() };
}
private void WithMaximumSize(int size)
{
Mocker.GetMock<IConfigService>().SetupGet(c => c.MaximumSize).Returns(size);
}
private void WithSize(int size)
{
_remoteEpisode.Release.Size = size * 1024 * 1024;
}
[Test]
public void should_return_true_when_maximum_size_is_set_to_zero()
{
WithMaximumSize(0);
WithSize(1000);
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
}
[Test]
public void should_return_true_when_size_is_smaller_than_maximum_size()
{
WithMaximumSize(2000);
WithSize(1999);
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
}
[Test]
public void should_return_true_when_size_is_equals_to_maximum_size()
{
WithMaximumSize(2000);
WithSize(2000);
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
}
[Test]
public void should_return_false_when_size_is_bigger_than_maximum_size()
{
WithMaximumSize(2000);
WithSize(2001);
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeFalse();
}
[Test]
public void should_return_true_when_size_is_zero()
{
WithMaximumSize(2000);
WithSize(0);
Subject.IsSatisfiedBy(_remoteEpisode, null).Accepted.Should().BeTrue();
}
}
}

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