1
0
mirror of https://github.com/Sonarr/Sonarr.git synced 2026-03-08 14:50:43 -04:00

Compare commits

..

1 Commits

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

View File

@@ -1,25 +0,0 @@
# This file is for unifying the coding style for different editors and IDEs
# editorconfig.org
root = true
[*.{cs,html,js,hbs}]
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 4
[*.less]
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 2
# They have troubles with TABS. Use 2 spaces
[{package.json,.travis.yml}]
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 2

View File

@@ -1,5 +0,0 @@
Provide a description of the feature request or bug, the more details the better.
Please use https://forums.sonarr.tv/ for support or other questions. (When in doubt, use the forums)

View File

@@ -1,14 +0,0 @@
#### Database Migration
YES | NO
#### Description
A few sentences describing the overall goals of the pull request's commits.
#### Todos
- [ ] Tests
- [ ] Documentation
#### Issues Fixed or Closed by this PR
*

7
.gitignore vendored
View File

@@ -10,7 +10,6 @@ src/**/[Oo]bj/
*.suo
*.user
*.sln.docstates
.vs/
# Build results
*_i.c
@@ -42,9 +41,6 @@ src/**/[Oo]bj/
_ReSharper*
_dotCover*
# DevExpress CodeRush
src/.cr/
# NCrunch
*.ncrunch*
.*crunch*.local.xml
@@ -132,6 +128,3 @@ output/*
._*
_start
_temp_*/**/*
src/.idea/

6
.idea/encodings.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="PROJECT" charset="UTF-8" />
</component>
</project>

View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<file url="file://$PROJECT_DIR$" libraries="{Sonarr node_modules}" />
<includedPredefinedLibrary name="ECMAScript 6" />
</component>
</project>

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 490 B

After

Width:  |  Height:  |  Size: 707 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -1,52 +0,0 @@
# Sonarr
Sonarr is a PVR for Usenet and BitTorrent users. It can monitor multiple RSS feeds for new episodes of your favorite shows and will grab, sort and rename them. It can also be configured to automatically upgrade the quality of files already downloaded when a better quality format becomes available.
## Major Features Include:
* Support for major platforms: Windows, Linux, macOS, Raspberry Pi, etc.
* Automatically detects new episodes
* Can scan your existing library and download any missing episodes
* Can watch for better quality of the episodes you already have and do an automatic upgrade. *eg. from DVD to Blu-Ray*
* Automatic failed download handling will try another release if one fails
* Manual search so you can pick any release or to see why a release was not downloaded automatically
* Fully configurable episode renaming
* Full integration with SABnzbd and NZBGet
* Full integration with Kodi, Plex (notification, library update, metadata)
* Full support for specials and multi-episode releases
* And a beautiful UI
## Configuring Development Environment:
### Requirements
* Visual Studio 2015 (https://www.visualstudio.com/vs/)
* [Git](https://git-scm.com/downloads)
* [NodeJS](https://nodejs.org/en/download/)
### Setup
* Make sure all the required software mentioned above are installed.
* Clone the repository into your development machine. [*info*](https://help.github.com/articles/working-with-repositories)
* Grab the submodules `git submodule init && git submodule update`
* Install the required Node Packages `npm install`
* Start gulp to monitor your dev environment for any changes that need post processing using `npm start` command.
*Please note gulp must be running at all times while you are working with Sonarr client source files.*
### Development
* Open `NzbDrone.sln` in Visual Studio
* Make sure `NzbDrone.Console` is set as the startup project
### License
* [GNU GPL v3](http://www.gnu.org/licenses/gpl.html)
* Copyright 2010-2017
### Sponsors
* [JetBrains](http://www.jetbrains.com/) for providing us with free licenses to their great tools
* [ReSharper](http://www.jetbrains.com/resharper/)
* [WebStorm](http://www.jetbrains.com/webstorm/)
* [TeamCity](http://www.jetbrains.com/teamcity/)

View File

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

View File

@@ -5,7 +5,7 @@ var postcss = require('gulp-postcss');
var sourcemaps = require('gulp-sourcemaps');
var autoprefixer = require('autoprefixer-core');
var livereload = require('gulp-livereload');
var cleancss = require('gulp-clean-css');
var print = require('gulp-print');
var paths = require('./paths');
var errorHandler = require('./errorHandler');
@@ -33,13 +33,12 @@ gulp.task('less', function() {
.pipe(sourcemaps.init())
.pipe(less({
dumpLineNumbers : 'false',
compress : false,
yuicompress : false,
compress : true,
yuicompress : true,
ieCompat : true,
strictImports : true
}))
.pipe(postcss([ autoprefixer({ browsers: ['last 2 versions'] }) ]))
.pipe(cleancss())
.on('error', errorHandler.onError)
.pipe(sourcemaps.write(paths.dest.content))
.pipe(gulp.dest(paths.dest.content))

8
integration_mono.sh Normal file
View File

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

View File

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

View File

@@ -4,15 +4,14 @@
"description": "Sonarr",
"main": "main.js",
"scripts": {
"build": "gulp build",
"start": "gulp watch"
"preinstall": ""
},
"repository": {
"type": "git",
"url": "git://github.com/Sonarr/Sonarr.git"
},
"author": "",
"license": "GPL-3.0",
"license": "BSD",
"gitHead": "9ff7aa1bf7fe38c4c5bdb92f56c8ad556916ed67",
"readmeFilename": "readme.md",
"dependencies": {
@@ -20,7 +19,6 @@
"del": "1.2.0",
"gulp": "3.9.0",
"gulp-cached": "1.1.0",
"gulp-clean-css": "^3.0.4",
"gulp-concat": "2.6.0",
"gulp-declare": "0.3.0",
"gulp-handlebars": "3.0.1",

55
readme.md Normal file
View File

@@ -0,0 +1,55 @@
# Sonarr #
Sonarr is a PVR for Usenet and BitTorrent users. It can monitor multiple RSS feeds for new episodes of your favorite shows and will grab, sort and rename them. It can also be configured to automatically upgrade the quality of files already downloaded when a better quality format becomes available.
## Major Features Include: ##
* Support for major platforms: Windows, Linux, OSX, Raspberry Pi, etc.
* Automatically detects new episodes
* Can scan your existing library and download any missing episodes
* Can watch for better quality of the episodes you already have and do an automatic upgrade. *eg. from DVD to Blu-Ray*
* Automatic failed download handling will try another release if one fails
* Manual search so you can pick any release or to see why a release was not downloaded automatically
* Fully configurable episode renaming
* Full integration with SABNzbd and NzbGet
* Full integration with XBMC, Plex (notification, library update, metadata)
* Full support for specials and multi-episode releases
* And a beautiful UI
## Configuring Development Environment: ##
### Requirements ###
- Visual Studio 2015 [Free Community Edition](https://www.visualstudio.com/en-us/products/visual-studio-community-vs.aspx)
- [Git](http://git-scm.com/downloads)
- [NodeJS](http://nodejs.org/download/)
- [Gulp](http://gulpjs.com)
### Setup ###
- Make sure all the required software mentioned above are installed.
- Clone the repository into your development machine. [*info*](https://help.github.com/articles/working-with-repositories)
- Grab the submodules `git submodule init && git submodule update`
- install the required Node Packages `npm install`
- install gulp `npm install gulp -g`
- start gulp to monitor your dev environment for any changes that need post processing using `gulp watch` command.
*Please note gulp must be running at all times while you are working with Sonarr client source files.*
### Development ###
- Open `NzbDrone.sln` in Visual Studio
- Make sure `NzbDrone.Console` is set as the startup project
### License ###
* [GNU GPL v3](http://www.gnu.org/licenses/gpl.html)
Copyright 2010-2016
### Sponsors ###
- [JetBrains](http://www.jetbrains.com/) for providing us with free licenses to their great tools
- [ReSharper](http://www.jetbrains.com/resharper/)
- [WebStorm](http://www.jetbrains.com/webstorm/)
- [TeamCity](http://www.jetbrains.com/teamcity/)

View File

@@ -75,8 +75,6 @@
<xs:enumeration value="seedtype" /> <!-- TBD, which criteria must be met. was going for 'ratio,seedtime,both' but afaik it's always 'either' -->
<xs:enumeration value="minimumratio" />
<xs:enumeration value="minimumseedtime" />
<xs:enumeration value="downloadvolumefactor" /> <!-- factor for the download volume, in most cases it should be set to 1, if a torrent is set to freeleech set it to 0, if only 50% is counted set it to 0.5 -->
<xs:enumeration value="uploadvolumefactor" /> <!-- factor for the upload volume, in most cases it should be set to 1, if a torrent is set to neutral leech (upload is not counted) set it to 0, if it's set to double upload set it to 2 -->
</xs:restriction>
</xs:simpleType>
<xs:element name="attr">

View File

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

View File

@@ -1,4 +1,5 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following

View File

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

View File

@@ -1,4 +1,5 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following

View File

@@ -24,7 +24,6 @@ namespace LogentriesNLog.fastJSON
SerializeNullValues = false;
UseOptimizedDatasetSchema = false;
UsingGlobalTypes = false;
UseUTCDateTime = true;
}
public bool UseOptimizedDatasetSchema = true;
public bool UseFastGuid = true;
@@ -40,7 +39,7 @@ namespace LogentriesNLog.fastJSON
return ToJSON(obj, UseSerializerExtension, UseFastGuid, UseOptimizedDatasetSchema, SerializeNullValues);
}
public string ToJSON(object obj,
bool enableSerializerExtensions,
bool enableFastGuid,
@@ -50,13 +49,13 @@ namespace LogentriesNLog.fastJSON
return new JSONSerializer(enableOptimizedDatasetSchema, enableFastGuid, enableSerializerExtensions, serializeNullValues, IndentOutput).ConvertToJSON(obj);
}
public T ToObject<T>(string json)
{
return (T)ToObject(json, typeof(T));
}
public object ToObject(string json, Type type)
{
var ht = new JsonParser(json).Decode() as Dictionary<string, object>;
@@ -321,7 +320,7 @@ namespace LogentriesNLog.fastJSON
}
}
_getterscache.Add(type, getters);
return getters;
}
@@ -449,7 +448,7 @@ namespace LogentriesNLog.fastJSON
#if !SILVERLIGHT
else if (pi.isDictionary || pi.isHashtable)
oset = CreateDictionary((ArrayList)v, pi.pt, pi.GenericTypes, globaltypes);
#else
#else
else if (pi.isDictionary)
oset = CreateDictionary((List<object>)v, pi.pt, pi.GenericTypes, globaltypes);
#endif
@@ -818,4 +817,4 @@ namespace LogentriesNLog.fastJSON
}
#endif
}
}
}

View File

@@ -170,8 +170,6 @@ namespace LogentriesNLog.fastJSON
_output.Append(dt.Minute.ToString("00", NumberFormatInfo.InvariantInfo));
_output.Append(":");
_output.Append(dt.Second.ToString("00", NumberFormatInfo.InvariantInfo));
_output.Append(".");
_output.Append(dt.Millisecond.ToString("000", NumberFormatInfo.InvariantInfo));
if (JSON.Instance.UseUTCDateTime)
_output.Append("Z");

View File

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

View File

@@ -198,8 +198,7 @@ namespace Marr.Data.Mapping
{
return AutoMapPropertiesWhere(m =>
m.MemberType == MemberTypes.Property &&
!DataHelper.IsSimpleType((m as PropertyInfo).PropertyType) &&
!MapRepository.Instance.TypeConverters.ContainsKey((m as PropertyInfo).PropertyType));
!DataHelper.IsSimpleType((m as PropertyInfo).PropertyType));
}
/// <summary>

View File

@@ -1,6 +1,9 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;
namespace Microsoft.AspNet.SignalR.Infrastructure

View File

@@ -4,7 +4,7 @@ using System.Diagnostics;
namespace Microsoft.AspNet.SignalR.Infrastructure
{
public class NoOpPerformanceCounter : IPerformanceCounter
internal class NoOpPerformanceCounter : IPerformanceCounter
{
public string CounterName
{

View File

@@ -43,9 +43,9 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.9.0.1\lib\net40\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
<Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Newtonsoft.Json.6.0.6\lib\net40\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
@@ -281,4 +281,4 @@
<Target Name="BeforeBuild">
</Target>
-->
</Project>
</Project>

View File

@@ -6,6 +6,7 @@ using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Configuration;
using Microsoft.AspNet.SignalR.Hosting;

View File

@@ -1,9 +1,13 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Infrastructure;

View File

@@ -3,6 +3,7 @@
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Hosting;
using Microsoft.AspNet.SignalR.Infrastructure;

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net40" />
<package id="Newtonsoft.Json" version="6.0.6" targetFramework="net40" />
</packages>

View File

@@ -5,10 +5,13 @@ using System.Collections.Generic;
using System.Collections.Specialized;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Owin.Infrastructure;
using Microsoft.AspNet.SignalR.Hosting;
namespace Microsoft.AspNet.SignalR.Owin
{
using WebSocketFunc = Func<IDictionary<string, object>, Task>;
public partial class ServerRequest :
#if NET45
IWebSocketRequest

View File

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

View File

@@ -0,0 +1,168 @@
using System;
using System.Collections.Generic;
using FizzWare.NBuilder;
using FluentAssertions;
using Marr.Data;
using NUnit.Framework;
using NzbDrone.Api.Commands;
using NzbDrone.Api.Config;
using NzbDrone.Api.Episodes;
using NzbDrone.Api.History;
using NzbDrone.Api.Indexers;
using NzbDrone.Api.Logs;
using NzbDrone.Api.Mapping;
using NzbDrone.Api.Profiles;
using NzbDrone.Api.RootFolders;
using NzbDrone.Api.Series;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Instrumentation;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Organizer;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.RootFolders;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Update.Commands;
using NzbDrone.Test.Common;
using System.Linq;
namespace NzbDrone.Api.Test.MappingTests
{
[TestFixture]
public class ResourceMappingFixture : TestBase
{
[TestCase(typeof(Core.Tv.Series), typeof(SeriesResource))]
[TestCase(typeof(Episode), typeof(EpisodeResource))]
[TestCase(typeof(RootFolder), typeof(RootFolderResource))]
[TestCase(typeof(NamingConfig), typeof(NamingConfigResource))]
// [TestCase(typeof(IndexerDefinition), typeof(IndexerResource))] //TODO: Ignoring because we don't have a good way to ignore properties with value injector
[TestCase(typeof(ReleaseInfo), typeof(ReleaseResource))]
[TestCase(typeof(ParsedEpisodeInfo), typeof(ReleaseResource))]
[TestCase(typeof(DownloadDecision), typeof(ReleaseResource))]
[TestCase(typeof(Core.History.History), typeof(HistoryResource))]
[TestCase(typeof(Profile), typeof(ProfileResource))]
[TestCase(typeof(ProfileQualityItem), typeof(ProfileQualityItemResource))]
[TestCase(typeof(Log), typeof(LogResource))]
[TestCase(typeof(Command), typeof(CommandResource))]
public void matching_fields(Type modelType, Type resourceType)
{
MappingValidation.ValidateMapping(modelType, resourceType);
}
[Test]
public void should_map_lazy_loaded_values_should_not_be_inject_if_not_loaded()
{
var modelWithLazy = new ModelWithLazy()
{
Guid = new TestLazyLoaded<Guid>()
};
modelWithLazy.InjectTo<ModelWithNoLazy>().Guid.Should().BeEmpty();
modelWithLazy.Guid.IsLoaded.Should().BeFalse();
}
[Test]
public void should_map_lay_loaded_values_should_be_inject_if_loaded()
{
var guid = Guid.NewGuid();
var modelWithLazy = new ModelWithLazy()
{
Guid = new LazyLoaded<Guid>(guid)
};
modelWithLazy.InjectTo<ModelWithNoLazy>().Guid.Should().Be(guid);
modelWithLazy.Guid.IsLoaded.Should().BeTrue();
}
[Test]
public void should_be_able_to_map_lists()
{
var modelList = Builder<TestModel>.CreateListOfSize(10).Build();
var resourceList = modelList.InjectTo<List<TestResource>>();
resourceList.Should().HaveSameCount(modelList);
}
[Test]
public void should_map_wrapped_models()
{
var modelList = Builder<TestModel>.CreateListOfSize(10).Build().ToList();
var wrapper = new TestModelWrapper
{
TestlList = modelList
};
wrapper.InjectTo<TestResourceWrapper>();
}
[Test]
public void should_map_profile()
{
var profileResource = new ProfileResource
{
Cutoff = Quality.WEBDL1080p,
Items = new List<ProfileQualityItemResource> { new ProfileQualityItemResource { Quality = Quality.WEBDL1080p, Allowed = true } }
};
profileResource.InjectTo<Profile>();
}
[Test]
public void should_map_tracked_command()
{
var commandResource = new CommandModel { Body = new ApplicationUpdateCommand() };
commandResource.InjectTo<CommandResource>();
}
}
public class ModelWithLazy
{
public LazyLoaded<Guid> Guid { get; set; }
}
public class ModelWithNoLazy
{
public Guid Guid { get; set; }
}
public class TestLazyLoaded<T> : LazyLoaded<T>
{
public override void Prepare(Func<IDataMapper> dataMapperFactory, object parent)
{
throw new InvalidOperationException();
}
}
public class TestModelWrapper
{
public List<TestModel> TestlList { get; set; }
}
public class TestResourceWrapper
{
public List<TestResource> TestList { get; set; }
}
public class TestModel
{
public string Field1 { get; set; }
public string Field2 { get; set; }
}
public class TestResource
{
public string Field1 { get; set; }
public string Field2 { get; set; }
}
}

View File

@@ -38,21 +38,17 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="FizzWare.NBuilder, Version=4.0.0.115, Culture=neutral, PublicKeyToken=5651b03e12e42c12, processorArchitecture=MSIL">
<HintPath>..\packages\NBuilder.4.0.0\lib\net40\FizzWare.NBuilder.dll</HintPath>
<Reference Include="FluentAssertions, Version=4.2.1.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
<HintPath>..\packages\FluentAssertions.4.2.1\lib\net40\FluentAssertions.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="FluentAssertions, Version=4.19.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
<HintPath>..\packages\FluentAssertions.4.19.0\lib\net40\FluentAssertions.dll</HintPath>
<Reference Include="FluentAssertions.Core, Version=4.2.1.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
<HintPath>..\packages\FluentAssertions.4.2.1\lib\net40\FluentAssertions.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<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>
</Reference>
<Reference Include="Moq, Version=4.2.1510.2205, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
<HintPath>..\packages\Moq.4.2.1510.2205\lib\net40\Moq.dll</HintPath>
</Reference>
<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>
<Reference Include="nunit.framework, Version=2.6.3.13283, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\NUnit.2.6.3\lib\nunit.framework.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
@@ -61,12 +57,19 @@
<Reference Include="System.Xml" />
<Reference Include="System.Xml.Linq" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="FizzWare.NBuilder">
<HintPath>..\packages\NBuilder.3.0.1.1\lib\FizzWare.NBuilder.dll</HintPath>
</Reference>
<Reference Include="Moq">
<HintPath>..\packages\Moq.4.0.10827\lib\NET40\Moq.dll</HintPath>
</Reference>
<Reference Include="Omu.ValueInjecter">
<HintPath>..\packages\ValueInjecter.2.3.3\lib\net35\Omu.ValueInjecter.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="ClientSchemaTests\SchemaBuilderFixture.cs" />
<Compile Include="MappingTests\ResourceMappingFixture.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
@@ -108,4 +111,4 @@
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>

View File

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

View File

@@ -43,7 +43,7 @@ namespace NzbDrone.Api.Authentication
expiry = DateTime.UtcNow.AddDays(7);
}
return this.LoginAndRedirect(user.Identifier, expiry, _configFileProvider.UrlBase + "/");
return this.LoginAndRedirect(user.Identifier, expiry);
}
private Response Logout()

View File

@@ -27,18 +27,16 @@ namespace NzbDrone.Api.Authentication
_configFileProvider = configFileProvider;
}
public int Order => 10;
public void Register(IPipelines pipelines)
{
if (_configFileProvider.AuthenticationMethod == AuthenticationType.Forms)
{
RegisterFormsAuth(pipelines);
RegisterFormsAuth(pipelines);
}
else if (_configFileProvider.AuthenticationMethod == AuthenticationType.Basic)
{
pipelines.EnableBasicAuthentication(new BasicAuthenticationConfiguration(_authenticationService, "Sonarr"));
pipelines.EnableBasicAuthentication(new BasicAuthenticationConfiguration(_authenticationService, "Sonarr"));
}
pipelines.BeforeRequest.AddItemToEndOfPipeline((Func<NancyContext, Response>) RequiresAuthentication);
@@ -68,7 +66,6 @@ namespace NzbDrone.Api.Authentication
{
RedirectUrl = _configFileProvider.UrlBase + "/login",
UserMapper = _authenticationService,
Path = _configFileProvider.UrlBase,
CryptographyConfiguration = cryptographyConfiguration
});
}
@@ -78,7 +75,7 @@ namespace NzbDrone.Api.Authentication
if (context.Request.IsApiRequest())
{
if ((context.Response.StatusCode == HttpStatusCode.SeeOther &&
context.Response.Headers["Location"].StartsWith($"{_configFileProvider.UrlBase}/login", StringComparison.InvariantCultureIgnoreCase)) ||
context.Response.Headers["Location"].StartsWith("/login", StringComparison.InvariantCultureIgnoreCase)) ||
context.Response.StatusCode == HttpStatusCode.Unauthorized)
{
context.Response = new { Error = "Unauthorized" }.AsResponse(HttpStatusCode.Unauthorized);

View File

@@ -16,9 +16,15 @@ namespace NzbDrone.Api.Blacklist
private PagingResource<BlacklistResource> GetBlacklist(PagingResource<BlacklistResource> pagingResource)
{
var pagingSpec = pagingResource.MapToPagingSpec<BlacklistResource, Core.Blacklisting.Blacklist>("id", SortDirection.Ascending);
var pagingSpec = new PagingSpec<Core.Blacklisting.Blacklist>
{
Page = pagingResource.Page,
PageSize = pagingResource.PageSize,
SortKey = pagingResource.SortKey,
SortDirection = pagingResource.SortDirection
};
return ApplyToPage(_blacklistService.Paged, pagingSpec, BlacklistResourceMapper.MapToResource);
return ApplyToPage(_blacklistService.Paged, pagingSpec);
}
private void DeleteBlacklist(int id)
@@ -26,4 +32,4 @@ namespace NzbDrone.Api.Blacklist
_blacklistService.Delete(id);
}
}
}
}

View File

@@ -20,28 +20,4 @@ namespace NzbDrone.Api.Blacklist
public SeriesResource Series { get; set; }
}
public static class BlacklistResourceMapper
{
public static BlacklistResource MapToResource(this Core.Blacklisting.Blacklist model)
{
if (model == null) return null;
return new BlacklistResource
{
Id = model.Id,
SeriesId = model.SeriesId,
EpisodeIds = model.EpisodeIds,
SourceTitle = model.SourceTitle,
Quality = model.Quality,
Date = model.Date,
Protocol = model.Protocol,
Indexer = model.Indexer,
Message = model.Message,
Series = model.Series.ToResource()
};
}
}
}

View File

@@ -2,53 +2,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Ical.Net;
using Ical.Net.DataTypes;
using Ical.Net.Interfaces.Serialization;
using Ical.Net.Serialization;
using Ical.Net.Serialization.iCalendar.Factory;
using DDay.iCal;
using NzbDrone.Core.Tv;
using Nancy.Responses;
using NzbDrone.Core.Tags;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Api.Calendar
{
public class CalendarFeedModule : NzbDroneFeedModule
{
private readonly IEpisodeService _episodeService;
private readonly ITagService _tagService;
public CalendarFeedModule(IEpisodeService episodeService, ITagService tagService)
public CalendarFeedModule(IEpisodeService episodeService)
: base("calendar")
{
_episodeService = episodeService;
_tagService = tagService;
Get["/NzbDrone.ics"] = options => GetCalendarFeed();
Get["/Sonarr.ics"] = options => GetCalendarFeed();
}
private Response GetCalendarFeed()
{
var pastDays = 7;
var futureDays = 28;
var futureDays = 28;
var start = DateTime.Today.AddDays(-pastDays);
var end = DateTime.Today.AddDays(futureDays);
var unmonitored = false;
var premiersOnly = false;
var asAllDay = false;
var tags = new List<int>();
// TODO: Remove start/end parameters in v3, they don't work well for iCal
var queryStart = Request.Query.Start;
var queryEnd = Request.Query.End;
var queryPastDays = Request.Query.PastDays;
var queryFutureDays = Request.Query.FutureDays;
var queryUnmonitored = Request.Query.Unmonitored;
var queryPremiersOnly = Request.Query.PremiersOnly;
var queryAsAllDay = Request.Query.AsAllDay;
var queryTags = Request.Query.Tags;
if (queryStart.HasValue) start = DateTime.Parse(queryStart.Value);
if (queryEnd.HasValue) end = DateTime.Parse(queryEnd.Value);
@@ -65,76 +48,33 @@ namespace NzbDrone.Api.Calendar
end = DateTime.Today.AddDays(futureDays);
}
if (queryUnmonitored.HasValue)
{
unmonitored = bool.Parse(queryUnmonitored.Value);
}
if (queryPremiersOnly.HasValue)
{
premiersOnly = bool.Parse(queryPremiersOnly.Value);
}
if (queryAsAllDay.HasValue)
{
asAllDay = bool.Parse(queryAsAllDay.Value);
}
if (queryTags.HasValue)
{
var tagInput = (string)queryTags.Value.ToString();
tags.AddRange(tagInput.Split(',').Select(_tagService.GetTag).Select(t => t.Id));
}
var episodes = _episodeService.EpisodesBetweenDates(start, end, unmonitored);
var calendar = new Ical.Net.Calendar
{
ProductId = "-//sonarr.tv//Sonarr//EN"
};
var episodes = _episodeService.EpisodesBetweenDates(start, end, false);
var icalCalendar = new iCalendar();
foreach (var episode in episodes.OrderBy(v => v.AirDateUtc.Value))
{
if (premiersOnly && (episode.SeasonNumber == 0 || episode.EpisodeNumber != 1))
{
continue;
}
if (tags.Any() && tags.None(episode.Series.Tags.Contains))
{
continue;
}
var occurrence = calendar.Create<Event>();
occurrence.Uid = "NzbDrone_episode_" + episode.Id;
var occurrence = icalCalendar.Create<Event>();
occurrence.UID = "NzbDrone_episode_" + episode.Id.ToString();
occurrence.Status = episode.HasFile ? EventStatus.Confirmed : EventStatus.Tentative;
occurrence.Start = new iCalDateTime(episode.AirDateUtc.Value) { HasTime = true };
occurrence.End = new iCalDateTime(episode.AirDateUtc.Value.AddMinutes(episode.Series.Runtime)) { HasTime = true };
occurrence.Description = episode.Overview;
occurrence.Categories = new List<string>() { episode.Series.Network };
if (asAllDay)
{
occurrence.Start = new CalDateTime(episode.AirDateUtc.Value) { HasTime = false };
}
else
{
occurrence.Start = new CalDateTime(episode.AirDateUtc.Value) { HasTime = true };
occurrence.End = new CalDateTime(episode.AirDateUtc.Value.AddMinutes(episode.Series.Runtime)) { HasTime = true };
}
switch (episode.Series.SeriesType)
{
case SeriesTypes.Daily:
occurrence.Summary = $"{episode.Series.Title} - {episode.Title}";
occurrence.Summary = string.Format("{0} - {1}", episode.Series.Title, episode.Title);
break;
default:
occurrence.Summary =$"{episode.Series.Title} - {episode.SeasonNumber}x{episode.EpisodeNumber:00} - {episode.Title}";
occurrence.Summary = string.Format("{0} - {1}x{2:00} - {3}", episode.Series.Title, episode.SeasonNumber, episode.EpisodeNumber, episode.Title);
break;
}
}
var serializer = (IStringSerializer) new SerializerFactory().Build(calendar.GetType(), new SerializationContext());
var icalendar = serializer.SerializeToString(calendar);
var serializer = new DDay.iCal.Serialization.iCalendar.SerializerFactory().Build(icalCalendar.GetType(), new DDay.iCal.Serialization.SerializationContext()) as DDay.iCal.Serialization.IStringSerializer;
var icalendar = serializer.SerializeToString(icalCalendar);
return new TextResponse(icalendar, "text/calendar");
}

View File

@@ -33,7 +33,7 @@ namespace NzbDrone.Api.Calendar
if (queryEnd.HasValue) end = DateTime.Parse(queryEnd.Value);
if (queryIncludeUnmonitored.HasValue) includeUnmonitored = Convert.ToBoolean(queryIncludeUnmonitored.Value);
var resources = MapToResource(_episodeService.EpisodesBetweenDates(start, end, includeUnmonitored), true, true);
var resources = ToListResource(() => _episodeService.EpisodesBetweenDates(start, end, includeUnmonitored));
return resources.OrderBy(e => e.AirDateUtc).ToList();
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
namespace NzbDrone.Api.ClientSchema
{

View File

@@ -6,6 +6,7 @@ using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Reflection;
using NzbDrone.Core.Annotations;
using Omu.ValueInjecter;
namespace NzbDrone.Api.ClientSchema
{
@@ -55,7 +56,7 @@ namespace NzbDrone.Api.ClientSchema
return result.OrderBy(r => r.Order).ToList();
}
public static object ReadFromSchema(List<Field> fields, Type targetType)
public static object ReadFormSchema(List<Field> fields, Type targetType, object defaults = null)
{
Ensure.That(targetType, () => targetType).IsNotNull();
@@ -63,6 +64,11 @@ namespace NzbDrone.Api.ClientSchema
var target = Activator.CreateInstance(targetType);
if (defaults != null)
{
target.InjectFrom(defaults);
}
foreach (var propertyInfo in properties)
{
var fieldAttribute = propertyInfo.GetAttribute<FieldDefinitionAttribute>(false);
@@ -73,14 +79,14 @@ namespace NzbDrone.Api.ClientSchema
if (propertyInfo.PropertyType == typeof(int))
{
var value = field.Value.ToString().ParseInt32();
propertyInfo.SetValue(target, value ?? 0, null);
var value = Convert.ToInt32(field.Value);
propertyInfo.SetValue(target, value, null);
}
else if (propertyInfo.PropertyType == typeof(long))
{
var value = field.Value.ToString().ParseInt64();
propertyInfo.SetValue(target, value ?? 0, null);
var value = Convert.ToInt64(field.Value);
propertyInfo.SetValue(target, value, null);
}
else if (propertyInfo.PropertyType == typeof(int?))
@@ -140,9 +146,9 @@ namespace NzbDrone.Api.ClientSchema
}
public static T ReadFromSchema<T>(List<Field> fields)
public static T ReadFormSchema<T>(List<Field> fields)
{
return (T)ReadFromSchema(fields, typeof(T));
return (T)ReadFormSchema(fields, typeof(T));
}
private static List<SelectOption> GetSelectOptions(Type selectOptions)

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Api.Extensions;
using NzbDrone.Api.Mapping;
using NzbDrone.Api.Validation;
using NzbDrone.Common;
using NzbDrone.Core.Datastore.Events;
@@ -35,13 +36,15 @@ namespace NzbDrone.Api.Commands
private CommandResource GetCommand(int id)
{
return _commandQueueManager.Get(id).ToResource();
return _commandQueueManager.Get(id).InjectTo<CommandResource>();
}
private int StartCommand(CommandResource commandResource)
{
var commandType = _serviceFactory.GetImplementations(typeof(Command))
.Single(c => c.Name.Replace("Command", "").Equals(commandResource.Name, StringComparison.InvariantCultureIgnoreCase));
var commandType =
_serviceFactory.GetImplementations(typeof (Command))
.Single(c => c.Name.Replace("Command", "")
.Equals(commandResource.Name, StringComparison.InvariantCultureIgnoreCase));
dynamic command = Request.Body.FromJson(commandType);
command.Trigger = CommandTrigger.Manual;
@@ -52,14 +55,14 @@ namespace NzbDrone.Api.Commands
private List<CommandResource> GetStartedCommands()
{
return _commandQueueManager.GetStarted().ToResource();
return ToListResource(_commandQueueManager.GetStarted());
}
public void Handle(CommandUpdatedEvent message)
{
if (message.Command.Body.SendUpdatesToClient)
{
BroadcastResourceChange(ModelAction.Updated, message.Command.ToResource());
BroadcastResourceChange(ModelAction.Updated, message.Command.InjectTo<CommandResource>());
}
}
}

View File

@@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using NzbDrone.Api.REST;
using NzbDrone.Core.Messaging.Commands;
@@ -11,7 +9,7 @@ namespace NzbDrone.Api.Commands
{
public string Name { get; set; }
public string Message { get; set; }
public object Body { get; set; }
public Command Body { get; set; }
public CommandPriority Priority { get; set; }
public CommandStatus Status { get; set; }
public DateTime Queued { get; set; }
@@ -72,7 +70,7 @@ namespace NzbDrone.Api.Commands
{
get
{
if (Body != null) return (Body as Command).SendUpdatesToClient;
if (Body != null) return Body.SendUpdatesToClient;
return false;
}
@@ -84,7 +82,7 @@ namespace NzbDrone.Api.Commands
{
get
{
if (Body != null) return (Body as Command).UpdateScheduledTask;
if (Body != null) return Body.UpdateScheduledTask;
return false;
}
@@ -94,37 +92,4 @@ namespace NzbDrone.Api.Commands
public DateTime? LastExecutionTime { get; set; }
}
public static class CommandResourceMapper
{
public static CommandResource ToResource(this CommandModel model)
{
if (model == null) return null;
return new CommandResource
{
Id = model.Id,
Name = model.Name,
Message = model.Message,
Body = model.Body,
Priority = model.Priority,
Status = model.Status,
Queued = model.QueuedAt,
Started = model.StartedAt,
Ended = model.EndedAt,
Duration = model.Duration,
Exception = model.Exception,
Trigger = model.Trigger,
CompletionMessage = model.Body.CompletionMessage,
LastExecutionTime = model.Body.LastExecutionTime
};
}
public static List<CommandResource> ToResource(this IEnumerable<CommandModel> models)
{
return models.Select(ToResource).ToList();
}
}
}

View File

@@ -1,4 +1,5 @@
using FluentValidation;
using System;
using FluentValidation;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Validation.Paths;
@@ -20,10 +21,5 @@ namespace NzbDrone.Api.Config
.SetValidator(pathExistsValidator)
.When(c => !string.IsNullOrWhiteSpace(c.DownloadedEpisodesFolder));
}
protected override DownloadClientConfigResource ToResource(IConfigService model)
{
return DownloadClientConfigResourceMapper.ToResource(model);
}
}
}

View File

@@ -1,5 +1,5 @@
using NzbDrone.Api.REST;
using NzbDrone.Core.Configuration;
using System;
using NzbDrone.Api.REST;
namespace NzbDrone.Api.Config
{
@@ -15,23 +15,4 @@ namespace NzbDrone.Api.Config
public bool AutoRedownloadFailed { get; set; }
public bool RemoveFailedDownloads { get; set; }
}
public static class DownloadClientConfigResourceMapper
{
public static DownloadClientConfigResource ToResource(IConfigService model)
{
return new DownloadClientConfigResource
{
DownloadedEpisodesFolder = model.DownloadedEpisodesFolder,
DownloadClientWorkingFolders = model.DownloadClientWorkingFolders,
DownloadedEpisodesScanInterval = model.DownloadedEpisodesScanInterval,
EnableCompletedDownloadHandling = model.EnableCompletedDownloadHandling,
RemoveCompletedDownloads = model.RemoveCompletedDownloads,
AutoRedownloadFailed = model.AutoRedownloadFailed,
RemoveFailedDownloads = model.RemoveFailedDownloads
};
}
}
}

View File

@@ -8,20 +8,19 @@ using NzbDrone.Core.Configuration;
using NzbDrone.Core.Update;
using NzbDrone.Core.Validation;
using NzbDrone.Core.Validation.Paths;
using Omu.ValueInjecter;
namespace NzbDrone.Api.Config
{
public class HostConfigModule : NzbDroneRestModule<HostConfigResource>
{
private readonly IConfigFileProvider _configFileProvider;
private readonly IConfigService _configService;
private readonly IUserService _userService;
public HostConfigModule(IConfigFileProvider configFileProvider, IConfigService configService, IUserService userService)
public HostConfigModule(IConfigFileProvider configFileProvider, IUserService userService)
: base("/config/host")
{
_configFileProvider = configFileProvider;
_configService = configService;
_userService = userService;
GetResourceSingle = GetHostConfig;
@@ -49,10 +48,12 @@ namespace NzbDrone.Api.Config
private HostConfigResource GetHostConfig()
{
var resource = _configFileProvider.ToResource(_configService);
var resource = new HostConfigResource();
resource.InjectFrom(_configFileProvider);
resource.Id = 1;
var user = _userService.FindUser();
if (user != null)
{
resource.Username = user.Username;
@@ -74,7 +75,6 @@ namespace NzbDrone.Api.Config
.ToDictionary(prop => prop.Name, prop => prop.GetValue(resource, null));
_configFileProvider.SaveConfigDictionary(dictionary);
_configService.SaveConfigDictionary(dictionary);
if (resource.Username.IsNotNullOrWhiteSpace() && resource.Password.IsNotNullOrWhiteSpace())
{

View File

@@ -1,8 +1,7 @@
using NzbDrone.Api.REST;
using System;
using NzbDrone.Api.REST;
using NzbDrone.Core.Authentication;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Update;
using NzbDrone.Common.Http.Proxy;
namespace NzbDrone.Api.Config
{
@@ -20,54 +19,11 @@ namespace NzbDrone.Api.Config
public string LogLevel { get; set; }
public string Branch { get; set; }
public string ApiKey { get; set; }
public bool Torrent { get; set; }
public string SslCertHash { get; set; }
public string UrlBase { get; set; }
public bool UpdateAutomatically { get; set; }
public UpdateMechanism UpdateMechanism { get; set; }
public string UpdateScriptPath { get; set; }
public bool ProxyEnabled { get; set; }
public ProxyType ProxyType { get; set; }
public string ProxyHostname { get; set; }
public int ProxyPort { get; set; }
public string ProxyUsername { get; set; }
public string ProxyPassword { get; set; }
public string ProxyBypassFilter { get; set; }
public bool ProxyBypassLocalAddresses { get; set; }
}
public static class HostConfigResourceMapper
{
public static HostConfigResource ToResource(this IConfigFileProvider model, IConfigService configService)
{
// TODO: Clean this mess up. don't mix data from multiple classes, use sub-resources instead?
return new HostConfigResource
{
BindAddress = model.BindAddress,
Port = model.Port,
SslPort = model.SslPort,
EnableSsl = model.EnableSsl,
LaunchBrowser = model.LaunchBrowser,
AuthenticationMethod = model.AuthenticationMethod,
AnalyticsEnabled = model.AnalyticsEnabled,
//Username
//Password
LogLevel = model.LogLevel,
Branch = model.Branch,
ApiKey = model.ApiKey,
SslCertHash = model.SslCertHash,
UrlBase = model.UrlBase,
UpdateAutomatically = model.UpdateAutomatically,
UpdateMechanism = model.UpdateMechanism,
UpdateScriptPath = model.UpdateScriptPath,
ProxyEnabled = configService.ProxyEnabled,
ProxyType = configService.ProxyType,
ProxyHostname = configService.ProxyHostname,
ProxyPort = configService.ProxyPort,
ProxyUsername = configService.ProxyUsername,
ProxyPassword = configService.ProxyPassword,
ProxyBypassFilter = configService.ProxyBypassFilter,
ProxyBypassLocalAddresses = configService.ProxyBypassLocalAddresses
};
}
}
}

View File

@@ -19,10 +19,5 @@ namespace NzbDrone.Api.Config
SharedValidator.RuleFor(c => c.RssSyncInterval)
.IsValidRssSyncInterval();
}
protected override IndexerConfigResource ToResource(IConfigService model)
{
return IndexerConfigResourceMapper.ToResource(model);
}
}
}

View File

@@ -1,5 +1,5 @@
using NzbDrone.Api.REST;
using NzbDrone.Core.Configuration;
using System;
using NzbDrone.Api.REST;
namespace NzbDrone.Api.Config
{
@@ -9,17 +9,4 @@ namespace NzbDrone.Api.Config
public int Retention { get; set; }
public int RssSyncInterval { get; set; }
}
public static class IndexerConfigResourceMapper
{
public static IndexerConfigResource ToResource(IConfigService model)
{
return new IndexerConfigResource
{
MinimumAge = model.MinimumAge,
Retention = model.Retention,
RssSyncInterval = model.RssSyncInterval,
};
}
}
}

View File

@@ -1,4 +1,5 @@
using FluentValidation;
using System;
using FluentValidation;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Validation.Paths;
@@ -13,10 +14,5 @@ namespace NzbDrone.Api.Config
SharedValidator.RuleFor(c => c.FolderChmod).NotEmpty();
SharedValidator.RuleFor(c => c.RecycleBin).IsValidPath().SetValidator(pathExistsValidator).When(c => !string.IsNullOrWhiteSpace(c.RecycleBin));
}
protected override MediaManagementConfigResource ToResource(IConfigService model)
{
return MediaManagementConfigResourceMapper.ToResource(model);
}
}
}

View File

@@ -1,5 +1,5 @@
using NzbDrone.Api.REST;
using NzbDrone.Core.Configuration;
using System;
using NzbDrone.Api.REST;
using NzbDrone.Core.MediaFiles;
namespace NzbDrone.Api.Config
@@ -20,35 +20,6 @@ namespace NzbDrone.Api.Config
public bool SkipFreeSpaceCheckWhenImporting { get; set; }
public bool CopyUsingHardlinks { get; set; }
public bool ImportExtraFiles { get; set; }
public string ExtraFileExtensions { get; set; }
public bool EnableMediaInfo { get; set; }
}
public static class MediaManagementConfigResourceMapper
{
public static MediaManagementConfigResource ToResource(IConfigService model)
{
return new MediaManagementConfigResource
{
AutoUnmonitorPreviouslyDownloadedEpisodes = model.AutoUnmonitorPreviouslyDownloadedEpisodes,
RecycleBin = model.RecycleBin,
AutoDownloadPropers = model.AutoDownloadPropers,
CreateEmptySeriesFolders = model.CreateEmptySeriesFolders,
FileDate = model.FileDate,
SetPermissionsLinux = model.SetPermissionsLinux,
FileChmod = model.FileChmod,
FolderChmod = model.FolderChmod,
ChownUser = model.ChownUser,
ChownGroup = model.ChownGroup,
SkipFreeSpaceCheckWhenImporting = model.SkipFreeSpaceCheckWhenImporting,
CopyUsingHardlinks = model.CopyUsingHardlinks,
ImportExtraFiles = model.ImportExtraFiles,
ExtraFileExtensions = model.ExtraFileExtensions,
EnableMediaInfo = model.EnableMediaInfo
};
}
}
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using FluentValidation;
using FluentValidation.Results;
@@ -6,7 +7,9 @@ using Nancy.Responses;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Organizer;
using Nancy.ModelBinding;
using NzbDrone.Api.Mapping;
using NzbDrone.Api.Extensions;
using Omu.ValueInjecter;
namespace NzbDrone.Api.Config
{
@@ -43,7 +46,7 @@ namespace NzbDrone.Api.Config
private void UpdateNamingConfig(NamingConfigResource resource)
{
var nameSpec = resource.ToModel();
var nameSpec = resource.InjectTo<NamingConfig>();
ValidateFormatResult(nameSpec);
_namingConfigService.Save(nameSpec);
@@ -52,14 +55,16 @@ namespace NzbDrone.Api.Config
private NamingConfigResource GetNamingConfig()
{
var nameSpec = _namingConfigService.GetConfig();
var resource = nameSpec.ToResource();
var resource = nameSpec.InjectTo<NamingConfigResource>();
if (resource.StandardEpisodeFormat.IsNotNullOrWhiteSpace())
if (string.IsNullOrWhiteSpace(resource.StandardEpisodeFormat))
{
var basicConfig = _filenameBuilder.GetBasicNamingConfig(nameSpec);
basicConfig.AddToResource(resource);
return resource;
}
var basicConfig = _filenameBuilder.GetBasicNamingConfig(nameSpec);
resource.InjectFrom(basicConfig);
return resource;
}
@@ -70,7 +75,7 @@ namespace NzbDrone.Api.Config
private JsonResponse<NamingSampleResource> GetExamples(NamingConfigResource config)
{
var nameSpec = config.ToModel();
var nameSpec = config.InjectTo<NamingConfig>();
var sampleResource = new NamingSampleResource();
var singleEpisodeSampleResult = _filenameSampleService.GetStandardSample(nameSpec);

View File

@@ -1,5 +1,5 @@
using NzbDrone.Api.REST;
using NzbDrone.Core.Organizer;
using System;
using NzbDrone.Api.REST;
namespace NzbDrone.Api.Config
{
@@ -20,57 +20,4 @@ namespace NzbDrone.Api.Config
public string Separator { get; set; }
public string NumberStyle { get; set; }
}
public static class NamingConfigResourceMapper
{
public static NamingConfigResource ToResource(this NamingConfig model)
{
return new NamingConfigResource
{
Id = model.Id,
RenameEpisodes = model.RenameEpisodes,
ReplaceIllegalCharacters = model.ReplaceIllegalCharacters,
MultiEpisodeStyle = model.MultiEpisodeStyle,
StandardEpisodeFormat = model.StandardEpisodeFormat,
DailyEpisodeFormat = model.DailyEpisodeFormat,
AnimeEpisodeFormat = model.AnimeEpisodeFormat,
SeriesFolderFormat = model.SeriesFolderFormat,
SeasonFolderFormat = model.SeasonFolderFormat
//IncludeSeriesTitle
//IncludeEpisodeTitle
//IncludeQuality
//ReplaceSpaces
//Separator
//NumberStyle
};
}
public static void AddToResource(this BasicNamingConfig basicNamingConfig, NamingConfigResource resource)
{
resource.IncludeSeriesTitle = basicNamingConfig.IncludeSeriesTitle;
resource.IncludeEpisodeTitle = basicNamingConfig.IncludeEpisodeTitle;
resource.IncludeQuality = basicNamingConfig.IncludeQuality;
resource.ReplaceSpaces = basicNamingConfig.ReplaceSpaces;
resource.Separator = basicNamingConfig.Separator;
resource.NumberStyle = basicNamingConfig.NumberStyle;
}
public static NamingConfig ToModel(this NamingConfigResource resource)
{
return new NamingConfig
{
Id = resource.Id,
RenameEpisodes = resource.RenameEpisodes,
ReplaceIllegalCharacters = resource.ReplaceIllegalCharacters,
MultiEpisodeStyle = resource.MultiEpisodeStyle,
StandardEpisodeFormat = resource.StandardEpisodeFormat,
DailyEpisodeFormat = resource.DailyEpisodeFormat,
AnimeEpisodeFormat = resource.AnimeEpisodeFormat,
SeriesFolderFormat = resource.SeriesFolderFormat,
SeasonFolderFormat = resource.SeasonFolderFormat
};
}
}
}

View File

@@ -2,6 +2,7 @@
using System.Reflection;
using NzbDrone.Api.REST;
using NzbDrone.Core.Configuration;
using Omu.ValueInjecter;
namespace NzbDrone.Api.Config
{
@@ -26,14 +27,13 @@ namespace NzbDrone.Api.Config
private TResource GetConfig()
{
var resource = ToResource(_configService);
var resource = new TResource();
resource.InjectFrom(_configService);
resource.Id = 1;
return resource;
}
protected abstract TResource ToResource(IConfigService model);
private TResource GetConfig(int id)
{
return GetConfig();

View File

@@ -1,18 +1,45 @@
using NzbDrone.Core.Configuration;
using System.Linq;
using System.Reflection;
using NzbDrone.Core.Configuration;
using Omu.ValueInjecter;
namespace NzbDrone.Api.Config
{
public class UiConfigModule : NzbDroneConfigModule<UiConfigResource>
public class UiConfigModule : NzbDroneRestModule<UiConfigResource>
{
public UiConfigModule(IConfigService configService)
: base(configService)
{
private readonly IConfigService _configService;
public UiConfigModule(IConfigService configService)
: base("/config/ui")
{
_configService = configService;
GetResourceSingle = GetUiConfig;
GetResourceById = GetUiConfig;
UpdateResource = SaveUiConfig;
}
protected override UiConfigResource ToResource(IConfigService model)
private UiConfigResource GetUiConfig()
{
return UiConfigResourceMapper.ToResource(model);
var resource = new UiConfigResource();
resource.InjectFrom(_configService);
resource.Id = 1;
return resource;
}
private UiConfigResource GetUiConfig(int id)
{
return GetUiConfig();
}
private void SaveUiConfig(UiConfigResource resource)
{
var dictionary = resource.GetType()
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
.ToDictionary(prop => prop.Name, prop => prop.GetValue(resource, null));
_configService.SaveConfigDictionary(dictionary);
}
}
}

View File

@@ -1,5 +1,5 @@
using NzbDrone.Api.REST;
using NzbDrone.Core.Configuration;
using System;
using NzbDrone.Api.REST;
namespace NzbDrone.Api.Config
{
@@ -17,23 +17,4 @@ namespace NzbDrone.Api.Config
public bool EnableColorImpairedMode { get; set; }
}
public static class UiConfigResourceMapper
{
public static UiConfigResource ToResource(IConfigService model)
{
return new UiConfigResource
{
FirstDayOfWeek = model.FirstDayOfWeek,
CalendarWeekColumnHeader = model.CalendarWeekColumnHeader,
ShortDateFormat = model.ShortDateFormat,
LongDateFormat = model.LongDateFormat,
TimeFormat = model.TimeFormat,
ShowRelativeDates = model.ShowRelativeDates,
EnableColorImpairedMode = model.EnableColorImpairedMode,
};
}
}
}

View File

@@ -8,16 +8,15 @@ namespace NzbDrone.Api.DiskSpace
private readonly IDiskSpaceService _diskSpaceService;
public DiskSpaceModule(IDiskSpaceService diskSpaceService)
: base("diskspace")
:base("diskspace")
{
_diskSpaceService = diskSpaceService;
GetResourceAll = GetFreeSpace;
}
public List<DiskSpaceResource> GetFreeSpace()
{
return _diskSpaceService.GetFreeSpace().ConvertAll(DiskSpaceResourceMapper.MapToResource);
return ToListResource(_diskSpaceService.GetFreeSpace);
}
}
}

View File

@@ -1,4 +1,5 @@
using NzbDrone.Api.REST;
using System;
using NzbDrone.Api.REST;
namespace NzbDrone.Api.DiskSpace
{
@@ -9,20 +10,4 @@ namespace NzbDrone.Api.DiskSpace
public long FreeSpace { get; set; }
public long TotalSpace { get; set; }
}
public static class DiskSpaceResourceMapper
{
public static DiskSpaceResource MapToResource(this Core.DiskSpace.DiskSpace model)
{
if (model == null) return null;
return new DiskSpaceResource
{
Path = model.Path,
Label = model.Label,
FreeSpace = model.FreeSpace,
TotalSpace = model.TotalSpace
};
}
}
}

View File

@@ -9,22 +9,6 @@ namespace NzbDrone.Api.DownloadClient
{
}
protected override void MapToResource(DownloadClientResource resource, DownloadClientDefinition definition)
{
base.MapToResource(resource, definition);
resource.Enable = definition.Enable;
resource.Protocol = definition.Protocol;
}
protected override void MapToModel(DownloadClientDefinition definition, DownloadClientResource resource)
{
base.MapToModel(definition, resource);
definition.Enable = resource.Enable;
definition.Protocol = resource.Protocol;
}
protected override void Validate(DownloadClientDefinition definition, bool includeWarnings)
{
if (!definition.Enable) return;

View File

@@ -1,4 +1,5 @@
using NzbDrone.Core.Indexers;
using System;
using NzbDrone.Core.Indexers;
namespace NzbDrone.Api.DownloadClient
{

View File

@@ -1,11 +1,11 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NLog;
using NzbDrone.Api.REST;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Api.Mapping;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Tv;
@@ -14,19 +14,17 @@ using NzbDrone.SignalR;
namespace NzbDrone.Api.EpisodeFiles
{
public class EpisodeFileModule : NzbDroneRestModuleWithSignalR<EpisodeFileResource, EpisodeFile>,
public class EpisodeModule : NzbDroneRestModuleWithSignalR<EpisodeFileResource, EpisodeFile>,
IHandle<EpisodeFileAddedEvent>
{
private readonly IMediaFileService _mediaFileService;
private readonly IDiskProvider _diskProvider;
private readonly IRecycleBinProvider _recycleBinProvider;
private readonly ISeriesService _seriesService;
private readonly IQualityUpgradableSpecification _qualityUpgradableSpecification;
private readonly Logger _logger;
public EpisodeFileModule(IBroadcastSignalRMessage signalRBroadcaster,
public EpisodeModule(IBroadcastSignalRMessage signalRBroadcaster,
IMediaFileService mediaFileService,
IDiskProvider diskProvider,
IRecycleBinProvider recycleBinProvider,
ISeriesService seriesService,
IQualityUpgradableSpecification qualityUpgradableSpecification,
@@ -34,7 +32,6 @@ namespace NzbDrone.Api.EpisodeFiles
: base(signalRBroadcaster)
{
_mediaFileService = mediaFileService;
_diskProvider = diskProvider;
_recycleBinProvider = recycleBinProvider;
_seriesService = seriesService;
_qualityUpgradableSpecification = qualityUpgradableSpecification;
@@ -50,7 +47,7 @@ namespace NzbDrone.Api.EpisodeFiles
var episodeFile = _mediaFileService.Get(id);
var series = _seriesService.GetSeries(episodeFile.SeriesId);
return episodeFile.ToResource(series, _qualityUpgradableSpecification);
return MapToResource(series, episodeFile);
}
private List<EpisodeFileResource> GetEpisodeFiles()
@@ -64,7 +61,8 @@ namespace NzbDrone.Api.EpisodeFiles
var series = _seriesService.GetSeries(seriesId);
return _mediaFileService.GetFilesBySeries(seriesId).ConvertAll(f => f.ToResource(series, _qualityUpgradableSpecification));
return _mediaFileService.GetFilesBySeries(seriesId)
.Select(f => MapToResource(series, f)).ToList();
}
private void SetQuality(EpisodeFileResource episodeFileResource)
@@ -79,13 +77,22 @@ namespace NzbDrone.Api.EpisodeFiles
var episodeFile = _mediaFileService.Get(id);
var series = _seriesService.GetSeries(episodeFile.SeriesId);
var fullPath = Path.Combine(series.Path, episodeFile.RelativePath);
var subfolder = _diskProvider.GetParentFolder(series.Path).GetRelativePath(_diskProvider.GetParentFolder(fullPath));
_logger.Info("Deleting episode file: {0}", fullPath);
_recycleBinProvider.DeleteFile(fullPath, subfolder);
_recycleBinProvider.DeleteFile(fullPath);
_mediaFileService.Delete(episodeFile, DeleteMediaFileReason.Manual);
}
private EpisodeFileResource MapToResource(Core.Tv.Series series, EpisodeFile episodeFile)
{
var resource = episodeFile.InjectTo<EpisodeFileResource>();
resource.Path = Path.Combine(series.Path, episodeFile.RelativePath);
resource.QualityCutoffNotMet = _qualityUpgradableSpecification.CutoffNotMet(series.Profile.Value, episodeFile.Quality);
return resource;
}
public void Handle(EpisodeFileAddedEvent message)
{
BroadcastResourceChange(ModelAction.Updated, message.EpisodeFile.Id);

View File

@@ -1,5 +1,4 @@
using System;
using System.IO;
using NzbDrone.Api.REST;
using NzbDrone.Core.Qualities;
@@ -18,47 +17,4 @@ namespace NzbDrone.Api.EpisodeFiles
public bool QualityCutoffNotMet { get; set; }
}
public static class EpisodeFileResourceMapper
{
private static EpisodeFileResource ToResource(this Core.MediaFiles.EpisodeFile model)
{
if (model == null) return null;
return new EpisodeFileResource
{
Id = model.Id,
SeriesId = model.SeriesId,
SeasonNumber = model.SeasonNumber,
RelativePath = model.RelativePath,
//Path
Size = model.Size,
DateAdded = model.DateAdded,
SceneName = model.SceneName,
Quality = model.Quality,
//QualityCutoffNotMet
};
}
public static EpisodeFileResource ToResource(this Core.MediaFiles.EpisodeFile model, Core.Tv.Series series, Core.DecisionEngine.IQualityUpgradableSpecification qualityUpgradableSpecification)
{
if (model == null) return null;
return new EpisodeFileResource
{
Id = model.Id,
SeriesId = model.SeriesId,
SeasonNumber = model.SeasonNumber,
RelativePath = model.RelativePath,
Path = Path.Combine(series.Path, model.RelativePath),
Size = model.Size,
DateAdded = model.DateAdded,
SceneName = model.SceneName,
Quality = model.Quality,
QualityCutoffNotMet = qualityUpgradableSpecification.CutoffNotMet(series.Profile.Value, model.Quality)
};
}
}
}

View File

@@ -27,7 +27,7 @@ namespace NzbDrone.Api.Episodes
var seriesId = (int)Request.Query.SeriesId;
var resources = MapToResource(_episodeService.GetEpisodeBySeries(seriesId), false, true);
var resources = ToListResource(_episodeService.GetEpisodeBySeries(seriesId));
return resources;
}
@@ -36,5 +36,10 @@ namespace NzbDrone.Api.Episodes
{
_episodeService.SetEpisodeMonitored(episodeResource.Id, episodeResource.Monitored);
}
protected override List<EpisodeResource> LoadSeries(List<EpisodeResource> resources)
{
return resources;
}
}
}

View File

@@ -1,6 +1,9 @@
using System.Collections.Generic;
using NzbDrone.Common.Extensions;
using NzbDrone.Api.EpisodeFiles;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NzbDrone.Api.Extensions;
using NzbDrone.Api.Mapping;
using NzbDrone.Api.Series;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.DecisionEngine;
@@ -50,65 +53,41 @@ namespace NzbDrone.Api.Episodes
protected EpisodeResource GetEpisode(int id)
{
var episode = _episodeService.GetEpisode(id);
var resource = MapToResource(episode, true, true);
return resource;
episode.EpisodeFile.LazyLoad();
episode.Series = _seriesService.GetSeries(episode.SeriesId);
return ToResource(episode);
}
protected EpisodeResource MapToResource(Episode episode, bool includeSeries, bool includeEpisodeFile)
protected override EpisodeResource ToResource<TModel>(TModel model)
{
var resource = episode.ToResource();
var resource = base.ToResource(model);
if (includeSeries || includeEpisodeFile)
var episode = model as Episode;
if (episode != null)
{
var series = episode.Series ?? _seriesService.GetSeries(episode.SeriesId);
if (includeSeries)
if (episode.EpisodeFile.IsLoaded && episode.EpisodeFile.Value != null)
{
resource.Series = series.ToResource();
}
if (includeEpisodeFile && episode.EpisodeFileId != 0)
{
resource.EpisodeFile = episode.EpisodeFile.Value.ToResource(series, _qualityUpgradableSpecification);
resource.EpisodeFile.Path = Path.Combine(episode.Series.Path, episode.EpisodeFile.Value.RelativePath);
resource.EpisodeFile.QualityCutoffNotMet = _qualityUpgradableSpecification.CutoffNotMet(episode.Series.Profile.Value, episode.EpisodeFile.Value.Quality);
}
}
return resource;
}
protected List<EpisodeResource> MapToResource(List<Episode> episodes, bool includeSeries, bool includeEpisodeFile)
protected override List<EpisodeResource> ToListResource<TModel>(IEnumerable<TModel> modelList)
{
var result = episodes.ToResource();
var resources = base.ToListResource(modelList);
if (includeSeries || includeEpisodeFile)
{
var seriesDict = new Dictionary<int, Core.Tv.Series>();
for (var i = 0; i < episodes.Count; i++)
{
var episode = episodes[i];
var resource = result[i];
return LoadSeries(resources);
var series = episode.Series ?? seriesDict.GetValueOrDefault(episodes[i].SeriesId) ?? _seriesService.GetSeries(episodes[i].SeriesId);
seriesDict[series.Id] = series;
if (includeSeries)
{
resource.Series = series.ToResource();
}
if (includeEpisodeFile && episodes[i].EpisodeFileId != 0)
{
resource.EpisodeFile = episodes[i].EpisodeFile.Value.ToResource(series, _qualityUpgradableSpecification);
}
}
}
return result;
}
public void Handle(EpisodeGrabbedEvent message)
{
foreach (var episode in message.Episode.Episodes)
{
var resource = episode.ToResource();
var resource = episode.InjectTo<EpisodeResource>();
resource.Grabbed = true;
BroadcastResourceChange(ModelAction.Updated, resource);
@@ -122,5 +101,10 @@ namespace NzbDrone.Api.Episodes
BroadcastResourceChange(ModelAction.Updated, episode.Id);
}
}
protected virtual List<EpisodeResource> LoadSeries(List<EpisodeResource> resources)
{
return resources.LoadSubtype<EpisodeResource, SeriesResource, Core.Tv.Series>(e => e.SeriesId, _seriesService.GetSeries).ToList();
}
}
}

View File

@@ -1,11 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using NzbDrone.Api.EpisodeFiles;
using NzbDrone.Api.REST;
using NzbDrone.Api.Series;
using NzbDrone.Core.Tv;
namespace NzbDrone.Api.Episodes
{
@@ -28,6 +25,8 @@ namespace NzbDrone.Api.Episodes
public int? SceneEpisodeNumber { get; set; }
public int? SceneSeasonNumber { get; set; }
public bool UnverifiedSceneNumbering { get; set; }
public DateTime? EndTime { get; set; }
public DateTime? GrabDate { get; set; }
public string SeriesTitle { get; set; }
public SeriesResource Series { get; set; }
@@ -35,44 +34,4 @@ namespace NzbDrone.Api.Episodes
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public bool Grabbed { get; set; }
}
public static class EpisodeResourceMapper
{
public static EpisodeResource ToResource(this Episode model)
{
if (model == null) return null;
return new EpisodeResource
{
Id = model.Id,
SeriesId = model.SeriesId,
EpisodeFileId = model.EpisodeFileId,
SeasonNumber = model.SeasonNumber,
EpisodeNumber = model.EpisodeNumber,
Title = model.Title,
AirDate = model.AirDate,
AirDateUtc = model.AirDateUtc,
Overview = model.Overview,
//EpisodeFile
HasFile = model.HasFile,
Monitored = model.Monitored,
AbsoluteEpisodeNumber = model.AbsoluteEpisodeNumber,
SceneAbsoluteEpisodeNumber = model.SceneAbsoluteEpisodeNumber,
SceneEpisodeNumber = model.SceneEpisodeNumber,
SceneSeasonNumber = model.SceneSeasonNumber,
UnverifiedSceneNumbering = model.UnverifiedSceneNumbering,
SeriesTitle = model.SeriesTitle,
//Series = model.Series.MapToResource(),
};
}
public static List<EpisodeResource> ToResource(this IEnumerable<Episode> models)
{
if (models == null) return null;
return models.Select(ToResource).ToList();
}
}
}

View File

@@ -28,10 +28,10 @@ namespace NzbDrone.Api.Episodes
if (Request.Query.SeasonNumber.HasValue)
{
var seasonNumber = (int)Request.Query.SeasonNumber;
return _renameEpisodeFileService.GetRenamePreviews(seriesId, seasonNumber).ToResource();
return ToListResource(() => _renameEpisodeFileService.GetRenamePreviews(seriesId, seasonNumber));
}
return _renameEpisodeFileService.GetRenamePreviews(seriesId).ToResource();
return ToListResource(() => _renameEpisodeFileService.GetRenamePreviews(seriesId));
}
}
}

View File

@@ -1,5 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using System;
using System.Collections.Generic;
using NzbDrone.Api.REST;
namespace NzbDrone.Api.Episodes
@@ -13,27 +13,4 @@ namespace NzbDrone.Api.Episodes
public string ExistingPath { get; set; }
public string NewPath { get; set; }
}
public static class RenameEpisodeResourceMapper
{
public static RenameEpisodeResource ToResource(this Core.MediaFiles.RenameEpisodeFilePreview model)
{
if (model == null) return null;
return new RenameEpisodeResource
{
SeriesId = model.SeriesId,
SeasonNumber = model.SeasonNumber,
EpisodeNumbers = model.EpisodeNumbers.ToList(),
EpisodeFileId = model.EpisodeFileId,
ExistingPath = model.ExistingPath,
NewPath = model.NewPath
};
}
public static List<RenameEpisodeResource> ToResource(this IEnumerable<Core.MediaFiles.RenameEpisodeFilePreview> models)
{
return models.Select(ToResource).ToList();
}
}
}

View File

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

View File

@@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using NzbDrone.Api.Mapping;
using NzbDrone.Api.REST;
using NzbDrone.Common.Cache;
using NzbDrone.Core.Datastore;
namespace NzbDrone.Api.Extensions
{
public static class LazyExtensions
{
private static readonly ICached<MethodInfo> SetterCache = new Cached<MethodInfo>();
public static IEnumerable<TParent> LoadSubtype<TParent, TChild, TSourceChild>(this IEnumerable<TParent> parents, Func<TParent, int> foreignKeySelector, Func<IEnumerable<int>, IEnumerable<TSourceChild>> sourceChildSelector)
where TSourceChild : ModelBase, new()
where TChild : RestResource, new()
where TParent : RestResource
{
var parentList = parents.Where(p => foreignKeySelector(p) != 0).ToList();
if (!parentList.Any())
{
return parents;
}
var ids = parentList.Select(foreignKeySelector).Distinct();
var childDictionary = sourceChildSelector(ids).ToDictionary(child => child.Id, child => child);
var childSetter = GetChildSetter<TParent, TChild>();
foreach (var episode in parentList)
{
childSetter.Invoke(episode, new object[] { childDictionary[foreignKeySelector(episode)].InjectTo<TChild>() });
}
return parents;
}
private static MethodInfo GetChildSetter<TParent, TChild>()
where TChild : RestResource
where TParent : RestResource
{
var key = typeof(TChild).FullName + typeof(TParent).FullName;
return SetterCache.Get(key, () =>
{
var property = typeof(TParent).GetProperties().Single(c => c.PropertyType == typeof(TChild));
return property.GetSetMethod();
});
}
}
}

View File

@@ -14,8 +14,6 @@ namespace NzbDrone.Api.Extensions.Pipelines
_cacheableSpecification = cacheableSpecification;
}
public int Order => 0;
public void Register(IPipelines pipelines)
{
pipelines.AfterRequest.AddItemToStartOfPipeline((Action<NancyContext>) Handle);

View File

@@ -7,8 +7,6 @@ namespace NzbDrone.Api.Extensions.Pipelines
{
public class CorsPipeline : IRegisterNancyPipeline
{
public int Order => 0;
public void Register(IPipelines pipelines)
{
pipelines.AfterRequest.AddItemToEndOfPipeline((Action<NancyContext>) Handle);

View File

@@ -5,7 +5,6 @@ using System.Linq;
using Nancy;
using Nancy.Bootstrapper;
using NLog;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Api.Extensions.Pipelines
{
@@ -13,8 +12,6 @@ namespace NzbDrone.Api.Extensions.Pipelines
{
private readonly Logger _logger;
public int Order => 0;
public GzipCompressionPipeline(Logger logger)
{
_logger = logger;
@@ -22,62 +19,50 @@ namespace NzbDrone.Api.Extensions.Pipelines
public void Register(IPipelines pipelines)
{
pipelines.AfterRequest.AddItemToEndOfPipeline(CompressResponse);
pipelines.AfterRequest.AddItemToEndOfPipeline(c => CompressResponse(c.Request, c.Response));
}
private void CompressResponse(NancyContext context)
private Response CompressResponse(Request request, Response response)
{
var request = context.Request;
var response = context.Response;
try
{
if (
!response.ContentType.Contains("image")
&& !response.ContentType.Contains("font")
&& request.Headers.AcceptEncoding.Any(x => x.Contains("gzip"))
&& !AlreadyGzipEncoded(response)
&& !ContentLengthIsTooSmall(response))
&& (!response.Headers.ContainsKey("Content-Encoding") || response.Headers["Content-Encoding"] != "gzip"))
{
var contents = response.Contents;
response.Headers["Content-Encoding"] = "gzip";
response.Contents = responseStream =>
var data = new MemoryStream();
response.Contents.Invoke(data);
data.Position = 0;
if (data.Length < 1024)
{
using (var gzip = new GZipStream(responseStream, CompressionMode.Compress, true))
using (var buffered = new BufferedStream(gzip, 8192))
response.Contents = stream =>
{
contents.Invoke(buffered);
}
};
data.CopyTo(stream);
stream.Flush();
};
}
else
{
response.Headers["Content-Encoding"] = "gzip";
response.Contents = s =>
{
var gzip = new GZipStream(s, CompressionMode.Compress, true);
data.CopyTo(gzip);
gzip.Close();
};
}
}
return response;
}
catch (Exception ex)
{
_logger.Error(ex, "Unable to gzip response");
_logger.ErrorException("Unable to gzip response", ex);
throw;
}
}
private static bool ContentLengthIsTooSmall(Response response)
{
var contentLength = response.Headers.GetValueOrDefault("Content-Length");
if (contentLength != null && long.Parse(contentLength) < 1024)
{
return true;
}
return false;
}
private static bool AlreadyGzipEncoded(Response response)
{
var contentEncoding = response.Headers.GetValueOrDefault("Content-Encoding");
if (contentEncoding == "gzip")
{
return true;
}
return false;
}
}
}

View File

@@ -4,8 +4,6 @@ namespace NzbDrone.Api.Extensions.Pipelines
{
public interface IRegisterNancyPipeline
{
int Order { get; }
void Register(IPipelines pipelines);
}
}

View File

@@ -14,8 +14,6 @@ namespace NzbDrone.Api.Extensions.Pipelines
_cacheableSpecification = cacheableSpecification;
}
public int Order => 0;
public void Register(IPipelines pipelines)
{
pipelines.BeforeRequest.AddItemToStartOfPipeline((Func<NancyContext, Response>) Handle);

View File

@@ -7,8 +7,6 @@ namespace NzbDrone.Api.Extensions.Pipelines
{
public class NzbDroneVersionPipeline : IRegisterNancyPipeline
{
public int Order => 0;
public void Register(IPipelines pipelines)
{
pipelines.AfterRequest.AddItemToStartOfPipeline((Action<NancyContext>) Handle);

View File

@@ -1,91 +0,0 @@
using System;
using System.Threading;
using Nancy;
using Nancy.Bootstrapper;
using NLog;
using NzbDrone.Api.ErrorManagement;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Api.Extensions.Pipelines
{
public class RequestLoggingPipeline : IRegisterNancyPipeline
{
private static readonly Logger _loggerHttp = LogManager.GetLogger("Http");
private static readonly Logger _loggerApi = LogManager.GetLogger("Api");
private static int _requestSequenceID;
private readonly NzbDroneErrorPipeline _errorPipeline;
public RequestLoggingPipeline(NzbDroneErrorPipeline errorPipeline)
{
_errorPipeline = errorPipeline;
}
public int Order => 100;
public void Register(IPipelines pipelines)
{
pipelines.BeforeRequest.AddItemToStartOfPipeline(LogStart);
pipelines.AfterRequest.AddItemToEndOfPipeline(LogEnd);
pipelines.OnError.AddItemToEndOfPipeline(LogError);
}
private Response LogStart(NancyContext context)
{
var id = Interlocked.Increment(ref _requestSequenceID);
context.Items["ApiRequestSequenceID"] = id;
context.Items["ApiRequestStartTime"] = DateTime.UtcNow;
var reqPath = GetRequestPathAndQuery(context.Request);
_loggerHttp.Trace("Req: {0} [{1}] {2}", id, context.Request.Method, reqPath);
return null;
}
private void LogEnd(NancyContext context)
{
var id = (int)context.Items["ApiRequestSequenceID"];
var startTime = (DateTime)context.Items["ApiRequestStartTime"];
var endTime = DateTime.UtcNow;
var duration = endTime - startTime;
var reqPath = GetRequestPathAndQuery(context.Request);
_loggerHttp.Trace("Res: {0} [{1}] {2}: {3}.{4} ({5} ms)", id, context.Request.Method, reqPath, (int)context.Response.StatusCode, context.Response.StatusCode, (int)duration.TotalMilliseconds);
if (context.Request.IsApiRequest())
{
_loggerApi.Debug("[{0}] {1}: {2}.{3} ({4} ms)", context.Request.Method, reqPath, (int)context.Response.StatusCode, context.Response.StatusCode, (int)duration.TotalMilliseconds);
}
}
private Response LogError(NancyContext context, Exception exception)
{
var response = _errorPipeline.HandleException(context, exception);
context.Response = response;
LogEnd(context);
context.Response = null;
return response;
}
private static string GetRequestPathAndQuery(Request request)
{
if (request.Url.Query.IsNotNullOrWhiteSpace())
{
return string.Concat(request.Url.Path, request.Url.Query);
}
else
{
return request.Url.Path;
}
}
}
}

View File

@@ -14,7 +14,7 @@ namespace NzbDrone.Api.Frontend
{
public bool IsCacheable(NancyContext context)
{
if (!RuntimeInfo.IsProduction)
if (!RuntimeInfoBase.IsProduction)
{
return false;
}

View File

@@ -1,3 +1,4 @@
using System;
using System.IO;
using NLog;
using NzbDrone.Common.Disk;

View File

@@ -74,7 +74,7 @@ namespace NzbDrone.Api.Frontend.Mappers
private string GetIndexText()
{
if (RuntimeInfo.IsProduction && _generatedContent != null)
if (RuntimeInfoBase.IsProduction && _generatedContent != null)
{
return _generatedContent;
}
@@ -106,7 +106,7 @@ namespace NzbDrone.Api.Frontend.Mappers
text = text.Replace("APP_BRANCH", _configFileProvider.Branch.ToLower());
text = text.Replace("APP_ANALYTICS", _analyticsService.IsEnabled.ToString().ToLowerInvariant());
text = text.Replace("URL_BASE", URL_BASE);
text = text.Replace("PRODUCTION", RuntimeInfo.IsProduction.ToString().ToLowerInvariant());
text = text.Replace("PRODUCTION", RuntimeInfoBase.IsProduction.ToString().ToLowerInvariant());
_generatedContent = text;

View File

@@ -67,7 +67,7 @@ namespace NzbDrone.Api.Frontend.Mappers
private string GetLoginText()
{
if (RuntimeInfo.IsProduction && _generatedContent != null)
if (RuntimeInfoBase.IsProduction && _generatedContent != null)
{
return _generatedContent;
}

View File

@@ -1,5 +1,6 @@
using System.IO;
using System.Text.RegularExpressions;
using Nancy;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;

View File

@@ -1,4 +1,5 @@
using System.IO;
using System;
using System.IO;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using NLog;
using Nancy;
@@ -21,7 +21,7 @@ namespace NzbDrone.Api.Frontend.Mappers
_diskProvider = diskProvider;
_logger = logger;
if (!RuntimeInfo.IsProduction)
if (!RuntimeInfoBase.IsProduction)
{
_caseSensitive = StringComparison.OrdinalIgnoreCase;
}
@@ -38,7 +38,7 @@ namespace NzbDrone.Api.Frontend.Mappers
if (_diskProvider.FileExists(filePath, _caseSensitive))
{
var response = new StreamResponse(() => GetContentStream(filePath), MimeTypes.GetMimeType(filePath));
return new MaterialisingResponse(response);
return response;
}
_logger.Warn("File {0} not found", filePath);

View File

@@ -20,7 +20,7 @@ namespace NzbDrone.Api.Health
private List<HealthResource> GetHealth()
{
return _healthCheckService.Results().ToResource();
return ToListResource(_healthCheckService.Results);
}
public void Handle(HealthCheckCompleteEvent message)

View File

@@ -1,7 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using System;
using NzbDrone.Api.REST;
using NzbDrone.Common.Http;
using NzbDrone.Core.HealthCheck;
namespace NzbDrone.Api.Health
@@ -10,28 +8,6 @@ namespace NzbDrone.Api.Health
{
public HealthCheckResult Type { get; set; }
public string Message { get; set; }
public HttpUri WikiUrl { get; set; }
}
public static class HealthResourceMapper
{
public static HealthResource ToResource(this HealthCheck model)
{
if (model == null) return null;
return new HealthResource
{
Id = model.Id,
Type = model.Type,
Message = model.Message,
WikiUrl = model.WikiUrl
};
}
public static List<HealthResource> ToResource(this IEnumerable<HealthCheck> models)
{
return models.Select(ToResource).ToList();
}
public Uri WikiUrl { get; set; }
}
}

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