Compare commits
257 Commits
v6.0.4.102
...
v2.0.0.485
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b03f434329 | ||
|
|
68290b0385 | ||
|
|
773274f3cc | ||
|
|
a33d8968ed | ||
|
|
e41baccd02 | ||
|
|
fef3423019 | ||
|
|
11926d8b2d | ||
|
|
4189bc6f76 | ||
|
|
601b54c244 | ||
|
|
905ab18336 | ||
|
|
f46a576df5 | ||
|
|
60231fbcae | ||
|
|
63860e783e | ||
|
|
cf8b9df5ad | ||
|
|
ff6841e410 | ||
|
|
ab49afe116 | ||
|
|
afcf136c4f | ||
|
|
d1b85444d0 | ||
|
|
63e0eba02f | ||
|
|
475c99d492 | ||
|
|
23552c3267 | ||
|
|
a49e37239e | ||
|
|
65b936ed94 | ||
|
|
359ef04861 | ||
|
|
8cf028e071 | ||
|
|
eea3419849 | ||
|
|
62bc63312d | ||
|
|
6a6d415625 | ||
|
|
1fbe82ae47 | ||
|
|
87f3cc9014 | ||
|
|
ab07a40931 | ||
|
|
10f292b225 | ||
|
|
de5ce23989 | ||
|
|
d0e226e269 | ||
|
|
d7cb5090fc | ||
|
|
416e9abca5 | ||
|
|
6dcb7768a9 | ||
|
|
baf83b4c71 | ||
|
|
285288db1a | ||
|
|
ec3dd982f6 | ||
|
|
410aa467b8 | ||
|
|
0c89a4ae8f | ||
|
|
a1edbafa8a | ||
|
|
ef5a400c68 | ||
|
|
8cc02a9d9c | ||
|
|
e83e852e0d | ||
|
|
4e10d30cf6 | ||
|
|
f335cc1af8 | ||
|
|
f4bea5512c | ||
|
|
9f8091e4d7 | ||
|
|
5aa02eb15c | ||
|
|
6bbe4ce066 | ||
|
|
563b5ef017 | ||
|
|
b9df5634bf | ||
|
|
755575d107 | ||
|
|
8eaab46488 | ||
|
|
4fbc481780 | ||
|
|
a41b5723d4 | ||
|
|
c2b66cf524 | ||
|
|
0d782e1cac | ||
|
|
edf549d0fd | ||
|
|
cf7ce9804f | ||
|
|
e8d01daf03 | ||
|
|
766520b851 | ||
|
|
14144bd4d9 | ||
|
|
7b0e40d5d0 | ||
|
|
2dbf095fd5 | ||
|
|
2a86f8c241 | ||
|
|
c184e7ddcc | ||
|
|
95c81f8905 | ||
|
|
db15949704 | ||
|
|
a63248401e | ||
|
|
1b32411219 | ||
|
|
cd7368512d | ||
|
|
a5bc4a8f11 | ||
|
|
2ae41a3404 | ||
|
|
312136a57c | ||
|
|
53e51af9c7 | ||
|
|
f852ca91c0 | ||
|
|
413dd51db1 | ||
|
|
9d93fc1092 | ||
|
|
0bbb82c67f | ||
|
|
cd8ae0d036 | ||
|
|
d726a7acb2 | ||
|
|
c94636e2b3 | ||
|
|
3749f3e2e5 | ||
|
|
1f93bec055 | ||
|
|
a003a89b14 | ||
|
|
35fca89dad | ||
|
|
e97e13e897 | ||
|
|
f8d5f1fc94 | ||
|
|
46a1ff3e2d | ||
|
|
f36d5dc881 | ||
|
|
f8b8fcfb8d | ||
|
|
de7f68570e | ||
|
|
fa006d85fd | ||
|
|
413ce1d9a7 | ||
|
|
41f769790d | ||
|
|
b63bcd16a7 | ||
|
|
b485bdaeec | ||
|
|
b70d167911 | ||
|
|
924fe80997 | ||
|
|
cd450a44bf | ||
|
|
35741b9cae | ||
|
|
c9d1807670 | ||
|
|
94886e767b | ||
|
|
e4c3418987 | ||
|
|
5613ab05e0 | ||
|
|
372442af2c | ||
|
|
28c45f941b | ||
|
|
ea1616586f | ||
|
|
e48600da42 | ||
|
|
5d9d2e684e | ||
|
|
2e392e0f5e | ||
|
|
83370ddbbb | ||
|
|
c20b152c28 | ||
|
|
bf5067466d | ||
|
|
ec7f749541 | ||
|
|
56ecbf4a31 | ||
|
|
1b39911135 | ||
|
|
6aaefae2d5 | ||
|
|
db9d601115 | ||
|
|
e7331539f0 | ||
|
|
58bd57bed6 | ||
|
|
7a58082cd7 | ||
|
|
2e08f195e4 | ||
|
|
a1a5e29c3e | ||
|
|
5033886b90 | ||
|
|
29419d6575 | ||
|
|
3c22f68f5a | ||
|
|
a0d98951aa | ||
|
|
70f7404499 | ||
|
|
abd70f5381 | ||
|
|
878e973081 | ||
|
|
2bf3b9e7dd | ||
|
|
2326db0dea | ||
|
|
3590fedeaf | ||
|
|
f4866cae69 | ||
|
|
149d191f62 | ||
|
|
bb9bd63382 | ||
|
|
34fda24124 | ||
|
|
c8d10829a0 | ||
|
|
ae2bdb757a | ||
|
|
714ad075fc | ||
|
|
87a05df2fd | ||
|
|
f3263efa52 | ||
|
|
1cad11d207 | ||
|
|
781df8b20a | ||
|
|
ebcce05588 | ||
|
|
bbf2134fe1 | ||
|
|
081c5fc332 | ||
|
|
47915d5e05 | ||
|
|
47e221d9a0 | ||
|
|
bf485f6f2c | ||
|
|
b365d8a537 | ||
|
|
fee8da88a6 | ||
|
|
cc0dbf1af4 | ||
|
|
836131ebb1 | ||
|
|
9a870a3709 | ||
|
|
afe05189da | ||
|
|
2abaef16f1 | ||
|
|
37d5a3f2ad | ||
|
|
be4d70e3a9 | ||
|
|
79043f2c64 | ||
|
|
1dab0aee6a | ||
|
|
9b162f2d5e | ||
|
|
5518cf5362 | ||
|
|
f7e3d9b4c2 | ||
|
|
6d9a952bd1 | ||
|
|
3501e33722 | ||
|
|
fa89d33900 | ||
|
|
0af48fb2e8 | ||
|
|
7e9f0d0522 | ||
|
|
1f8bd8e1e9 | ||
|
|
2855090005 | ||
|
|
060b9f6fd1 | ||
|
|
9304547c95 | ||
|
|
c56c83e169 | ||
|
|
c6fa883662 | ||
|
|
4043d07ab1 | ||
|
|
8af3348e7f | ||
|
|
49d0d4c357 | ||
|
|
47b1157b96 | ||
|
|
adc79f0eba | ||
|
|
6b117427f8 | ||
|
|
7884dd9a39 | ||
|
|
45d8b1e2ad | ||
|
|
cf306f4aba | ||
|
|
d7aa23388e | ||
|
|
82a99b7f80 | ||
|
|
2f6d9e191e | ||
|
|
0782a15979 | ||
|
|
ddd119a4eb | ||
|
|
d4788b4cae | ||
|
|
812999423b | ||
|
|
657730f4d2 | ||
|
|
0255eb3aca | ||
|
|
fc15daa37e | ||
|
|
10264a5bfb | ||
|
|
ef044f1ff5 | ||
|
|
ef03e9e9a7 | ||
|
|
3bd7c09acf | ||
|
|
fbd2f8dea4 | ||
|
|
15e07f72d4 | ||
|
|
f25bfe9d28 | ||
|
|
d5e720c404 | ||
|
|
c9a8ebc2e6 | ||
|
|
5e7e816c03 | ||
|
|
f56076a135 | ||
|
|
54dd527f01 | ||
|
|
c6eb19c04d | ||
|
|
38b65ba27d | ||
|
|
a2a49ce934 | ||
|
|
047d5a4388 | ||
|
|
aae69ff49a | ||
|
|
da451cfe03 | ||
|
|
01e2f4e7e5 | ||
|
|
8aacc61c50 | ||
|
|
111e401a2c | ||
|
|
8d91f18823 | ||
|
|
cea6469ab8 | ||
|
|
ced7a7dce2 | ||
|
|
20a2cfe260 | ||
|
|
5b0a285b84 | ||
|
|
68ea8a551c | ||
|
|
2e36d35815 | ||
|
|
ed2e4d0f1d | ||
|
|
0bdc137093 | ||
|
|
cd7e208efa | ||
|
|
73840dcacc | ||
|
|
e45d4f60a4 | ||
|
|
782efcfaf1 | ||
|
|
76a7d4f866 | ||
|
|
d61976251e | ||
|
|
2487e8ed49 | ||
|
|
54bc642476 | ||
|
|
6577b0a721 | ||
|
|
bcd67dee5e | ||
|
|
e6705db743 | ||
|
|
dd7fdd8ace | ||
|
|
a1f112e62f | ||
|
|
053c730799 | ||
|
|
61579cfb7e | ||
|
|
92d706a10d | ||
|
|
6ae5829439 | ||
|
|
8252a2a60f | ||
|
|
009dc14805 | ||
|
|
e0ff25d5be | ||
|
|
ad7d571b24 | ||
|
|
598b5322b7 | ||
|
|
76505bdaa1 | ||
|
|
d64d35361c | ||
|
|
d5ef451bb4 | ||
|
|
3140d5d4b0 | ||
|
|
fdb5ccdae1 | ||
|
|
a2ce435239 | ||
|
|
a34e69b35b |
2
.gitignore
vendored
@@ -133,3 +133,5 @@ output/*
|
||||
|
||||
_start
|
||||
_temp_*/**/*
|
||||
|
||||
src/.idea/
|
||||
|
||||
BIN
Logo/1024.png
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 55 KiB |
BIN
Logo/128.png
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 3.4 KiB |
BIN
Logo/16.png
|
Before Width: | Height: | Size: 707 B After Width: | Height: | Size: 490 B |
BIN
Logo/256.png
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 7.2 KiB |
BIN
Logo/32.png
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.1 KiB |
BIN
Logo/400.png
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 15 KiB |
BIN
Logo/48.png
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 1.5 KiB |
BIN
Logo/512.png
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 21 KiB |
BIN
Logo/64.png
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 2.0 KiB |
BIN
Logo/800.png
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 34 KiB |
BIN
Logo/96-Outline-White.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
52
README.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# 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/)
|
||||
@@ -25,7 +25,7 @@ gulp.task('copyHtml', function () {
|
||||
});
|
||||
|
||||
gulp.task('copyContent', function () {
|
||||
return gulp.src([paths.src.content + '**/*.*', '!**/*.less'])
|
||||
return gulp.src([paths.src.content + '**/*.*', '!**/*.less', '!**/*.css'])
|
||||
.pipe(gulp.dest(paths.dest.content))
|
||||
.pipe(livereload());
|
||||
});
|
||||
|
||||
11
gulp/less.js
@@ -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');
|
||||
@@ -16,6 +16,10 @@ gulp.task('less', function() {
|
||||
paths.src.content + 'bootstrap.less',
|
||||
paths.src.content + 'theme.less',
|
||||
paths.src.content + 'overrides.less',
|
||||
paths.src.content + 'bootstrap.toggle-switch.css',
|
||||
paths.src.content + 'fullcalendar.css',
|
||||
paths.src.content + 'Messenger/messenger.css',
|
||||
paths.src.content + 'Messenger/messenger.flat.css',
|
||||
paths.src.root + 'Series/series.less',
|
||||
paths.src.root + 'Activity/activity.less',
|
||||
paths.src.root + 'AddSeries/addSeries.less',
|
||||
@@ -33,12 +37,13 @@ gulp.task('less', function() {
|
||||
.pipe(sourcemaps.init())
|
||||
.pipe(less({
|
||||
dumpLineNumbers : 'false',
|
||||
compress : true,
|
||||
yuicompress : true,
|
||||
compress : false,
|
||||
yuicompress : false,
|
||||
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))
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
"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",
|
||||
|
||||
53
readme.md
@@ -1,53 +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, 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/)
|
||||
|
||||
### 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-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/)
|
||||
@@ -52,8 +52,7 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NLog.4.3.11\lib\net40\NLog.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<HintPath>..\packages\NLog.4.4.3\lib\net40\NLog.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="NLog" version="4.3.11" targetFramework="net40" />
|
||||
<package id="NLog" version="4.4.3" targetFramework="net40" />
|
||||
</packages>
|
||||
@@ -43,9 +43,9 @@
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<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 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>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
@@ -281,4 +281,4 @@
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Newtonsoft.Json" version="6.0.6" targetFramework="net40" />
|
||||
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net40" />
|
||||
</packages>
|
||||
@@ -5,7 +5,6 @@ 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;
|
||||
|
||||
namespace Microsoft.AspNet.SignalR.Owin
|
||||
|
||||
@@ -42,17 +42,17 @@
|
||||
<HintPath>..\packages\NBuilder.4.0.0\lib\net40\FizzWare.NBuilder.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="FluentAssertions, Version=4.18.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<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>
|
||||
<Reference Include="FluentAssertions.Core, Version=4.18.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.Core.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<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="nunit.framework, Version=3.5.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NUnit.3.5.0\lib\net40\nunit.framework.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<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>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="FluentAssertions" version="4.18.0" targetFramework="net40" />
|
||||
<package id="FluentAssertions" version="4.19.0" targetFramework="net40" />
|
||||
<package id="Moq" version="4.0.10827" />
|
||||
<package id="NBuilder" version="4.0.0" targetFramework="net40" />
|
||||
<package id="NUnit" version="3.5.0" targetFramework="net40" />
|
||||
<package id="NUnit" version="3.6.0" targetFramework="net40" />
|
||||
</packages>
|
||||
@@ -33,12 +33,12 @@ namespace NzbDrone.Api.Authentication
|
||||
{
|
||||
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);
|
||||
@@ -64,10 +64,13 @@ namespace NzbDrone.Api.Authentication
|
||||
new DefaultHmacProvider(new PassphraseKeyGenerator(_configService.HmacPassphrase, Encoding.ASCII.GetBytes(_configService.HmacSalt)))
|
||||
);
|
||||
|
||||
FormsAuthentication.FormsAuthenticationCookieName = "_ncfa_sonarr";
|
||||
|
||||
FormsAuthentication.Enable(pipelines, new FormsAuthenticationConfiguration
|
||||
{
|
||||
RedirectUrl = _configFileProvider.UrlBase + "/login",
|
||||
UserMapper = _authenticationService,
|
||||
Path = _configFileProvider.UrlBase,
|
||||
CryptographyConfiguration = cryptographyConfiguration
|
||||
});
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ namespace NzbDrone.Api.Calendar
|
||||
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
|
||||
@@ -46,6 +47,7 @@ namespace NzbDrone.Api.Calendar
|
||||
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);
|
||||
@@ -73,6 +75,11 @@ namespace NzbDrone.Api.Calendar
|
||||
premiersOnly = bool.Parse(queryPremiersOnly.Value);
|
||||
}
|
||||
|
||||
if (queryAsAllDay.HasValue)
|
||||
{
|
||||
asAllDay = bool.Parse(queryAsAllDay.Value);
|
||||
}
|
||||
|
||||
if (queryTags.HasValue)
|
||||
{
|
||||
var tagInput = (string)queryTags.Value.ToString();
|
||||
@@ -102,11 +109,19 @@ namespace NzbDrone.Api.Calendar
|
||||
var occurrence = calendar.Create<Event>();
|
||||
occurrence.Uid = "NzbDrone_episode_" + episode.Id;
|
||||
occurrence.Status = episode.HasFile ? EventStatus.Confirmed : EventStatus.Tentative;
|
||||
occurrence.Start = new CalDateTime(episode.AirDateUtc.Value) { HasTime = true };
|
||||
occurrence.End = new CalDateTime(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:
|
||||
|
||||
@@ -73,14 +73,14 @@ namespace NzbDrone.Api.ClientSchema
|
||||
|
||||
if (propertyInfo.PropertyType == typeof(int))
|
||||
{
|
||||
var value = Convert.ToInt32(field.Value);
|
||||
propertyInfo.SetValue(target, value, null);
|
||||
var value = field.Value.ToString().ParseInt32();
|
||||
propertyInfo.SetValue(target, value ?? 0, null);
|
||||
}
|
||||
|
||||
else if (propertyInfo.PropertyType == typeof(long))
|
||||
{
|
||||
var value = Convert.ToInt64(field.Value);
|
||||
propertyInfo.SetValue(target, value, null);
|
||||
var value = field.Value.ToString().ParseInt64();
|
||||
propertyInfo.SetValue(target, value ?? 0, null);
|
||||
}
|
||||
|
||||
else if (propertyInfo.PropertyType == typeof(int?))
|
||||
|
||||
@@ -20,6 +20,7 @@ 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; }
|
||||
}
|
||||
@@ -44,6 +45,7 @@ namespace NzbDrone.Api.Config
|
||||
|
||||
SkipFreeSpaceCheckWhenImporting = model.SkipFreeSpaceCheckWhenImporting,
|
||||
CopyUsingHardlinks = model.CopyUsingHardlinks,
|
||||
ImportExtraFiles = model.ImportExtraFiles,
|
||||
ExtraFileExtensions = model.ExtraFileExtensions,
|
||||
EnableMediaInfo = model.EnableMediaInfo
|
||||
};
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using NLog;
|
||||
using NzbDrone.Api.REST;
|
||||
using NzbDrone.Core.Datastore.Events;
|
||||
using NzbDrone.Core.MediaFiles;
|
||||
using NzbDrone.Core.MediaFiles.Events;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.Core.Exceptions;
|
||||
using NzbDrone.SignalR;
|
||||
using HttpStatusCode = System.Net.HttpStatusCode;
|
||||
|
||||
namespace NzbDrone.Api.EpisodeFiles
|
||||
{
|
||||
@@ -16,24 +15,21 @@ namespace NzbDrone.Api.EpisodeFiles
|
||||
IHandle<EpisodeFileAddedEvent>
|
||||
{
|
||||
private readonly IMediaFileService _mediaFileService;
|
||||
private readonly IRecycleBinProvider _recycleBinProvider;
|
||||
private readonly IDeleteMediaFiles _mediaFileDeletionService;
|
||||
private readonly ISeriesService _seriesService;
|
||||
private readonly IQualityUpgradableSpecification _qualityUpgradableSpecification;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public EpisodeFileModule(IBroadcastSignalRMessage signalRBroadcaster,
|
||||
IMediaFileService mediaFileService,
|
||||
IRecycleBinProvider recycleBinProvider,
|
||||
IDeleteMediaFiles mediaFileDeletionService,
|
||||
ISeriesService seriesService,
|
||||
IQualityUpgradableSpecification qualityUpgradableSpecification,
|
||||
Logger logger)
|
||||
IQualityUpgradableSpecification qualityUpgradableSpecification)
|
||||
: base(signalRBroadcaster)
|
||||
{
|
||||
_mediaFileService = mediaFileService;
|
||||
_recycleBinProvider = recycleBinProvider;
|
||||
_mediaFileDeletionService = mediaFileDeletionService;
|
||||
_seriesService = seriesService;
|
||||
_qualityUpgradableSpecification = qualityUpgradableSpecification;
|
||||
_logger = logger;
|
||||
GetResourceById = GetEpisodeFile;
|
||||
GetResourceAll = GetEpisodeFiles;
|
||||
UpdateResource = SetQuality;
|
||||
@@ -72,12 +68,15 @@ namespace NzbDrone.Api.EpisodeFiles
|
||||
private void DeleteEpisodeFile(int id)
|
||||
{
|
||||
var episodeFile = _mediaFileService.Get(id);
|
||||
var series = _seriesService.GetSeries(episodeFile.SeriesId);
|
||||
var fullPath = Path.Combine(series.Path, episodeFile.RelativePath);
|
||||
|
||||
_logger.Info("Deleting episode file: {0}", fullPath);
|
||||
_recycleBinProvider.DeleteFile(fullPath);
|
||||
_mediaFileService.Delete(episodeFile, DeleteMediaFileReason.Manual);
|
||||
if (episodeFile == null)
|
||||
{
|
||||
throw new NzbDroneClientException(HttpStatusCode.NotFound, "Episode file not found");
|
||||
}
|
||||
|
||||
var series = _seriesService.GetSeries(episodeFile.SeriesId);
|
||||
|
||||
_mediaFileDeletionService.DeleteEpisodeFile(series, episodeFile);
|
||||
}
|
||||
|
||||
public void Handle(EpisodeFileAddedEvent message)
|
||||
|
||||
@@ -3,7 +3,6 @@ using NzbDrone.Api.REST;
|
||||
using NzbDrone.Core.Tv;
|
||||
using NzbDrone.Core.DecisionEngine;
|
||||
using NzbDrone.SignalR;
|
||||
using Nancy;
|
||||
|
||||
namespace NzbDrone.Api.Episodes
|
||||
{
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace NzbDrone.Api.Episodes
|
||||
{
|
||||
public abstract class EpisodeModuleWithSignalR : NzbDroneRestModuleWithSignalR<EpisodeResource, Episode>,
|
||||
IHandle<EpisodeGrabbedEvent>,
|
||||
IHandle<EpisodeDownloadedEvent>
|
||||
IHandle<EpisodeImportedEvent>
|
||||
{
|
||||
protected readonly IEpisodeService _episodeService;
|
||||
protected readonly ISeriesService _seriesService;
|
||||
@@ -115,9 +115,14 @@ namespace NzbDrone.Api.Episodes
|
||||
}
|
||||
}
|
||||
|
||||
public void Handle(EpisodeDownloadedEvent message)
|
||||
public void Handle(EpisodeImportedEvent message)
|
||||
{
|
||||
foreach (var episode in message.Episode.Episodes)
|
||||
if (!message.NewDownload)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var episode in message.EpisodeInfo.Episodes)
|
||||
{
|
||||
BroadcastResourceChange(ModelAction.Updated, episode.Id);
|
||||
}
|
||||
|
||||
@@ -63,18 +63,16 @@ namespace NzbDrone.Api.ErrorManagement
|
||||
}.AsResponse(HttpStatusCode.Conflict);
|
||||
}
|
||||
|
||||
var sqlErrorMessage = string.Format("[{0} {1}]", context.Request.Method, context.Request.Path);
|
||||
|
||||
_logger.Error(sqLiteException, sqlErrorMessage);
|
||||
_logger.Error(sqLiteException, "[{0} {1}]", context.Request.Method, context.Request.Path);
|
||||
}
|
||||
|
||||
_logger.Fatal(exception, "Request Failed");
|
||||
|
||||
_logger.Fatal(exception, "Request Failed. {0} {1}", context.Request.Method, context.Request.Path);
|
||||
|
||||
return new ErrorModel
|
||||
{
|
||||
Message = exception.Message,
|
||||
Description = exception.ToString()
|
||||
}.AsResponse(HttpStatusCode.InternalServerError);
|
||||
{
|
||||
Message = exception.Message,
|
||||
Description = exception.ToString()
|
||||
}.AsResponse(HttpStatusCode.InternalServerError);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ namespace NzbDrone.Api.Extensions.Pipelines
|
||||
context.Items["ApiRequestStartTime"] = DateTime.UtcNow;
|
||||
|
||||
var reqPath = GetRequestPathAndQuery(context.Request);
|
||||
|
||||
|
||||
_loggerHttp.Trace("Req: {0} [{1}] {2}", id, context.Request.Method, reqPath);
|
||||
|
||||
return null;
|
||||
@@ -80,7 +80,7 @@ namespace NzbDrone.Api.Extensions.Pipelines
|
||||
{
|
||||
if (request.Url.Query.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
return string.Concat(request.Url.Path, "?", request.Url.Query);
|
||||
return string.Concat(request.Url.Path, request.Url.Query);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -88,4 +88,4 @@ namespace NzbDrone.Api.Extensions.Pipelines
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
46
src/NzbDrone.Api/Extensions/Pipelines/UrlBasePipeline.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using Nancy;
|
||||
using Nancy.Bootstrapper;
|
||||
using Nancy.Responses;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Configuration;
|
||||
|
||||
namespace NzbDrone.Api.Extensions.Pipelines
|
||||
{
|
||||
public class UrlBasePipeline : IRegisterNancyPipeline
|
||||
{
|
||||
private readonly string _urlBase;
|
||||
|
||||
public UrlBasePipeline(IConfigFileProvider configFileProvider)
|
||||
{
|
||||
_urlBase = configFileProvider.UrlBase;
|
||||
}
|
||||
|
||||
public int Order => 99;
|
||||
|
||||
public void Register(IPipelines pipelines)
|
||||
{
|
||||
if (_urlBase.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
pipelines.BeforeRequest.AddItemToStartOfPipeline((Func<NancyContext, Response>) Handle);
|
||||
}
|
||||
}
|
||||
|
||||
private Response Handle(NancyContext context)
|
||||
{
|
||||
var basePath = context.Request.Url.BasePath;
|
||||
|
||||
if (basePath.IsNullOrWhiteSpace())
|
||||
{
|
||||
return new RedirectResponse($"{_urlBase}{context.Request.Path}{context.Request.Url.Query}");
|
||||
}
|
||||
|
||||
if (_urlBase != basePath)
|
||||
{
|
||||
return new NotFoundResponse();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ namespace NzbDrone.Api.Frontend
|
||||
{
|
||||
public bool IsCacheable(NancyContext context)
|
||||
{
|
||||
if (!RuntimeInfoBase.IsProduction)
|
||||
if (!RuntimeInfo.IsProduction)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using Nancy;
|
||||
@@ -17,7 +17,7 @@ namespace NzbDrone.Api.Frontend.Mappers
|
||||
private readonly IAnalyticsService _analyticsService;
|
||||
private readonly Func<ICacheBreakerProvider> _cacheBreakProviderFactory;
|
||||
private readonly string _indexPath;
|
||||
private static readonly Regex ReplaceRegex = new Regex(@"(?:(?<attribute>href|src)=\"")(?<path>.*?(?<extension>css|js|png|ico|ics))(?:\"")(?:\s(?<nohash>data-no-hash))?", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
private static readonly Regex ReplaceRegex = new Regex(@"(?:(?<attribute>href|src)=\"")(?<path>.*?(?<extension>css|js|png|ico|ics|svg))(?:\"")(?:\s(?<nohash>data-no-hash))?", RegexOptions.Compiled | RegexOptions.IgnoreCase);
|
||||
|
||||
private static string API_KEY;
|
||||
private static string URL_BASE;
|
||||
@@ -74,7 +74,7 @@ namespace NzbDrone.Api.Frontend.Mappers
|
||||
|
||||
private string GetIndexText()
|
||||
{
|
||||
if (RuntimeInfoBase.IsProduction && _generatedContent != null)
|
||||
if (RuntimeInfo.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", RuntimeInfoBase.IsProduction.ToString().ToLowerInvariant());
|
||||
text = text.Replace("PRODUCTION", RuntimeInfo.IsProduction.ToString().ToLowerInvariant());
|
||||
|
||||
_generatedContent = text;
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace NzbDrone.Api.Frontend.Mappers
|
||||
|
||||
private string GetLoginText()
|
||||
{
|
||||
if (RuntimeInfoBase.IsProduction && _generatedContent != null)
|
||||
if (RuntimeInfo.IsProduction && _generatedContent != null)
|
||||
{
|
||||
return _generatedContent;
|
||||
}
|
||||
|
||||
@@ -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 (!RuntimeInfoBase.IsProduction)
|
||||
if (!RuntimeInfo.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 response;
|
||||
return new MaterialisingResponse(response);
|
||||
}
|
||||
|
||||
_logger.Warn("File {0} not found", filePath);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Nancy.Responses;
|
||||
@@ -38,20 +38,6 @@ namespace NzbDrone.Api.Frontend
|
||||
return new NotFoundResponse();
|
||||
}
|
||||
|
||||
//Redirect to the subfolder if the request went to the base URL
|
||||
if (path.Equals("/"))
|
||||
{
|
||||
var urlBase = _configFileProvider.UrlBase;
|
||||
|
||||
if (!string.IsNullOrEmpty(urlBase))
|
||||
{
|
||||
if (Request.Url.BasePath != urlBase)
|
||||
{
|
||||
return new RedirectResponse(urlBase + "/");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var mapper = _requestMappers.SingleOrDefault(m => m.CanHandle(path));
|
||||
|
||||
if (mapper != null)
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace NzbDrone.Api.Indexers
|
||||
|
||||
private Response DownloadRelease(ReleaseResource release)
|
||||
{
|
||||
var remoteEpisode = _remoteEpisodeCache.Find(release.Guid);
|
||||
var remoteEpisode = _remoteEpisodeCache.Find(GetCacheKey(release));
|
||||
|
||||
if (remoteEpisode == null)
|
||||
{
|
||||
@@ -96,7 +96,7 @@ namespace NzbDrone.Api.Indexers
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, "Episode search failed: " + ex.Message);
|
||||
_logger.Error(ex, "Episode search failed");
|
||||
}
|
||||
|
||||
return new List<ReleaseResource>();
|
||||
@@ -113,8 +113,14 @@ namespace NzbDrone.Api.Indexers
|
||||
|
||||
protected override ReleaseResource MapDecision(DownloadDecision decision, int initialWeight)
|
||||
{
|
||||
_remoteEpisodeCache.Set(decision.RemoteEpisode.Release.Guid, decision.RemoteEpisode, TimeSpan.FromMinutes(30));
|
||||
return base.MapDecision(decision, initialWeight);
|
||||
var resource = base.MapDecision(decision, initialWeight);
|
||||
_remoteEpisodeCache.Set(GetCacheKey(resource), decision.RemoteEpisode, TimeSpan.FromMinutes(30));
|
||||
return resource;
|
||||
}
|
||||
|
||||
private string GetCacheKey(ReleaseResource resource)
|
||||
{
|
||||
return string.Concat(resource.IndexerId, "_", resource.Guid);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,9 +24,9 @@ namespace NzbDrone.Api
|
||||
|
||||
protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
|
||||
{
|
||||
Logger.Info("Starting NzbDrone API");
|
||||
Logger.Info("Starting Web Server");
|
||||
|
||||
if (RuntimeInfoBase.IsProduction)
|
||||
if (RuntimeInfo.IsProduction)
|
||||
{
|
||||
DiagnosticsHook.Disable(pipelines);
|
||||
}
|
||||
|
||||
@@ -41,20 +41,17 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="antlr.runtime, Version=2.7.6.2, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Ical.Net.2.2.25\lib\net40\antlr.runtime.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<HintPath>..\packages\Ical.Net.2.2.32\lib\net40\antlr.runtime.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="FluentValidation, Version=6.2.1.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\FluentValidation.6.2.1.0\lib\portable-net40+sl50+wp80+win8+wpa81\FluentValidation.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Ical.Net, Version=2.1.0.30332, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Ical.Net.2.2.25\lib\net40\Ical.Net.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Reference Include="Ical.Net, Version=2.1.0.18776, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Ical.Net.2.2.32\lib\net40\Ical.Net.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Ical.Net.Collections, Version=2.1.0.30331, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Ical.Net.2.2.25\lib\net40\Ical.Net.Collections.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Reference Include="Ical.Net.Collections, Version=2.1.0.18775, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Ical.Net.2.2.32\lib\net40\Ical.Net.Collections.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Nancy, Version=1.4.2.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Nancy.1.4.3\lib\net40\Nancy.dll</HintPath>
|
||||
@@ -68,17 +65,15 @@
|
||||
<HintPath>..\packages\Nancy.Authentication.Forms.1.4.1\lib\net40\Nancy.Authentication.Forms.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<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 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>
|
||||
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NLog.4.3.11\lib\net40\NLog.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<HintPath>..\packages\NLog.4.4.3\lib\net40\NLog.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NodaTime, Version=1.3.0.0, Culture=neutral, PublicKeyToken=4226afe0d9b296d1, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Ical.Net.2.2.25\lib\net40\NodaTime.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<HintPath>..\packages\Ical.Net.2.2.32\lib\net40\NodaTime.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
@@ -111,6 +106,7 @@
|
||||
<Compile Include="Commands\CommandResource.cs" />
|
||||
<Compile Include="Extensions\AccessControlHeaders.cs" />
|
||||
<Compile Include="Extensions\Pipelines\CorsPipeline.cs" />
|
||||
<Compile Include="Extensions\Pipelines\UrlBasePipeline.cs" />
|
||||
<Compile Include="Extensions\Pipelines\RequestLoggingPipeline.cs" />
|
||||
<Compile Include="Frontend\Mappers\LoginHtmlMapper.cs" />
|
||||
<Compile Include="Frontend\Mappers\RobotsTxtMapper.cs" />
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace NzbDrone.Api.Series
|
||||
|
||||
public static List<Season> ToModel(this IEnumerable<SeasonResource> resources)
|
||||
{
|
||||
return resources.Select(ToModel).ToList();
|
||||
return resources?.Select(ToModel).ToList() ?? new List<Season>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,12 +29,14 @@ namespace NzbDrone.Api.Series
|
||||
|
||||
{
|
||||
private readonly ISeriesService _seriesService;
|
||||
private readonly IAddSeriesService _addSeriesService;
|
||||
private readonly ISeriesStatisticsService _seriesStatisticsService;
|
||||
private readonly ISceneMappingService _sceneMappingService;
|
||||
private readonly IMapCoversToLocal _coverMapper;
|
||||
|
||||
public SeriesModule(IBroadcastSignalRMessage signalRBroadcaster,
|
||||
ISeriesService seriesService,
|
||||
IAddSeriesService addSeriesService,
|
||||
ISeriesStatisticsService seriesStatisticsService,
|
||||
ISceneMappingService sceneMappingService,
|
||||
IMapCoversToLocal coverMapper,
|
||||
@@ -48,6 +50,7 @@ namespace NzbDrone.Api.Series
|
||||
: base(signalRBroadcaster)
|
||||
{
|
||||
_seriesService = seriesService;
|
||||
_addSeriesService = addSeriesService;
|
||||
_seriesStatisticsService = seriesStatisticsService;
|
||||
_sceneMappingService = sceneMappingService;
|
||||
|
||||
@@ -74,7 +77,6 @@ namespace NzbDrone.Api.Series
|
||||
|
||||
PostValidator.RuleFor(s => s.Path).IsValidPath().When(s => s.RootFolderPath.IsNullOrWhiteSpace());
|
||||
PostValidator.RuleFor(s => s.RootFolderPath).IsValidPath().When(s => s.Path.IsNullOrWhiteSpace());
|
||||
PostValidator.RuleFor(s => s.Title).NotEmpty();
|
||||
PostValidator.RuleFor(s => s.TvdbId).GreaterThan(0).SetValidator(seriesExistsValidator);
|
||||
|
||||
PutValidator.RuleFor(s => s.Path).IsValidPath();
|
||||
@@ -114,7 +116,7 @@ namespace NzbDrone.Api.Series
|
||||
{
|
||||
var model = seriesResource.ToModel();
|
||||
|
||||
return _seriesService.AddSeries(model).Id;
|
||||
return _addSeriesService.AddSeries(model).Id;
|
||||
}
|
||||
|
||||
private void UpdateSeries(SeriesResource seriesResource)
|
||||
@@ -180,7 +182,7 @@ namespace NzbDrone.Api.Series
|
||||
|
||||
foreach (var season in resource.Seasons)
|
||||
{
|
||||
season.Statistics = SeasonStatisticsResourceMapper.ToResource(dictSeasonStats.GetValueOrDefault(season.SeasonNumber));
|
||||
season.Statistics = dictSeasonStats.GetValueOrDefault(season.SeasonNumber).ToResource();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,19 +207,9 @@ namespace NzbDrone.Api.Series
|
||||
|
||||
public static Core.Tv.Series ToModel(this SeriesResource resource, Core.Tv.Series series)
|
||||
{
|
||||
series.TvdbId = resource.TvdbId;
|
||||
var updatedSeries = resource.ToModel();
|
||||
|
||||
series.Seasons = resource.Seasons.ToModel();
|
||||
series.Path = resource.Path;
|
||||
series.ProfileId = resource.ProfileId;
|
||||
|
||||
series.SeasonFolder = resource.SeasonFolder;
|
||||
series.Monitored = resource.Monitored;
|
||||
|
||||
series.SeriesType = resource.SeriesType;
|
||||
series.RootFolderPath = resource.RootFolderPath;
|
||||
series.Tags = resource.Tags;
|
||||
series.AddOptions = resource.AddOptions;
|
||||
series.ApplyChanges(updatedSeries);
|
||||
|
||||
return series;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ namespace NzbDrone.Api.System
|
||||
{
|
||||
private readonly IAppFolderInfo _appFolderInfo;
|
||||
private readonly IRuntimeInfo _runtimeInfo;
|
||||
private readonly IPlatformInfo _platformInfo;
|
||||
private readonly IOsInfo _osInfo;
|
||||
private readonly IRouteCacheProvider _routeCacheProvider;
|
||||
private readonly IConfigFileProvider _configFileProvider;
|
||||
private readonly IMainDatabase _database;
|
||||
@@ -20,14 +22,17 @@ namespace NzbDrone.Api.System
|
||||
|
||||
public SystemModule(IAppFolderInfo appFolderInfo,
|
||||
IRuntimeInfo runtimeInfo,
|
||||
IPlatformInfo platformInfo,
|
||||
IOsInfo osInfo,
|
||||
IRouteCacheProvider routeCacheProvider,
|
||||
IConfigFileProvider configFileProvider,
|
||||
IMainDatabase database,
|
||||
ILifecycleService lifecycleService)
|
||||
: base("system")
|
||||
ILifecycleService lifecycleService) : base("system")
|
||||
{
|
||||
_appFolderInfo = appFolderInfo;
|
||||
_runtimeInfo = runtimeInfo;
|
||||
_platformInfo = platformInfo;
|
||||
_osInfo = osInfo;
|
||||
_routeCacheProvider = routeCacheProvider;
|
||||
_configFileProvider = configFileProvider;
|
||||
_database = database;
|
||||
@@ -41,27 +46,29 @@ namespace NzbDrone.Api.System
|
||||
private Response GetStatus()
|
||||
{
|
||||
return new
|
||||
{
|
||||
Version = BuildInfo.Version.ToString(),
|
||||
BuildTime = BuildInfo.BuildDateTime,
|
||||
IsDebug = BuildInfo.IsDebug,
|
||||
IsProduction = RuntimeInfoBase.IsProduction,
|
||||
IsAdmin = _runtimeInfo.IsAdmin,
|
||||
IsUserInteractive = RuntimeInfoBase.IsUserInteractive,
|
||||
StartupPath = _appFolderInfo.StartUpFolder,
|
||||
AppData = _appFolderInfo.GetAppDataPath(),
|
||||
OsVersion = OsInfo.Version.ToString(),
|
||||
IsMonoRuntime = OsInfo.IsMonoRuntime,
|
||||
IsMono = OsInfo.IsNotWindows,
|
||||
IsLinux = OsInfo.IsLinux,
|
||||
IsOsx = OsInfo.IsOsx,
|
||||
IsWindows = OsInfo.IsWindows,
|
||||
Branch = _configFileProvider.Branch,
|
||||
Authentication = _configFileProvider.AuthenticationMethod,
|
||||
SqliteVersion = _database.Version,
|
||||
UrlBase = _configFileProvider.UrlBase,
|
||||
RuntimeVersion = _runtimeInfo.RuntimeVersion
|
||||
}.AsResponse();
|
||||
{
|
||||
Version = BuildInfo.Version.ToString(),
|
||||
BuildTime = BuildInfo.BuildDateTime,
|
||||
IsDebug = BuildInfo.IsDebug,
|
||||
IsProduction = RuntimeInfo.IsProduction,
|
||||
IsAdmin = _runtimeInfo.IsAdmin,
|
||||
IsUserInteractive = RuntimeInfo.IsUserInteractive,
|
||||
StartupPath = _appFolderInfo.StartUpFolder,
|
||||
AppData = _appFolderInfo.GetAppDataPath(),
|
||||
OsName = _osInfo.Name,
|
||||
OsVersion = _osInfo.Version,
|
||||
IsMonoRuntime = PlatformInfo.IsMono,
|
||||
IsMono = PlatformInfo.IsMono,
|
||||
IsLinux = OsInfo.IsLinux,
|
||||
IsOsx = OsInfo.IsOsx,
|
||||
IsWindows = OsInfo.IsWindows,
|
||||
Branch = _configFileProvider.Branch,
|
||||
Authentication = _configFileProvider.AuthenticationMethod,
|
||||
SqliteVersion = _database.Version,
|
||||
UrlBase = _configFileProvider.UrlBase,
|
||||
RuntimeVersion = _platformInfo.Version,
|
||||
RuntimeName = PlatformInfo.Platform
|
||||
}.AsResponse();
|
||||
}
|
||||
|
||||
private Response GetRoutes()
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace NzbDrone.Api
|
||||
/// </summary>
|
||||
public static IEnumerable<Func<Assembly, bool>> DefaultAutoRegisterIgnoredAssemblies = new Func<Assembly, bool>[]
|
||||
{
|
||||
asm => !asm.FullName.StartsWith("Nancy.", StringComparison.InvariantCulture),
|
||||
asm => !asm.FullName.StartsWith("Nancy.", StringComparison.InvariantCulture)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="FluentMigrator" publicKeyToken="aacfc7de5acabf05" culture="neutral" />
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="FluentValidation" version="6.2.1.0" targetFramework="net40" />
|
||||
<package id="Ical.Net" version="2.2.25" targetFramework="net40" />
|
||||
<package id="Ical.Net" version="2.2.32" targetFramework="net40" />
|
||||
<package id="Nancy" version="1.4.3" 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="Newtonsoft.Json" version="6.0.6" targetFramework="net40" />
|
||||
<package id="NLog" version="4.3.11" targetFramework="net40" />
|
||||
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net40" />
|
||||
<package id="NLog" version="4.4.3" targetFramework="net40" />
|
||||
</packages>
|
||||
@@ -41,21 +41,17 @@
|
||||
<HintPath>..\packages\NBuilder.4.0.0\lib\net40\FizzWare.NBuilder.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="FluentAssertions, Version=4.18.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<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>
|
||||
<Reference Include="FluentAssertions.Core, Version=4.18.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.Core.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<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="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NLog.4.3.11\lib\net40\NLog.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<HintPath>..\packages\NLog.4.4.3\lib\net40\NLog.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="nunit.framework, Version=3.5.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NUnit.3.5.0\lib\net40\nunit.framework.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<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>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
@@ -112,8 +108,8 @@
|
||||
xcopy /s /y "$(SolutionDir)\..\_output\NzbDrone.Windows.*" "$(TargetDir)"
|
||||
</PostBuildEvent>
|
||||
<PostBuildEvent Condition="('$(OS)' != 'Windows_NT')">
|
||||
cp -rv $(SolutionDir)\..\_output\NzbDrone.Mono.* $(TargetDir)
|
||||
cp -rv $(SolutionDir)\..\_output\NzbDrone.Windows.* $(TargetDir)
|
||||
cp -rv $(SolutionDir)\..\_output\NzbDrone.Mono.* $(TargetDir) || true
|
||||
cp -rv $(SolutionDir)\..\_output\NzbDrone.Windows.* $(TargetDir) || true
|
||||
</PostBuildEvent>
|
||||
</PropertyGroup>
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="FluentAssertions" version="4.18.0" targetFramework="net40" />
|
||||
<package id="FluentAssertions" version="4.19.0" targetFramework="net40" />
|
||||
<package id="Moq" version="4.0.10827" />
|
||||
<package id="NBuilder" version="4.0.0" targetFramework="net40" />
|
||||
<package id="NLog" version="4.3.11" targetFramework="net40" />
|
||||
<package id="NUnit" version="3.5.0" targetFramework="net40" />
|
||||
<package id="NLog" version="4.4.3" targetFramework="net40" />
|
||||
<package id="NUnit" version="3.6.0" targetFramework="net40" />
|
||||
</packages>
|
||||
@@ -38,21 +38,17 @@
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="FluentAssertions, Version=4.18.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<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>
|
||||
<Reference Include="FluentAssertions.Core, Version=4.18.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.Core.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<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="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NLog.4.3.11\lib\net40\NLog.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<HintPath>..\packages\NLog.4.4.3\lib\net40\NLog.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="nunit.framework, Version=3.5.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NUnit.3.5.0\lib\net40\nunit.framework.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<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>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
@@ -62,13 +58,11 @@
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="WebDriver, Version=3.0.1.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Selenium.WebDriver.3.0.1\lib\net40\WebDriver.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Reference Include="WebDriver, Version=3.2.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Selenium.WebDriver.3.2.0\lib\net40\WebDriver.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="WebDriver.Support, Version=3.0.1.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Selenium.Support.3.0.1\lib\net40\WebDriver.Support.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Reference Include="WebDriver.Support, Version=3.2.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Selenium.Support.3.2.0\lib\net40\WebDriver.Support.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="FluentMigrator" publicKeyToken="aacfc7de5acabf05" culture="neutral" />
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="FluentAssertions" version="4.18.0" targetFramework="net40" />
|
||||
<package id="NLog" version="4.3.11" targetFramework="net40" />
|
||||
<package id="NUnit" version="3.5.0" targetFramework="net40" />
|
||||
<package id="Selenium.Support" version="3.0.1" targetFramework="net40" />
|
||||
<package id="Selenium.WebDriver" version="3.0.1" targetFramework="net40" />
|
||||
<package id="FluentAssertions" version="4.19.0" targetFramework="net40" />
|
||||
<package id="NLog" version="4.4.3" targetFramework="net40" />
|
||||
<package id="NUnit" version="3.6.0" targetFramework="net40" />
|
||||
<package id="Selenium.Support" version="3.2.0" targetFramework="net40" />
|
||||
<package id="Selenium.WebDriver" version="3.2.0" targetFramework="net40" />
|
||||
</packages>
|
||||
@@ -16,6 +16,7 @@ namespace NzbDrone.Common.Test.DiskTests
|
||||
private readonly string _targetPath = @"C:\target\my.video.mkv".AsOsAgnostic();
|
||||
private readonly string _backupPath = @"C:\source\my.video.mkv.backup~".AsOsAgnostic();
|
||||
private readonly string _tempTargetPath = @"C:\target\my.video.mkv.partial~".AsOsAgnostic();
|
||||
private readonly string _nfsFile = ".nfs01231232";
|
||||
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
@@ -642,6 +643,21 @@ namespace NzbDrone.Common.Test.DiskTests
|
||||
VerifyCopyFolder(source.FullName, destination.FullName);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CopyFolder_should_ignore_nfs_temp_file()
|
||||
{
|
||||
WithRealDiskProvider();
|
||||
|
||||
var source = GetFilledTempFolder();
|
||||
|
||||
File.WriteAllText(Path.Combine(source.FullName, _nfsFile), "SubFile1");
|
||||
|
||||
var destination = new DirectoryInfo(GetTempFilePath());
|
||||
|
||||
Subject.TransferFolder(source.FullName, destination.FullName, TransferMode.Copy);
|
||||
|
||||
File.Exists(Path.Combine(destination.FullName, _nfsFile)).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MoveFolder_should_move_folder()
|
||||
@@ -704,6 +720,26 @@ namespace NzbDrone.Common.Test.DiskTests
|
||||
destination.GetFileSystemInfos().Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MirrorFolder_should_not_remove_nfs_files()
|
||||
{
|
||||
WithRealDiskProvider();
|
||||
|
||||
var original = GetFilledTempFolder();
|
||||
var source = new DirectoryInfo(GetTempFilePath());
|
||||
var destination = new DirectoryInfo(GetTempFilePath());
|
||||
|
||||
source.Create();
|
||||
Subject.TransferFolder(original.FullName, destination.FullName, TransferMode.Copy);
|
||||
|
||||
File.WriteAllText(Path.Combine(destination.FullName, _nfsFile), "SubFile1");
|
||||
|
||||
var count = Subject.MirrorFolder(source.FullName, destination.FullName);
|
||||
|
||||
count.Should().Equals(0);
|
||||
destination.GetFileSystemInfos().Should().HaveCount(1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MirrorFolder_should_add_new_files()
|
||||
{
|
||||
@@ -721,6 +757,24 @@ namespace NzbDrone.Common.Test.DiskTests
|
||||
VerifyCopyFolder(original.FullName, destination.FullName);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MirrorFolder_should_ignore_nfs_temp_file()
|
||||
{
|
||||
WithRealDiskProvider();
|
||||
|
||||
var source = GetFilledTempFolder();
|
||||
|
||||
File.WriteAllText(Path.Combine(source.FullName, _nfsFile), "SubFile1");
|
||||
|
||||
var destination = new DirectoryInfo(GetTempFilePath());
|
||||
|
||||
var count = Subject.MirrorFolder(source.FullName, destination.FullName);
|
||||
|
||||
count.Should().Equals(3);
|
||||
|
||||
File.Exists(Path.Combine(destination.FullName, _nfsFile)).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MirrorFolder_should_not_touch_equivalent_files()
|
||||
{
|
||||
|
||||
23
src/NzbDrone.Common.Test/EnvironmentInfo/BuildInfoFixture.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
|
||||
namespace NzbDrone.Common.Test.EnvironmentInfo
|
||||
{
|
||||
[TestFixture]
|
||||
public class BuildInfoFixture
|
||||
{
|
||||
[Test]
|
||||
public void should_return_version()
|
||||
{
|
||||
BuildInfo.Version.Major.Should().BeOneOf(2, 10);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_get_branch()
|
||||
{
|
||||
BuildInfo.Branch.Should().NotBe("unknow");
|
||||
BuildInfo.Branch.Should().NotBeNullOrWhiteSpace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ namespace NzbDrone.Common.Test
|
||||
[Test]
|
||||
public void IsProduction_should_return_false_when_run_within_nunit()
|
||||
{
|
||||
RuntimeInfoBase.IsProduction.Should().BeFalse("Process name is " + Process.GetCurrentProcess().ProcessName + " Folder is " + Directory.GetCurrentDirectory());
|
||||
RuntimeInfo.IsProduction.Should().BeFalse("Process name is " + Process.GetCurrentProcess().ProcessName + " Folder is " + Directory.GetCurrentDirectory());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
21
src/NzbDrone.Common.Test/HashUtilFixture.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace NzbDrone.Common.Test
|
||||
{
|
||||
[TestFixture]
|
||||
public class HashUtilFixture
|
||||
{
|
||||
[Test]
|
||||
public void should_create_anon_id()
|
||||
{
|
||||
HashUtil.AnonymousToken().Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_create_the_same_id()
|
||||
{
|
||||
HashUtil.AnonymousToken().Should().Be(HashUtil.AnonymousToken());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ using Moq;
|
||||
using NLog;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.Cache;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Common.Http.Dispatchers;
|
||||
using NzbDrone.Common.Http.Proxy;
|
||||
@@ -30,6 +31,12 @@ namespace NzbDrone.Common.Test.Http
|
||||
[SetUp]
|
||||
public void SetUp()
|
||||
{
|
||||
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.Version).Returns("9.0.0");
|
||||
|
||||
Mocker.SetConstant<IUserAgentBuilder>(Mocker.Resolve<UserAgentBuilder>());
|
||||
|
||||
Mocker.SetConstant<ICacheManager>(Mocker.Resolve<CacheManager>());
|
||||
Mocker.SetConstant<ICreateManagedWebProxy>(Mocker.Resolve<ManagedWebProxyFactory>());
|
||||
Mocker.SetConstant<IRateLimitService>(Mocker.Resolve<RateLimitService>());
|
||||
@@ -48,7 +55,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
[Test]
|
||||
public void should_execute_simple_get()
|
||||
{
|
||||
var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost));
|
||||
var request = new HttpRequest($"http://{_httpBinHost}/get");
|
||||
|
||||
var response = Subject.Execute(request);
|
||||
|
||||
@@ -58,7 +65,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
[Test]
|
||||
public void should_execute_https_get()
|
||||
{
|
||||
var request = new HttpRequest(string.Format("https://{0}/get", _httpBinHost));
|
||||
var request = new HttpRequest($"https://{_httpBinHost}/get");
|
||||
|
||||
var response = Subject.Execute(request);
|
||||
|
||||
@@ -68,7 +75,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
[Test]
|
||||
public void should_execute_typed_get()
|
||||
{
|
||||
var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost));
|
||||
var request = new HttpRequest($"http://{_httpBinHost}/get");
|
||||
|
||||
var response = Subject.Get<HttpBinResource>(request);
|
||||
|
||||
@@ -80,7 +87,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
{
|
||||
var message = "{ my: 1 }";
|
||||
|
||||
var request = new HttpRequest(string.Format("http://{0}/post", _httpBinHost));
|
||||
var request = new HttpRequest($"http://{_httpBinHost}/post");
|
||||
request.SetContent(message);
|
||||
|
||||
var response = Subject.Post<HttpBinResource>(request);
|
||||
@@ -91,7 +98,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
[TestCase("gzip")]
|
||||
public void should_execute_get_using_gzip(string compression)
|
||||
{
|
||||
var request = new HttpRequest(string.Format("http://{0}/{1}", _httpBinHost, compression));
|
||||
var request = new HttpRequest($"http://{_httpBinHost}/{compression}");
|
||||
|
||||
var response = Subject.Get<HttpBinResource>(request);
|
||||
|
||||
@@ -107,7 +114,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
[TestCase(HttpStatusCode.BadGateway)]
|
||||
public void should_throw_on_unsuccessful_status_codes(int statusCode)
|
||||
{
|
||||
var request = new HttpRequest(string.Format("http://{0}/status/{1}", _httpBinHost, statusCode));
|
||||
var request = new HttpRequest($"http://{_httpBinHost}/status/{statusCode}");
|
||||
|
||||
var exception = Assert.Throws<HttpException>(() => Subject.Get<HttpBinResource>(request));
|
||||
|
||||
@@ -119,7 +126,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
[Test]
|
||||
public void should_not_follow_redirects_when_not_in_production()
|
||||
{
|
||||
var request = new HttpRequest(string.Format("http://{0}/redirect/1", _httpBinHost));
|
||||
var request = new HttpRequest($"http://{_httpBinHost}/redirect/1");
|
||||
|
||||
Subject.Get(request);
|
||||
|
||||
@@ -129,7 +136,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
[Test]
|
||||
public void should_follow_redirects()
|
||||
{
|
||||
var request = new HttpRequest(string.Format("http://{0}/redirect/1", _httpBinHost));
|
||||
var request = new HttpRequest($"http://{_httpBinHost}/redirect/1");
|
||||
request.AllowAutoRedirect = true;
|
||||
|
||||
Subject.Get(request);
|
||||
@@ -140,7 +147,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
[Test]
|
||||
public void should_send_user_agent()
|
||||
{
|
||||
var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost));
|
||||
var request = new HttpRequest($"http://{_httpBinHost}/get");
|
||||
|
||||
var response = Subject.Get<HttpBinResource>(request);
|
||||
|
||||
@@ -154,7 +161,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
[TestCase("Accept", "text/xml, text/rss+xml, application/rss+xml")]
|
||||
public void should_send_headers(string header, string value)
|
||||
{
|
||||
var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost));
|
||||
var request = new HttpRequest($"http://{_httpBinHost}/get");
|
||||
request.Headers.Add(header, value);
|
||||
|
||||
var response = Subject.Get<HttpBinResource>(request);
|
||||
@@ -177,7 +184,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
[Test]
|
||||
public void should_send_cookie()
|
||||
{
|
||||
var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost));
|
||||
var request = new HttpRequest($"http://{_httpBinHost}/get");
|
||||
request.Cookies["my"] = "cookie";
|
||||
|
||||
var response = Subject.Get<HttpBinResource>(request);
|
||||
@@ -194,7 +201,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
var oldRequest = new HttpRequest("http://eu.httpbin.org/get");
|
||||
oldRequest.Cookies["my"] = "cookie";
|
||||
|
||||
var oldClient = new HttpClient(new IHttpRequestInterceptor[0], Mocker.Resolve<ICacheManager>(), Mocker.Resolve<IRateLimitService>(), Mocker.Resolve<IHttpDispatcher>(), 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>());
|
||||
|
||||
oldClient.Should().NotBeSameAs(Subject);
|
||||
|
||||
@@ -234,12 +241,12 @@ namespace NzbDrone.Common.Test.Http
|
||||
[Test]
|
||||
public void should_not_store_response_cookie()
|
||||
{
|
||||
var requestSet = new HttpRequest(string.Format("http://{0}/cookies/set?my=cookie", _httpBinHost));
|
||||
var requestSet = new HttpRequest($"http://{_httpBinHost}/cookies/set?my=cookie");
|
||||
requestSet.AllowAutoRedirect = false;
|
||||
|
||||
var responseSet = Subject.Get(requestSet);
|
||||
|
||||
var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost));
|
||||
var request = new HttpRequest($"http://{_httpBinHost}/get");
|
||||
|
||||
var response = Subject.Get<HttpBinResource>(request);
|
||||
|
||||
@@ -251,13 +258,13 @@ namespace NzbDrone.Common.Test.Http
|
||||
[Test]
|
||||
public void should_store_response_cookie()
|
||||
{
|
||||
var requestSet = new HttpRequest(string.Format("http://{0}/cookies/set?my=cookie", _httpBinHost));
|
||||
var requestSet = new HttpRequest($"http://{_httpBinHost}/cookies/set?my=cookie");
|
||||
requestSet.AllowAutoRedirect = false;
|
||||
requestSet.StoreResponseCookie = true;
|
||||
|
||||
var responseSet = Subject.Get(requestSet);
|
||||
|
||||
var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost));
|
||||
var request = new HttpRequest($"http://{_httpBinHost}/get");
|
||||
|
||||
var response = Subject.Get<HttpBinResource>(request);
|
||||
|
||||
@@ -273,14 +280,14 @@ namespace NzbDrone.Common.Test.Http
|
||||
[Test]
|
||||
public void should_overwrite_response_cookie()
|
||||
{
|
||||
var requestSet = new HttpRequest(string.Format("http://{0}/cookies/set?my=cookie", _httpBinHost));
|
||||
var requestSet = new HttpRequest($"http://{_httpBinHost}/cookies/set?my=cookie");
|
||||
requestSet.AllowAutoRedirect = false;
|
||||
requestSet.StoreResponseCookie = true;
|
||||
requestSet.Cookies["my"] = "oldcookie";
|
||||
|
||||
var responseSet = Subject.Get(requestSet);
|
||||
|
||||
var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost));
|
||||
var request = new HttpRequest($"http://{_httpBinHost}/get");
|
||||
|
||||
var response = Subject.Get<HttpBinResource>(request);
|
||||
|
||||
@@ -296,7 +303,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
[Test]
|
||||
public void should_throw_on_http429_too_many_requests()
|
||||
{
|
||||
var request = new HttpRequest(string.Format("http://{0}/status/429", _httpBinHost));
|
||||
var request = new HttpRequest($"http://{_httpBinHost}/status/429");
|
||||
|
||||
Assert.Throws<TooManyRequestsException>(() => Subject.Get(request));
|
||||
|
||||
@@ -316,7 +323,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
.Setup(v => v.PostResponse(It.IsAny<HttpResponse>()))
|
||||
.Returns<HttpResponse>(r => r);
|
||||
|
||||
var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost));
|
||||
var request = new HttpRequest($"http://{_httpBinHost}/get");
|
||||
|
||||
Subject.Get(request);
|
||||
|
||||
@@ -338,7 +345,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
{
|
||||
// the date is bad in the below - should be 13-Jul-2026
|
||||
string malformedCookie = @"__cfduid=d29e686a9d65800021c66faca0a29b4261436890790; expires=Mon, 13-Jul-26 16:19:50 GMT; path=/; HttpOnly";
|
||||
var requestSet = new HttpRequestBuilder(string.Format("http://{0}/response-headers", _httpBinHost))
|
||||
var requestSet = new HttpRequestBuilder($"http://{_httpBinHost}/response-headers")
|
||||
.AddQueryParam("Set-Cookie", malformedCookie)
|
||||
.Build();
|
||||
|
||||
@@ -347,7 +354,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
|
||||
var responseSet = Subject.Get(requestSet);
|
||||
|
||||
var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost));
|
||||
var request = new HttpRequest($"http://{_httpBinHost}/get");
|
||||
|
||||
var response = Subject.Get<HttpBinResource>(request);
|
||||
|
||||
@@ -371,7 +378,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
{
|
||||
try
|
||||
{
|
||||
string url = string.Format("http://{0}/response-headers?Set-Cookie={1}", _httpBinHost, Uri.EscapeUriString(malformedCookie));
|
||||
string url = $"http://{_httpBinHost}/response-headers?Set-Cookie={Uri.EscapeUriString(malformedCookie)}";
|
||||
|
||||
var requestSet = new HttpRequest(url);
|
||||
requestSet.AllowAutoRedirect = false;
|
||||
@@ -379,7 +386,7 @@ namespace NzbDrone.Common.Test.Http
|
||||
|
||||
var responseSet = Subject.Get(requestSet);
|
||||
|
||||
var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost));
|
||||
var request = new HttpRequest($"http://{_httpBinHost}/get");
|
||||
|
||||
var response = Subject.Get<HttpBinResource>(request);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using System;
|
||||
using System.Text;
|
||||
using NzbDrone.Common.Http;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
|
||||
namespace NzbDrone.Common.Test.Http
|
||||
{
|
||||
@@ -36,5 +37,17 @@ namespace NzbDrone.Common.Test.Http
|
||||
Action action = () => httpheader.GetEncodingFromContentType();
|
||||
action.ShouldThrow<ArgumentException>();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_parse_cookie_with_trailing_semi_colon()
|
||||
{
|
||||
var cookies = HttpHeader.ParseCookies("uid=123456; pass=123456b2f3abcde42ac3a123f3f1fc9f;");
|
||||
|
||||
cookies.Count.Should().Be(2);
|
||||
cookies.First().Key.Should().Be("uid");
|
||||
cookies.First().Value.Should().Be("123456");
|
||||
cookies.Last().Key.Should().Be("pass");
|
||||
cookies.Last().Value.Should().Be("123456b2f3abcde42ac3a123f3f1fc9f");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
30
src/NzbDrone.Common.Test/Http/UserAgentBuilderFixture.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using FluentAssertions;
|
||||
using NUnit.Framework;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Http;
|
||||
using NzbDrone.Test.Common;
|
||||
|
||||
namespace NzbDrone.Common.Test.Http
|
||||
{
|
||||
[TestFixture]
|
||||
public class UserAgentBuilderFixture : TestBase<UserAgentBuilder>
|
||||
{
|
||||
[Test]
|
||||
public void should_get_user_agent_if_os_version_is_null()
|
||||
{
|
||||
Mocker.GetMock<IOsInfo>().SetupGet(c => c.Version).Returns((string)null);
|
||||
Mocker.GetMock<IOsInfo>().SetupGet(c => c.Name).Returns("TestOS");
|
||||
|
||||
Subject.GetUserAgent(false).Should().NotBeNullOrWhiteSpace();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void should_get_use_os_family_if_name_is_null()
|
||||
{
|
||||
Mocker.GetMock<IOsInfo>().SetupGet(c => c.Version).Returns((string)null);
|
||||
Mocker.GetMock<IOsInfo>().SetupGet(c => c.Name).Returns((string)null);
|
||||
|
||||
Subject.GetUserAgent(false).Should().NotBeNullOrWhiteSpace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,21 +37,17 @@
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="FluentAssertions, Version=4.18.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<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>
|
||||
<Reference Include="FluentAssertions.Core, Version=4.18.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.Core.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<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="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NLog.4.3.11\lib\net40\NLog.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<HintPath>..\packages\NLog.4.4.3\lib\net40\NLog.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="nunit.framework, Version=3.5.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\NUnit.3.5.0\lib\net40\nunit.framework.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<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>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
@@ -76,6 +72,7 @@
|
||||
<Compile Include="DiskTests\IsParentFixtureBase.cs" />
|
||||
<Compile Include="DiskTests\DiskTransferServiceFixture.cs" />
|
||||
<Compile Include="EnsureTest\PathExtensionFixture.cs" />
|
||||
<Compile Include="EnvironmentInfo\BuildInfoFixture.cs" />
|
||||
<Compile Include="EnvironmentProviderTest.cs" />
|
||||
<Compile Include="EnvironmentTests\EnvironmentProviderTest.cs" />
|
||||
<Compile Include="EnvironmentTests\StartupArgumentsFixture.cs" />
|
||||
@@ -83,11 +80,13 @@
|
||||
<Compile Include="ExtensionTests\IEnumerableExtensionTests\ExceptByFixture.cs" />
|
||||
<Compile Include="ExtensionTests\IEnumerableExtensionTests\IntersectByFixture.cs" />
|
||||
<Compile Include="ExtensionTests\Int64ExtensionFixture.cs" />
|
||||
<Compile Include="HashUtilFixture.cs" />
|
||||
<Compile Include="Http\HttpClientFixture.cs" />
|
||||
<Compile Include="Http\HttpHeaderFixture.cs" />
|
||||
<Compile Include="Http\HttpRequestBuilderFixture.cs" />
|
||||
<Compile Include="Http\HttpRequestFixture.cs" />
|
||||
<Compile Include="Http\HttpUriFixture.cs" />
|
||||
<Compile Include="Http\UserAgentBuilderFixture.cs" />
|
||||
<Compile Include="InstrumentationTests\CleanseLogMessageFixture.cs" />
|
||||
<Compile Include="LevenshteinDistanceFixture.cs" />
|
||||
<Compile Include="OsPathFixture.cs" />
|
||||
|
||||
@@ -86,7 +86,7 @@ namespace NzbDrone.Common.Test
|
||||
{
|
||||
first.AsOsAgnostic().PathEquals(second.AsOsAgnostic()).Should().BeFalse();
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void should_return_false_when_not_a_child()
|
||||
{
|
||||
@@ -113,6 +113,7 @@ namespace NzbDrone.Common.Test
|
||||
[TestCase(@"C:\Test\", @"C:\Test\mydir")]
|
||||
[TestCase(@"C:\Test\", @"C:\Test\mydir\")]
|
||||
[TestCase(@"C:\Test", @"C:\Test\30.Rock.S01E01.Pilot.avi")]
|
||||
[TestCase(@"C:\", @"C:\Test\30.Rock.S01E01.Pilot.avi")]
|
||||
public void path_should_be_parent(string parentPath, string childPath)
|
||||
{
|
||||
parentPath.AsOsAgnostic().IsParentPath(childPath.AsOsAgnostic()).Should().BeTrue();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="FluentAssertions" version="4.18.0" targetFramework="net40" />
|
||||
<package id="FluentAssertions" version="4.19.0" targetFramework="net40" />
|
||||
<package id="Moq" version="4.0.10827" />
|
||||
<package id="NLog" version="4.3.11" targetFramework="net40" />
|
||||
<package id="NUnit" version="3.5.0" targetFramework="net40" />
|
||||
<package id="NLog" version="4.4.3" targetFramework="net40" />
|
||||
<package id="NUnit" version="3.6.0" targetFramework="net40" />
|
||||
</packages>
|
||||
@@ -20,8 +20,8 @@ namespace NzbDrone.Common.Cloud
|
||||
.CreateFactory();
|
||||
}
|
||||
|
||||
public IHttpRequestBuilderFactory Services { get; private set; }
|
||||
public IHttpRequestBuilderFactory Services { get; }
|
||||
|
||||
public IHttpRequestBuilderFactory SkyHookTvdb { get; private set; }
|
||||
public IHttpRequestBuilderFactory SkyHookTvdb { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,19 +6,21 @@ using NzbDrone.Common.EnvironmentInfo;
|
||||
using NzbDrone.Common.Messaging;
|
||||
using TinyIoC;
|
||||
|
||||
|
||||
namespace NzbDrone.Common.Composition
|
||||
{
|
||||
public abstract class ContainerBuilderBase
|
||||
{
|
||||
private readonly List<Type> _loadedTypes;
|
||||
|
||||
public IContainer Container { get; private set; }
|
||||
protected IContainer Container { get; }
|
||||
|
||||
protected ContainerBuilderBase(IStartupContext args, params string[] assemblies)
|
||||
protected ContainerBuilderBase(IStartupContext args, List<string> assemblies)
|
||||
{
|
||||
_loadedTypes = new List<Type>();
|
||||
|
||||
assemblies.Add(OsInfo.IsWindows ? "NzbDrone.Windows" : "NzbDrone.Mono");
|
||||
assemblies.Add("NzbDrone.Common");
|
||||
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
_loadedTypes.AddRange(Assembly.Load(assembly).GetTypes());
|
||||
|
||||
@@ -16,6 +16,19 @@ namespace NzbDrone.Common.Disk
|
||||
{
|
||||
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(DiskProviderBase));
|
||||
|
||||
public static StringComparison PathStringComparison
|
||||
{
|
||||
get
|
||||
{
|
||||
if (OsInfo.IsWindows)
|
||||
{
|
||||
return StringComparison.OrdinalIgnoreCase;
|
||||
}
|
||||
|
||||
return StringComparison.Ordinal;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract long? GetAvailableSpace(string path);
|
||||
public abstract void InheritFolderPermissions(string filename);
|
||||
public abstract void SetPermissions(string path, string mask, string user, string group);
|
||||
@@ -86,7 +99,7 @@ namespace NzbDrone.Common.Disk
|
||||
public bool FileExists(string path)
|
||||
{
|
||||
Ensure.That(path, () => path).IsValidPath();
|
||||
return FileExists(path, OsInfo.PathStringComparison);
|
||||
return FileExists(path, PathStringComparison);
|
||||
}
|
||||
|
||||
public bool FileExists(string path, StringComparison stringComparison)
|
||||
@@ -95,16 +108,16 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
switch (stringComparison)
|
||||
{
|
||||
case StringComparison.CurrentCulture:
|
||||
case StringComparison.InvariantCulture:
|
||||
case StringComparison.Ordinal:
|
||||
{
|
||||
return File.Exists(path) && path == path.GetActualCasing();
|
||||
}
|
||||
case StringComparison.CurrentCulture:
|
||||
case StringComparison.InvariantCulture:
|
||||
case StringComparison.Ordinal:
|
||||
{
|
||||
return File.Exists(path) && path == path.GetActualCasing();
|
||||
}
|
||||
default:
|
||||
{
|
||||
return File.Exists(path);
|
||||
}
|
||||
{
|
||||
return File.Exists(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,7 +128,7 @@ namespace NzbDrone.Common.Disk
|
||||
try
|
||||
{
|
||||
var testPath = Path.Combine(path, "sonarr_write_test.txt");
|
||||
var testContent = string.Format("This file was created to verify if '{0}' is writable. It should've been automatically deleted. Feel free to delete it.", path);
|
||||
var testContent = $"This file was created to verify if '{path}' is writable. It should've been automatically deleted. Feel free to delete it.";
|
||||
File.WriteAllText(testPath, testContent);
|
||||
File.Delete(testPath);
|
||||
return true;
|
||||
|
||||
@@ -64,11 +64,15 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
foreach (var subDir in _diskProvider.GetDirectoryInfos(sourcePath))
|
||||
{
|
||||
if (ShouldIgnore(subDir)) continue;
|
||||
|
||||
result &= TransferFolder(subDir.FullName, Path.Combine(targetPath, subDir.Name), mode, verificationMode);
|
||||
}
|
||||
|
||||
foreach (var sourceFile in _diskProvider.GetFileInfos(sourcePath))
|
||||
{
|
||||
if (ShouldIgnore(sourceFile)) continue;
|
||||
|
||||
var destFile = Path.Combine(targetPath, sourceFile.Name);
|
||||
|
||||
result &= TransferFile(sourceFile.FullName, destFile, mode, true, verificationMode);
|
||||
@@ -101,11 +105,15 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
foreach (var subDir in targetFolders.Where(v => !sourceFolders.Any(d => d.Name == v.Name)))
|
||||
{
|
||||
if (ShouldIgnore(subDir)) continue;
|
||||
|
||||
_diskProvider.DeleteFolder(subDir.FullName, true);
|
||||
}
|
||||
|
||||
foreach (var subDir in sourceFolders)
|
||||
{
|
||||
if (ShouldIgnore(subDir)) continue;
|
||||
|
||||
filesCopied += MirrorFolder(subDir.FullName, Path.Combine(targetPath, subDir.Name));
|
||||
}
|
||||
|
||||
@@ -114,11 +122,15 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
foreach (var targetFile in targetFiles.Where(v => !sourceFiles.Any(d => d.Name == v.Name)))
|
||||
{
|
||||
if (ShouldIgnore(targetFile)) continue;
|
||||
|
||||
_diskProvider.DeleteFile(targetFile.FullName);
|
||||
}
|
||||
|
||||
foreach (var sourceFile in sourceFiles)
|
||||
{
|
||||
if (ShouldIgnore(sourceFile)) continue;
|
||||
|
||||
var targetFile = Path.Combine(targetPath, sourceFile.Name);
|
||||
|
||||
if (CompareFiles(sourceFile.FullName, targetFile))
|
||||
@@ -352,7 +364,7 @@ namespace NzbDrone.Common.Disk
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, string.Format("Failed to properly rollback the file move [{0}] to [{1}], incomplete file may be left in target path.", sourcePath, targetPath));
|
||||
_logger.Error(ex, "Failed to properly rollback the file move [{0}] to [{1}], incomplete file may be left in target path.", sourcePath, targetPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -368,7 +380,7 @@ namespace NzbDrone.Common.Disk
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, string.Format("Failed to properly rollback the file move [{0}] to [{1}], file may be left in target path.", sourcePath, targetPath));
|
||||
_logger.Error(ex, "Failed to properly rollback the file move [{0}] to [{1}], file may be left in target path.", sourcePath, targetPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,7 +399,7 @@ namespace NzbDrone.Common.Disk
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error(ex, string.Format("Failed to properly rollback the file copy [{0}] to [{1}], file may be left in target path.", sourcePath, targetPath));
|
||||
_logger.Error(ex, "Failed to properly rollback the file copy [{0}] to [{1}], file may be left in target path.", sourcePath, targetPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -429,7 +441,7 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
if (i == RetryCount)
|
||||
{
|
||||
_logger.Error("Failed to completely transfer [{0}] to [{1}], aborting.", sourcePath, targetPath, i + 1, RetryCount);
|
||||
_logger.Error("Failed to completely transfer [{0}] to [{1}], aborting.", sourcePath, targetPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -564,5 +576,27 @@ namespace NzbDrone.Common.Disk
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldIgnore(DirectoryInfo folder)
|
||||
{
|
||||
if (folder.Name.StartsWith(".nfs"))
|
||||
{
|
||||
_logger.Trace("Ignoring folder {0}", folder.FullName);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool ShouldIgnore(FileInfo file)
|
||||
{
|
||||
if (file.Name.StartsWith(".nfs") || file.Name == "debug.log" || file.Name.EndsWith(".socket"))
|
||||
{
|
||||
_logger.Trace("Ignoring file {0}", file.FullName);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
namespace NzbDrone.Common.Disk
|
||||
@@ -8,10 +9,11 @@ namespace NzbDrone.Common.Disk
|
||||
private readonly DriveInfo _driveInfo;
|
||||
private readonly DriveType _driveType;
|
||||
|
||||
public DriveInfoMount(DriveInfo driveInfo, DriveType driveType = DriveType.Unknown)
|
||||
public DriveInfoMount(DriveInfo driveInfo, DriveType driveType = DriveType.Unknown, MountOptions mountOptions = null)
|
||||
{
|
||||
_driveInfo = driveInfo;
|
||||
_driveType = driveType;
|
||||
MountOptions = mountOptions;
|
||||
}
|
||||
|
||||
public long AvailableFreeSpace => _driveInfo.AvailableFreeSpace;
|
||||
@@ -33,6 +35,8 @@ namespace NzbDrone.Common.Disk
|
||||
|
||||
public bool IsReady => _driveInfo.IsReady;
|
||||
|
||||
public MountOptions MountOptions { get; private set; }
|
||||
|
||||
public string Name => _driveInfo.Name;
|
||||
|
||||
public string RootDirectory => _driveInfo.RootDirectory.FullName;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace NzbDrone.Common.Disk
|
||||
@@ -8,6 +9,7 @@ namespace NzbDrone.Common.Disk
|
||||
string DriveFormat { get; }
|
||||
DriveType DriveType { get; }
|
||||
bool IsReady { get; }
|
||||
MountOptions MountOptions { get; }
|
||||
string Name { get; }
|
||||
string RootDirectory { get; }
|
||||
long TotalFreeSpace { get; }
|
||||
|
||||
16
src/NzbDrone.Common/Disk/MountOptions.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NzbDrone.Common.Disk
|
||||
{
|
||||
public class MountOptions
|
||||
{
|
||||
private readonly Dictionary<string, string> _options;
|
||||
|
||||
public MountOptions(Dictionary<string, string> options)
|
||||
{
|
||||
_options = options;
|
||||
}
|
||||
|
||||
public bool IsReadOnly => _options.ContainsKey("ro");
|
||||
}
|
||||
}
|
||||
@@ -101,12 +101,12 @@ namespace NzbDrone.Common.EnsureThat
|
||||
|
||||
if (param.Value.IsPathValid()) return param;
|
||||
|
||||
if (OsInfo.IsNotWindows)
|
||||
if (OsInfo.IsWindows)
|
||||
{
|
||||
throw ExceptionFactory.CreateForParamValidation(param.Name, string.Format("value [{0}] is not a valid *nix path. paths must start with /", param.Value));
|
||||
throw ExceptionFactory.CreateForParamValidation(param.Name, string.Format("value [{0}] is not a valid Windows path. paths must be a full path eg. C:\\Windows", param.Value));
|
||||
}
|
||||
|
||||
throw ExceptionFactory.CreateForParamValidation(param.Name, string.Format("value [{0}] is not a valid Windows path. paths must be a full path eg. C:\\Windows", param.Value));
|
||||
|
||||
throw ExceptionFactory.CreateForParamValidation(param.Name, string.Format("value [{0}] is not a valid *nix path. paths must start with /", param.Value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,10 +41,10 @@ namespace NzbDrone.Common.EnvironmentInfo
|
||||
TempFolder = Path.GetTempPath();
|
||||
}
|
||||
|
||||
public string AppDataFolder { get; private set; }
|
||||
public string AppDataFolder { get; }
|
||||
|
||||
public string StartUpFolder { get; private set; }
|
||||
public string StartUpFolder { get; }
|
||||
|
||||
public string TempFolder { get; private set; }
|
||||
public string TempFolder { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,35 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace NzbDrone.Common.EnvironmentInfo
|
||||
{
|
||||
public static class BuildInfo
|
||||
{
|
||||
public static Version Version => Assembly.GetExecutingAssembly().GetName().Version;
|
||||
static BuildInfo()
|
||||
{
|
||||
var assembly = Assembly.GetExecutingAssembly();
|
||||
|
||||
Version = assembly.GetName().Version;
|
||||
|
||||
var attributes = assembly.GetCustomAttributes(true);
|
||||
|
||||
Branch = "unknow";
|
||||
|
||||
var config = attributes.OfType<AssemblyConfigurationAttribute>().FirstOrDefault();
|
||||
if (config != null)
|
||||
{
|
||||
Branch = config.Configuration;
|
||||
}
|
||||
|
||||
Release = $"{Version}-{Branch}";
|
||||
}
|
||||
|
||||
public static Version Version { get; }
|
||||
public static String Branch { get; }
|
||||
public static string Release { get; }
|
||||
|
||||
public static DateTime BuildDateTime
|
||||
{
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace NzbDrone.Common.EnvironmentInfo
|
||||
{
|
||||
public interface IOperatingSystemVersionInfo
|
||||
{
|
||||
string Version { get; }
|
||||
string Name { get; }
|
||||
string FullName { get; }
|
||||
}
|
||||
}
|
||||
9
src/NzbDrone.Common/EnvironmentInfo/IOsVersionAdapter.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace NzbDrone.Common.EnvironmentInfo
|
||||
{
|
||||
|
||||
public interface IOsVersionAdapter
|
||||
{
|
||||
bool Enabled { get; }
|
||||
OsVersionModel Read();
|
||||
}
|
||||
}
|
||||
50
src/NzbDrone.Common/EnvironmentInfo/IPlatformInfo.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
|
||||
namespace NzbDrone.Common.EnvironmentInfo
|
||||
{
|
||||
|
||||
public enum PlatformType
|
||||
{
|
||||
DotNet = 0,
|
||||
Mono = 1
|
||||
}
|
||||
|
||||
public interface IPlatformInfo
|
||||
{
|
||||
Version Version { get; }
|
||||
}
|
||||
|
||||
public abstract class PlatformInfo : IPlatformInfo
|
||||
{
|
||||
static PlatformInfo()
|
||||
{
|
||||
if (Type.GetType("Mono.Runtime") != null)
|
||||
{
|
||||
Platform = PlatformType.Mono;
|
||||
}
|
||||
else
|
||||
{
|
||||
Platform = PlatformType.DotNet;
|
||||
}
|
||||
}
|
||||
|
||||
public static PlatformType Platform { get; }
|
||||
public static bool IsMono => Platform == PlatformType.Mono;
|
||||
public static bool IsDotNet => Platform == PlatformType.DotNet;
|
||||
|
||||
public static string PlatformName
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsDotNet)
|
||||
{
|
||||
return ".NET";
|
||||
}
|
||||
|
||||
return "Mono";
|
||||
}
|
||||
}
|
||||
|
||||
public abstract Version Version { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
namespace NzbDrone.Common.EnvironmentInfo
|
||||
using System;
|
||||
|
||||
namespace NzbDrone.Common.EnvironmentInfo
|
||||
{
|
||||
public interface IRuntimeInfo
|
||||
{
|
||||
bool IsUserInteractive { get; }
|
||||
bool IsAdmin { get; }
|
||||
bool IsWindowsService { get; }
|
||||
bool IsConsole { get; }
|
||||
bool IsRunning { get; set; }
|
||||
bool IsExiting { get; set; }
|
||||
bool RestartPending { get; set; }
|
||||
string ExecutingApplication { get; }
|
||||
string RuntimeVersion { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,87 +1,98 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NLog;
|
||||
|
||||
namespace NzbDrone.Common.EnvironmentInfo
|
||||
{
|
||||
public static class OsInfo
|
||||
public class OsInfo : IOsInfo
|
||||
{
|
||||
public static Os Os { get; }
|
||||
|
||||
public static bool IsNotWindows => !IsWindows;
|
||||
public static bool IsLinux => Os == Os.Linux;
|
||||
public static bool IsOsx => Os == Os.Osx;
|
||||
public static bool IsWindows => Os == Os.Windows;
|
||||
|
||||
public string Version { get; }
|
||||
public string Name { get; }
|
||||
public string FullName { get; }
|
||||
|
||||
static OsInfo()
|
||||
{
|
||||
var platform = (int)Environment.OSVersion.Platform;
|
||||
var platform = Environment.OSVersion.Platform;
|
||||
|
||||
Version = Environment.OSVersion.Version;
|
||||
|
||||
IsMonoRuntime = Type.GetType("Mono.Runtime") != null;
|
||||
IsNotWindows = (platform == 4) || (platform == 6) || (platform == 128);
|
||||
IsOsx = IsRunningOnMac();
|
||||
IsLinux = IsNotWindows && !IsOsx;
|
||||
IsWindows = !IsNotWindows;
|
||||
|
||||
FirstDayOfWeek = CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek;
|
||||
|
||||
if (IsWindows)
|
||||
switch (platform)
|
||||
{
|
||||
Os = Os.Windows;
|
||||
PathStringComparison = StringComparison.OrdinalIgnoreCase;
|
||||
case PlatformID.Win32NT:
|
||||
{
|
||||
Os = Os.Windows;
|
||||
break;
|
||||
}
|
||||
case PlatformID.MacOSX:
|
||||
case PlatformID.Unix:
|
||||
{
|
||||
// Sometimes Mac OS reports itself as Unix
|
||||
if (Directory.Exists("/System/Library/CoreServices/") &&
|
||||
(File.Exists("/System/Library/CoreServices/SystemVersion.plist") ||
|
||||
File.Exists("/System/Library/CoreServices/ServerVersion.plist"))
|
||||
)
|
||||
{
|
||||
Os = Os.Osx;
|
||||
}
|
||||
else
|
||||
{
|
||||
Os = Os.Linux;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public OsInfo(IEnumerable<IOsVersionAdapter> versionAdapters, Logger logger)
|
||||
{
|
||||
OsVersionModel osInfo = null;
|
||||
|
||||
foreach (var osVersionAdapter in versionAdapters.Where(c => c.Enabled))
|
||||
{
|
||||
try
|
||||
{
|
||||
osInfo = osVersionAdapter.Read();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error(e, "Couldn't get OS Version info");
|
||||
}
|
||||
|
||||
if (osInfo != null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (osInfo != null)
|
||||
{
|
||||
Name = osInfo.Name;
|
||||
Version = osInfo.Version;
|
||||
FullName = osInfo.FullName;
|
||||
}
|
||||
else
|
||||
{
|
||||
Os = IsOsx ? Os.Osx : Os.Linux;
|
||||
|
||||
PathStringComparison = StringComparison.Ordinal;
|
||||
Name = Os.ToString();
|
||||
FullName = Name;
|
||||
}
|
||||
|
||||
Environment.SetEnvironmentVariable("OS_NAME", Name);
|
||||
Environment.SetEnvironmentVariable("OS_VERSION", Version);
|
||||
}
|
||||
}
|
||||
|
||||
public static Version Version { get; private set; }
|
||||
public static bool IsMonoRuntime { get; private set; }
|
||||
public static bool IsNotWindows { get; private set; }
|
||||
public static bool IsLinux { get; private set; }
|
||||
public static bool IsOsx { get; private set; }
|
||||
public static bool IsWindows { get; private set; }
|
||||
public static Os Os { get; private set; }
|
||||
public static DayOfWeek FirstDayOfWeek { get; private set; }
|
||||
public static StringComparison PathStringComparison { get; private set; }
|
||||
|
||||
//Borrowed from: https://github.com/jpobst/Pinta/blob/master/Pinta.Core/Managers/SystemManager.cs
|
||||
//From Managed.Windows.Forms/XplatUI
|
||||
[DllImport("libc")]
|
||||
static extern int uname(IntPtr buf);
|
||||
|
||||
[DebuggerStepThrough]
|
||||
static bool IsRunningOnMac()
|
||||
{
|
||||
var buf = IntPtr.Zero;
|
||||
|
||||
try
|
||||
{
|
||||
buf = Marshal.AllocHGlobal(8192);
|
||||
// This is a hacktastic way of getting sysname from uname ()
|
||||
if (uname(buf) == 0)
|
||||
{
|
||||
var os = Marshal.PtrToStringAnsi(buf);
|
||||
|
||||
if (os == "Darwin")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (buf != IntPtr.Zero)
|
||||
{
|
||||
Marshal.FreeHGlobal(buf);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
public interface IOsInfo
|
||||
{
|
||||
string Version { get; }
|
||||
string Name { get; }
|
||||
string FullName { get; }
|
||||
}
|
||||
|
||||
public enum Os
|
||||
|
||||
29
src/NzbDrone.Common/EnvironmentInfo/OsVersionModel.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
namespace NzbDrone.Common.EnvironmentInfo
|
||||
{
|
||||
public class OsVersionModel
|
||||
{
|
||||
|
||||
|
||||
public OsVersionModel(string name, string version, string fullName = null)
|
||||
{
|
||||
Name = Trim(name);
|
||||
Version = Trim(version);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(fullName))
|
||||
{
|
||||
fullName = $"{Name} {Version}";
|
||||
}
|
||||
|
||||
FullName = Trim(fullName);
|
||||
}
|
||||
|
||||
private static string Trim(string source)
|
||||
{
|
||||
return source.Trim().Trim('"', '\'');
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
public string FullName { get; }
|
||||
public string Version { get; }
|
||||
}
|
||||
}
|
||||
@@ -5,15 +5,14 @@ using System.Reflection;
|
||||
using System.Security.Principal;
|
||||
using System.ServiceProcess;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Processes;
|
||||
|
||||
namespace NzbDrone.Common.EnvironmentInfo
|
||||
{
|
||||
public abstract class RuntimeInfoBase : IRuntimeInfo
|
||||
public class RuntimeInfo : IRuntimeInfo
|
||||
{
|
||||
private readonly Logger _logger;
|
||||
|
||||
public RuntimeInfoBase(IServiceProvider serviceProvider, Logger logger)
|
||||
public RuntimeInfo(IServiceProvider serviceProvider, Logger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
|
||||
@@ -31,7 +30,7 @@ namespace NzbDrone.Common.EnvironmentInfo
|
||||
}
|
||||
}
|
||||
|
||||
static RuntimeInfoBase()
|
||||
static RuntimeInfo()
|
||||
{
|
||||
IsProduction = InternalIsProduction();
|
||||
}
|
||||
@@ -59,31 +58,18 @@ namespace NzbDrone.Common.EnvironmentInfo
|
||||
|
||||
public bool IsWindowsService { get; private set; }
|
||||
|
||||
public bool IsConsole
|
||||
{
|
||||
get
|
||||
{
|
||||
if (OsInfo.IsWindows)
|
||||
{
|
||||
return IsUserInteractive && Process.GetCurrentProcess().ProcessName.Equals(ProcessProvider.NZB_DRONE_CONSOLE_PROCESS_NAME, StringComparison.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsRunning { get; set; }
|
||||
public bool IsExiting { get; set; }
|
||||
public bool RestartPending { get; set; }
|
||||
public string ExecutingApplication { get; private set; }
|
||||
public string ExecutingApplication { get; }
|
||||
|
||||
public abstract string RuntimeVersion { get; }
|
||||
|
||||
public static bool IsProduction { get; private set; }
|
||||
public static bool IsProduction { get; }
|
||||
|
||||
private static bool InternalIsProduction()
|
||||
{
|
||||
if (BuildInfo.IsDebug || Debugger.IsAttached) return false;
|
||||
if (BuildInfo.Version.Revision > 10000) return false; //Official builds will never have such a high revision
|
||||
|
||||
//Official builds will never have such a high revision
|
||||
if (BuildInfo.Version.Revision > 10000) return false;
|
||||
|
||||
try
|
||||
{
|
||||
@@ -99,18 +85,19 @@ namespace NzbDrone.Common.EnvironmentInfo
|
||||
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var currentAssmeblyLocation = typeof(RuntimeInfoBase).Assembly.Location;
|
||||
if(currentAssmeblyLocation.ToLower().Contains("_output"))return false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
try
|
||||
{
|
||||
var currentAssemblyLocation = typeof(RuntimeInfo).Assembly.Location;
|
||||
if (currentAssemblyLocation.ToLower().Contains("_output")) return false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
string lowerCurrentDir = Directory.GetCurrentDirectory().ToLower();
|
||||
var lowerCurrentDir = Directory.GetCurrentDirectory().ToLower();
|
||||
if (lowerCurrentDir.Contains("teamcity")) return false;
|
||||
if (lowerCurrentDir.Contains("buildagent")) return false;
|
||||
if (lowerCurrentDir.Contains("_output")) return false;
|
||||
|
||||
return true;
|
||||
@@ -1,73 +0,0 @@
|
||||
using System.ComponentModel;
|
||||
using System.Configuration;
|
||||
|
||||
namespace NzbDrone.Common.Exceptron.Configuration
|
||||
{
|
||||
|
||||
public class ExceptronConfiguration : ConfigurationSection
|
||||
{
|
||||
public ExceptronConfiguration()
|
||||
{
|
||||
Host = "http://exceptron.azurewebsites.net/api/v1/";
|
||||
IncludeMachineName = true;
|
||||
}
|
||||
|
||||
public static ExceptronConfiguration ReadConfig(string sectionName = "exceptron")
|
||||
{
|
||||
var configSection = ConfigurationManager.GetSection(sectionName);
|
||||
|
||||
if (configSection == null)
|
||||
{
|
||||
throw new ConfigurationErrorsException("ExceptronConfiguration section missing.");
|
||||
}
|
||||
|
||||
return (ExceptronConfiguration)configSection;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// exceptron api address. Do not modify this property.
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public string Host { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If ExceptronClinet should throw exceptions in case of an error. Default: <see cref="bool.False"/>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Its recommended that this flag is set to True during development and <see cref="bool.False"/> in production systems.
|
||||
/// If an exception is thrown while this flag is set to <see cref="bool.False"/> the thrown exception will be returned in <see cref="ExceptionResponse.Exception"/>
|
||||
/// </remarks>
|
||||
[ConfigurationProperty("throwExceptions", DefaultValue = false)]
|
||||
public bool ThrowExceptions
|
||||
{
|
||||
get { return (bool)this["throwExceptions"]; }
|
||||
set { this["throwExceptions"] = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The API of this application. Can find your API key in application settings page.
|
||||
/// </summary>
|
||||
[ConfigurationProperty("apiKey")]
|
||||
public string ApiKey
|
||||
{
|
||||
get { return (string)this["apiKey"]; }
|
||||
set { this["apiKey"] = value; }
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// If the machine name should be attached to the exception report
|
||||
/// </summary>
|
||||
/// <remarks>Machine name can be usefull in webfarm enviroments when multiple
|
||||
/// servers are running the same app and the issue could be machine specific.
|
||||
/// Hoewever, You might want to disable this feature for privacy reasons.</remarks>
|
||||
[ConfigurationProperty("includeMachineName", DefaultValue = true)]
|
||||
public bool IncludeMachineName
|
||||
{
|
||||
get { return (bool)this["includeMachineName"]; }
|
||||
set { this["includeMachineName"] = value; }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace NzbDrone.Common.Exceptron
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents information that will be used to construct an exception report.
|
||||
/// </summary>
|
||||
public class ExceptionData
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception that is being reported
|
||||
/// </summary>
|
||||
public Exception Exception { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Component that experianced this exception.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// It is common to use the logger name that was used to log the exception as the component.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// DataAccess, Configuration, Registration, etc.
|
||||
/// </example>
|
||||
public string Component { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ID that will uniquely identify the user
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This Id does not have to be tied to the user's identity.
|
||||
/// You can use a system generated unique ID such as GUID.
|
||||
/// This field is used to report how many unique users are experiencing an error.
|
||||
/// </remarks>
|
||||
/// <example>
|
||||
/// "62E5C8EF-0CA2-43AB-B278-FC6994F776ED"
|
||||
/// "Timmy@aol.com"
|
||||
/// "26437"
|
||||
/// </example>
|
||||
public string UserId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Any message that should be attached to this exceptions
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// Something went wrong while checking for application updates.
|
||||
/// </example>
|
||||
public string Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Severity of the exception being reported
|
||||
/// </summary>
|
||||
public ExceptionSeverity Severity { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
using System;
|
||||
using NzbDrone.Common.EnvironmentInfo;
|
||||
|
||||
namespace NzbDrone.Common.Exceptron
|
||||
{
|
||||
public static class ExceptionExtentions
|
||||
{
|
||||
private const string IGNORE_FLAG = "exceptron_ignore";
|
||||
|
||||
public static Exception ExceptronIgnoreOnMono(this Exception exception)
|
||||
{
|
||||
if (OsInfo.IsNotWindows)
|
||||
{
|
||||
exception.ExceptronIgnore();
|
||||
}
|
||||
|
||||
return exception;
|
||||
}
|
||||
|
||||
public static Exception ExceptronIgnore(this Exception exception)
|
||||
{
|
||||
exception.Data.Add(IGNORE_FLAG, true);
|
||||
return exception;
|
||||
}
|
||||
|
||||
public static bool ExceptronShouldIgnore(this Exception exception)
|
||||
{
|
||||
return exception.Data.Contains(IGNORE_FLAG);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
namespace NzbDrone.Common.Exceptron
|
||||
{
|
||||
/// <summary>
|
||||
/// Severity of the exception being reported
|
||||
/// </summary>
|
||||
public enum ExceptionSeverity
|
||||
{
|
||||
/// <summary>
|
||||
/// Excepted Error. Can be ignored
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Error that can be handled gracefully
|
||||
/// </summary>
|
||||
Warning = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Blocking user from completing their intended action
|
||||
/// </summary>
|
||||
Error = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Will most likely cause the application to crash
|
||||
/// </summary>
|
||||
Fatal = 3
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
using System;
|
||||
using System.Net;
|
||||
|
||||
namespace NzbDrone.Common.Exceptron
|
||||
{
|
||||
public class ExceptronApiException : Exception
|
||||
{
|
||||
public ExceptronApiException(WebException innerException, string message)
|
||||
: base(message, innerException)
|
||||
{
|
||||
Response = (HttpWebResponse)innerException.Response;
|
||||
}
|
||||
|
||||
public HttpWebResponse Response { get; private set; }
|
||||
}
|
||||
}
|
||||
@@ -1,285 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using NzbDrone.Common.Exceptron.Configuration;
|
||||
using NzbDrone.Common.Exceptron.Message;
|
||||
|
||||
namespace NzbDrone.Common.Exceptron
|
||||
{
|
||||
public class ExceptronClient : IExceptronClient
|
||||
{
|
||||
internal IRestClient RestClient { private get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Version of Client
|
||||
/// </summary>
|
||||
public string ClientVersion => Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Name of Client
|
||||
/// </summary>
|
||||
public string ClientName => "Official .NET";
|
||||
|
||||
/// <summary>
|
||||
/// Client Configuration
|
||||
/// </summary>
|
||||
public ExceptronConfiguration Configuration { get; private set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Framework Type of the Host Application (.Net/mono)
|
||||
/// </summary>
|
||||
public string FrameworkType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="ExceptronClient"/>
|
||||
/// Loads <see cref="ExceptronConfiguration"/> from application config file.
|
||||
/// </summary>
|
||||
/// <param name="applicationVersion">Version of the currently running application</param>
|
||||
public ExceptronClient(Version applicationVersion)
|
||||
: this(ExceptronConfiguration.ReadConfig(), applicationVersion)
|
||||
{
|
||||
FrameworkType = ".Net";
|
||||
}
|
||||
|
||||
private readonly string _applicationVersion;
|
||||
private readonly string _maxFrameworkVersion;
|
||||
|
||||
|
||||
/// <param name="exceptronConfiguration">exceptron client configuration</param>
|
||||
/// <param name="applicationVersion"> </param>
|
||||
public ExceptronClient(ExceptronConfiguration exceptronConfiguration, Version applicationVersion)
|
||||
{
|
||||
if (exceptronConfiguration == null)
|
||||
throw new ArgumentNullException("exceptronConfiguration");
|
||||
|
||||
if (applicationVersion == null)
|
||||
throw new ArgumentNullException("applicationVersion");
|
||||
|
||||
if (string.IsNullOrEmpty(exceptronConfiguration.ApiKey))
|
||||
throw new ArgumentException("An API Key was not provided");
|
||||
|
||||
Configuration = exceptronConfiguration;
|
||||
|
||||
RestClient = new RestClient();
|
||||
|
||||
_applicationVersion = applicationVersion.ToString();
|
||||
|
||||
_maxFrameworkVersion = GetMaximumFrameworkVersion();
|
||||
|
||||
FrameworkType = ".Net";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Submit an exception to exceptron Servers.
|
||||
/// </summary>
|
||||
/// <param name="exception">Exception that is being reported</param>
|
||||
/// <param name="component"
|
||||
/// example="DataAccess, Configuration, Registration, etc."
|
||||
/// remarks="It is common to use the logger name that was used to log the exception as the component.">Component that experienced this exception.</param>
|
||||
|
||||
/// <param name="severity">Severity of the exception being reported</param>
|
||||
/// <param name="message"
|
||||
/// example="Something went wrong while checking for application updates.">Any message that should be attached to this exceptions</param>
|
||||
/// <param name="userId"
|
||||
/// remarks="This Id does not have to be tied to the user's identity.
|
||||
/// You can use a system generated unique ID such as GUID.
|
||||
/// This field is used to report how many unique users are experiencing an error."
|
||||
/// example="
|
||||
/// 62E5C8EF-0CA2-43AB-B278-FC6994F776ED
|
||||
/// Timmy@aol.com
|
||||
/// 26437
|
||||
/// ">ID that will uniquely identify the user</param>
|
||||
/// <param name="httpContext"><see cref="System.Web.HttpContext"/> in which the exception occurred. If no <see cref="System.Web.HttpContext"/> is provided
|
||||
/// <see cref="ExceptronClient"/> will try to get the current <see cref="System.Web.HttpContext"/> from <see cref="System.Web.HttpContext.Current"/></param>
|
||||
/// <returns></returns>
|
||||
public ExceptionResponse SubmitException(Exception exception, string component, ExceptionSeverity severity = ExceptionSeverity.None, string message = null, string userId = null)
|
||||
{
|
||||
var exceptionData = new ExceptionData
|
||||
{
|
||||
Exception = exception,
|
||||
Component = component,
|
||||
Severity = severity,
|
||||
Message = message,
|
||||
UserId = userId
|
||||
};
|
||||
|
||||
return SubmitException(exceptionData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Submit an exception to exceptron Servers.
|
||||
/// </summary>
|
||||
/// <param name="exceptionData">Exception data to be reported to the server</param>
|
||||
public ExceptionResponse SubmitException(ExceptionData exceptionData)
|
||||
{
|
||||
try
|
||||
{
|
||||
ValidateState(exceptionData);
|
||||
|
||||
var report = new ExceptionReport();
|
||||
|
||||
report.ap = Configuration.ApiKey;
|
||||
report.dn = ClientName;
|
||||
report.dv = ClientVersion;
|
||||
report.aver = _applicationVersion;
|
||||
|
||||
report.ext = exceptionData.Exception.GetType().FullName;
|
||||
report.stk = ConvertToFrames(exceptionData.Exception);
|
||||
report.exm = exceptionData.Exception.Message;
|
||||
|
||||
report.cmp = exceptionData.Component;
|
||||
report.uid = exceptionData.UserId;
|
||||
report.msg = exceptionData.Message;
|
||||
report.sv = (int)exceptionData.Severity;
|
||||
report.fv = _maxFrameworkVersion;
|
||||
report.ft = FrameworkType;
|
||||
|
||||
SetEnviromentInfo(report);
|
||||
|
||||
var exceptionResponse = RestClient.Put<ExceptionResponse>(Configuration.Host, report);
|
||||
|
||||
exceptionData.Exception.Data["et"] = exceptionResponse.RefId;
|
||||
|
||||
return exceptionResponse;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Trace.WriteLine("Unable to submit exception to exceptron. ", e.ToString());
|
||||
|
||||
if (Configuration.ThrowExceptions)
|
||||
{
|
||||
//throw;
|
||||
}
|
||||
|
||||
return new ExceptionResponse { Exception = e };
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateState(ExceptionData exceptionData)
|
||||
{
|
||||
if (string.IsNullOrEmpty(Configuration.ApiKey))
|
||||
throw new InvalidOperationException("ApiKey has not been provided for this client.");
|
||||
|
||||
if (exceptionData == null)
|
||||
throw new ArgumentNullException("exceptionData");
|
||||
|
||||
if (exceptionData.Exception == null)
|
||||
throw new ArgumentException("ExceptionData.Exception Cannot be null.", "exceptionData");
|
||||
}
|
||||
|
||||
private void SetEnviromentInfo(ExceptionReport report)
|
||||
{
|
||||
report.cul = Thread.CurrentThread.CurrentCulture.Name;
|
||||
|
||||
if (string.IsNullOrEmpty(report.cul))
|
||||
report.cul = "en";
|
||||
|
||||
try
|
||||
{
|
||||
report.os = Environment.OSVersion.VersionString;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
if (Configuration.ThrowExceptions) throw;
|
||||
}
|
||||
|
||||
if (Configuration.IncludeMachineName)
|
||||
{
|
||||
try
|
||||
{
|
||||
report.hn = Environment.MachineName;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
if (Configuration.ThrowExceptions) throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
internal static List<Frame> ConvertToFrames(Exception exception)
|
||||
{
|
||||
if (exception == null) return null;
|
||||
|
||||
var stackTrace = new StackTrace(exception, true);
|
||||
|
||||
var frames = stackTrace.GetFrames();
|
||||
|
||||
if (frames == null) return null;
|
||||
|
||||
var result = new List<Frame>();
|
||||
|
||||
for (int index = 0; index < frames.Length; index++)
|
||||
{
|
||||
var frame = frames[index];
|
||||
var method = frame.GetMethod();
|
||||
var declaringType = method.DeclaringType;
|
||||
|
||||
var fileName = frame.GetFileName();
|
||||
|
||||
var currentFrame = new Frame
|
||||
{
|
||||
i = index,
|
||||
fn = fileName,
|
||||
ln = frame.GetFileLineNumber(),
|
||||
m = method.ToString(),
|
||||
};
|
||||
|
||||
currentFrame.m = currentFrame.m.Substring(currentFrame.m.IndexOf(' ')).Trim();
|
||||
|
||||
|
||||
if (declaringType != null)
|
||||
{
|
||||
currentFrame.c = declaringType.FullName;
|
||||
}
|
||||
|
||||
result.Add(currentFrame);
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private string GetMaximumFrameworkVersion()
|
||||
{
|
||||
var clrVersion = Environment.Version;
|
||||
|
||||
if (clrVersion.Major == 2)
|
||||
{
|
||||
//Check if 2.0 or 3.5
|
||||
try
|
||||
{
|
||||
Assembly.Load("System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
|
||||
return "3.5";
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
|
||||
return "2.0";
|
||||
}
|
||||
|
||||
if (clrVersion.Major == 4)
|
||||
{
|
||||
//Check if 4.0 or 4.5
|
||||
try
|
||||
{
|
||||
Assembly.Load("System.Threading.Tasks.Parallel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
|
||||
return "4.5";
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
|
||||
return "4.0";
|
||||
}
|
||||
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
using System;
|
||||
using NzbDrone.Common.Exceptron.Configuration;
|
||||
using NzbDrone.Common.Exceptron.Message;
|
||||
|
||||
namespace NzbDrone.Common.Exceptron
|
||||
{
|
||||
public interface IExceptronClient
|
||||
{
|
||||
/// <summary>
|
||||
/// Client Configuration
|
||||
/// </summary>
|
||||
ExceptronConfiguration Configuration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Submit an exception to exceptron Servers.
|
||||
/// </summary>
|
||||
/// <param name="exceptionData">Exception data to be reported to the server</param>
|
||||
ExceptionResponse SubmitException(ExceptionData exceptionData);
|
||||
|
||||
/// <summary>
|
||||
/// Submit an exception to exceptron Servers.
|
||||
/// </summary>
|
||||
/// <param name="exception">Exception that is being reported</param>
|
||||
/// <param name="component"
|
||||
/// example="DataAccess, Configuration, Registration, etc."
|
||||
/// remarks="It is common to use the logger name that was used to log the exception as the component.">Component that experienced this exception.</param>
|
||||
/// <param name="severity">Severity of the exception being reported</param>
|
||||
/// <param name="message"
|
||||
/// example="Something went wrong while checking for application updates.">Any message that should be attached to this exceptions</param>
|
||||
/// <param name="userId"
|
||||
/// remarks="This Id does not have to be tied to the user's identity.
|
||||
/// You can use a system generated unique ID such as GUID.
|
||||
/// This field is used to report how many unique users are experiencing an error."
|
||||
/// example="
|
||||
/// 62E5C8EF-0CA2-43AB-B278-FC6994F776ED
|
||||
/// Timmy@aol.com
|
||||
/// 26437
|
||||
/// ">ID that will uniquely identify the user</param>
|
||||
/// <param name="httpContext"><see cref="System.Web.HttpContext"/> in which the exception occurred. If no <see cref="System.Web.HttpContext"/> is provided
|
||||
/// <see cref="ExceptronClient"/> will try to get the current <see cref="System.Web.HttpContext"/> from <see cref="System.Web.HttpContext.Current"/></param>
|
||||
/// <returns></returns>
|
||||
ExceptionResponse SubmitException(Exception exception, string component, ExceptionSeverity severity = ExceptionSeverity.None, string message = null, string userId = null);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
namespace NzbDrone.Common.Exceptron
|
||||
{
|
||||
internal interface IRestClient
|
||||
{
|
||||
TResponse Put<TResponse>(string url, object report) where TResponse : class, new();
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NzbDrone.Common.Exceptron.Message
|
||||
{
|
||||
internal class ExceptionReport
|
||||
{
|
||||
/// <summary>
|
||||
/// API key
|
||||
/// </summary>
|
||||
public string ap { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Application Version
|
||||
/// </summary>
|
||||
public string aver { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Exception Severity
|
||||
/// </summary>
|
||||
public int sv { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// User or Instance ID
|
||||
/// </summary>
|
||||
public string uid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Type of exception
|
||||
/// </summary>
|
||||
public string ext { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Exception message
|
||||
/// </summary>
|
||||
public string exm { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// List of frames that make up the StackTrace of the exception
|
||||
/// </summary>
|
||||
public List<Frame> stk { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Component that experienced this exception
|
||||
/// </summary>
|
||||
public string cmp { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Message that was logged along with the exception.
|
||||
/// </summary>
|
||||
public string msg { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// User's culture in
|
||||
/// </summary>
|
||||
/// <remarks>http://msdn.microsoft.com/en-us/library/system.globalization.cultureinfo.name.aspx</remarks>
|
||||
public string cul { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// OS Version
|
||||
/// </summary>
|
||||
public string os { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Name of the Client that generated and is sending this message
|
||||
/// </summary>
|
||||
public string dn { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Version of the Client that generated and is sending this message
|
||||
/// </summary>
|
||||
public string dv { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Host name of the machine that encountered this exception
|
||||
/// </summary>
|
||||
public string hn { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Request url
|
||||
/// <remarks>Only used for exception in context of a web request/</remarks>
|
||||
public string url { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Browser useragent
|
||||
/// </summary>
|
||||
/// <remarks>Only used for exception in context of a web request/</remarks>
|
||||
public string ua { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// HTTP response status code
|
||||
/// </summary>
|
||||
/// <remarks>Only used for exception in context of a web request/</remarks>
|
||||
public int sc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the HTTP data transfer method used by the client.
|
||||
/// </summary>
|
||||
/// <example>GET, POST, PUT, DELETE</example>
|
||||
public string hm { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Framework Version (CLR) of the Host Application
|
||||
/// </summary>
|
||||
public string fv { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Framework Type of the Host Application
|
||||
/// </summary>
|
||||
public string ft { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace NzbDrone.Common.Exceptron.Message
|
||||
{
|
||||
public class ExceptionResponse
|
||||
{
|
||||
/// <summary>
|
||||
/// Exception report reference ID. This ID will be shared across
|
||||
/// similar exceptions
|
||||
/// </summary>
|
||||
public string RefId { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Was the report successfully processed on the server
|
||||
/// </summary>
|
||||
public bool Successful => !string.IsNullOrEmpty(RefId);
|
||||
|
||||
/// <summary>
|
||||
/// Exception that caused the message to fail.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This property will only be populated if <see cref="ExceptronConfiguration.ThrowExceptions"/> is set to <see cref="bool.False"/>/>
|
||||
/// Exception is thrown if <see cref="ExceptronConfiguration.ThrowExceptions"/> is set to <see cref="bool.True"/>.
|
||||
/// </remarks>
|
||||
public Exception Exception { get; internal set; }
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
namespace NzbDrone.Common.Exceptron.Message
|
||||
{
|
||||
internal class Frame
|
||||
{
|
||||
/// <summary>
|
||||
/// Order of current frame
|
||||
/// </summary>
|
||||
public int i { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Line number of the current frame
|
||||
/// </summary>
|
||||
public int ln { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Method name for current frame
|
||||
/// </summary>
|
||||
public string m { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Class name for current frame
|
||||
/// </summary>
|
||||
public string c { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// File name for current frame
|
||||
/// </summary>
|
||||
public string fn { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using NzbDrone.Common.Exceptron.fastJSON;
|
||||
|
||||
namespace NzbDrone.Common.Exceptron
|
||||
{
|
||||
public sealed class RestClient : IRestClient
|
||||
{
|
||||
public TResponse Put<TResponse>(string url, object content) where TResponse : class ,new()
|
||||
{
|
||||
|
||||
if (content == null)
|
||||
throw new ArgumentNullException("content can not be null", "content");
|
||||
|
||||
if (string.IsNullOrEmpty(url))
|
||||
throw new ArgumentNullException("url can not be null or empty", "url");
|
||||
|
||||
Trace.WriteLine("Attempting PUT to " + url);
|
||||
|
||||
var json = JSON.Instance.ToJSON(content);
|
||||
|
||||
var bytes = Encoding.UTF8.GetBytes(json);
|
||||
var request = (HttpWebRequest)WebRequest.Create(url);
|
||||
request.Timeout = 10000;
|
||||
request.Method = "PUT";
|
||||
request.ContentType = "application/json";
|
||||
request.ContentLength = bytes.Length;
|
||||
request.Accept = "application/json";
|
||||
|
||||
var dataStream = request.GetRequestStream();
|
||||
dataStream.Write(bytes, 0, bytes.Length);
|
||||
dataStream.Close();
|
||||
|
||||
string responseContent = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
var webResponse = request.GetResponse();
|
||||
responseContent = ReadResponse(webResponse);
|
||||
var response = JSON.Instance.ToObject<TResponse>(responseContent);
|
||||
|
||||
return response;
|
||||
}
|
||||
catch (WebException e)
|
||||
{
|
||||
Trace.WriteLine(e.ToString());
|
||||
responseContent = ReadResponse(e.Response);
|
||||
throw new ExceptronApiException(e, responseContent);
|
||||
}
|
||||
finally
|
||||
{
|
||||
Trace.WriteLine(responseContent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static string ReadResponse(WebResponse webResponse)
|
||||
{
|
||||
if (webResponse == null) return string.Empty;
|
||||
|
||||
var responseStream = webResponse.GetResponseStream();
|
||||
|
||||
if (responseStream == null) return string.Empty;
|
||||
|
||||
var decodedStream = new StreamReader(responseStream, Encoding.GetEncoding(1252));
|
||||
return decodedStream.ReadToEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
//http://fastjson.codeplex.com/
|
||||
//http://fastjson.codeplex.com/license
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NzbDrone.Common.Exceptron.fastJSON
|
||||
{
|
||||
internal class Getters
|
||||
{
|
||||
public string Name;
|
||||
public JSON.GenericGetter Getter;
|
||||
public Type propertyType;
|
||||
}
|
||||
|
||||
internal class DatasetSchema
|
||||
{
|
||||
public List<string> Info { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,820 +0,0 @@
|
||||
//http://fastjson.codeplex.com/
|
||||
//http://fastjson.codeplex.com/license
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using System.Xml.Serialization;
|
||||
|
||||
namespace NzbDrone.Common.Exceptron.fastJSON
|
||||
{
|
||||
|
||||
internal class JSON
|
||||
{
|
||||
public readonly static JSON Instance = new JSON();
|
||||
|
||||
private JSON()
|
||||
{
|
||||
UseSerializerExtension = false;
|
||||
SerializeNullValues = false;
|
||||
UseOptimizedDatasetSchema = false;
|
||||
UsingGlobalTypes = false;
|
||||
}
|
||||
public bool UseOptimizedDatasetSchema = true;
|
||||
public bool UseFastGuid = true;
|
||||
public bool UseSerializerExtension = true;
|
||||
public bool IndentOutput = false;
|
||||
public bool SerializeNullValues = true;
|
||||
public bool UseUTCDateTime = false;
|
||||
public bool ShowReadOnlyProperties = false;
|
||||
public bool UsingGlobalTypes = true;
|
||||
|
||||
public string ToJSON(object obj)
|
||||
{
|
||||
return ToJSON(obj, UseSerializerExtension, UseFastGuid, UseOptimizedDatasetSchema, SerializeNullValues);
|
||||
}
|
||||
|
||||
|
||||
public string ToJSON(object obj,
|
||||
bool enableSerializerExtensions,
|
||||
bool enableFastGuid,
|
||||
bool enableOptimizedDatasetSchema,
|
||||
bool serializeNullValues)
|
||||
{
|
||||
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>;
|
||||
if (ht == null) return null;
|
||||
return ParseDictionary(ht, null, type);
|
||||
}
|
||||
|
||||
#if CUSTOMTYPE
|
||||
internal SafeDictionary<Type, Serialize> _customSerializer = new SafeDictionary<Type, Serialize>();
|
||||
internal SafeDictionary<Type, Deserialize> _customDeserializer = new SafeDictionary<Type, Deserialize>();
|
||||
|
||||
public void RegisterCustomType(Type type, Serialize serializer, Deserialize deserializer)
|
||||
{
|
||||
if (type != null && serializer != null && deserializer != null)
|
||||
{
|
||||
_customSerializer.Add(type, serializer);
|
||||
_customDeserializer.Add(type, deserializer);
|
||||
// reset property cache
|
||||
_propertycache = new SafeDictionary<string, SafeDictionary<string, myPropInfo>>();
|
||||
}
|
||||
}
|
||||
|
||||
internal bool IsTypeRegistered(Type t)
|
||||
{
|
||||
Serialize s;
|
||||
return _customSerializer.TryGetValue(t, out s);
|
||||
}
|
||||
#endif
|
||||
|
||||
#region [ PROPERTY GET SET CACHE ]
|
||||
|
||||
readonly SafeDictionary<Type, string> _tyname = new SafeDictionary<Type, string>();
|
||||
internal string GetTypeAssemblyName(Type t)
|
||||
{
|
||||
string val = "";
|
||||
if (_tyname.TryGetValue(t, out val))
|
||||
return val;
|
||||
string s = t.AssemblyQualifiedName;
|
||||
_tyname.Add(t, s);
|
||||
return s;
|
||||
}
|
||||
|
||||
readonly SafeDictionary<string, Type> _typecache = new SafeDictionary<string, Type>();
|
||||
private Type GetTypeFromCache(string typename)
|
||||
{
|
||||
Type val = null;
|
||||
if (_typecache.TryGetValue(typename, out val))
|
||||
return val;
|
||||
Type t = Type.GetType(typename);
|
||||
_typecache.Add(typename, t);
|
||||
return t;
|
||||
}
|
||||
|
||||
readonly SafeDictionary<Type, CreateObject> _constrcache = new SafeDictionary<Type, CreateObject>();
|
||||
private delegate object CreateObject();
|
||||
private object FastCreateInstance(Type objtype)
|
||||
{
|
||||
try
|
||||
{
|
||||
CreateObject c = null;
|
||||
if (_constrcache.TryGetValue(objtype, out c))
|
||||
{
|
||||
return c();
|
||||
}
|
||||
DynamicMethod dynMethod = new DynamicMethod("_", objtype, null, true);
|
||||
ILGenerator ilGen = dynMethod.GetILGenerator();
|
||||
|
||||
ilGen.Emit(OpCodes.Newobj, objtype.GetConstructor(Type.EmptyTypes));
|
||||
ilGen.Emit(OpCodes.Ret);
|
||||
c = (CreateObject)dynMethod.CreateDelegate(typeof(CreateObject));
|
||||
_constrcache.Add(objtype, c);
|
||||
return c();
|
||||
}
|
||||
catch (Exception exc)
|
||||
{
|
||||
throw new Exception(string.Format("Failed to fast create instance for type '{0}' from assemebly '{1}'",
|
||||
objtype.FullName, objtype.AssemblyQualifiedName), exc);
|
||||
}
|
||||
}
|
||||
|
||||
private struct myPropInfo
|
||||
{
|
||||
public bool filled;
|
||||
public Type pt;
|
||||
public Type bt;
|
||||
public Type changeType;
|
||||
public bool isDictionary;
|
||||
public bool isValueType;
|
||||
public bool isGenericType;
|
||||
public bool isArray;
|
||||
public bool isByteArray;
|
||||
public bool isGuid;
|
||||
#if !SILVERLIGHT
|
||||
public bool isDataSet;
|
||||
public bool isDataTable;
|
||||
public bool isHashtable;
|
||||
#endif
|
||||
public GenericSetter setter;
|
||||
public bool isEnum;
|
||||
public bool isDateTime;
|
||||
public Type[] GenericTypes;
|
||||
public bool isInt;
|
||||
public bool isLong;
|
||||
public bool isString;
|
||||
public bool isBool;
|
||||
public bool isClass;
|
||||
public GenericGetter getter;
|
||||
public bool isStringDictionary;
|
||||
public string Name;
|
||||
#if CUSTOMTYPE
|
||||
public bool isCustomType;
|
||||
#endif
|
||||
public bool CanWrite;
|
||||
}
|
||||
|
||||
readonly SafeDictionary<string, SafeDictionary<string, myPropInfo>> _propertycache = new SafeDictionary<string, SafeDictionary<string, myPropInfo>>();
|
||||
private SafeDictionary<string, myPropInfo> Getproperties(Type type, string typename)
|
||||
{
|
||||
SafeDictionary<string, myPropInfo> sd = null;
|
||||
if (_propertycache.TryGetValue(typename, out sd))
|
||||
{
|
||||
return sd;
|
||||
}
|
||||
sd = new SafeDictionary<string, myPropInfo>();
|
||||
var pr = type.GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
|
||||
foreach (var p in pr)
|
||||
{
|
||||
myPropInfo d = CreateMyProp(p.PropertyType, p.Name);
|
||||
d.CanWrite = p.CanWrite;
|
||||
d.setter = CreateSetMethod(p);
|
||||
d.getter = CreateGetMethod(p);
|
||||
sd.Add(p.Name, d);
|
||||
}
|
||||
|
||||
_propertycache.Add(typename, sd);
|
||||
return sd;
|
||||
}
|
||||
|
||||
private myPropInfo CreateMyProp(Type t, string name)
|
||||
{
|
||||
myPropInfo d = new myPropInfo();
|
||||
d.filled = true;
|
||||
d.CanWrite = true;
|
||||
d.pt = t;
|
||||
d.Name = name;
|
||||
d.isDictionary = t.Name.Contains("Dictionary");
|
||||
if (d.isDictionary)
|
||||
d.GenericTypes = t.GetGenericArguments();
|
||||
d.isValueType = t.IsValueType;
|
||||
d.isGenericType = t.IsGenericType;
|
||||
d.isArray = t.IsArray;
|
||||
if (d.isArray)
|
||||
d.bt = t.GetElementType();
|
||||
if (d.isGenericType)
|
||||
d.bt = t.GetGenericArguments()[0];
|
||||
d.isByteArray = t == typeof(byte[]);
|
||||
d.isGuid = (t == typeof(Guid) || t == typeof(Guid?));
|
||||
#if !SILVERLIGHT
|
||||
d.isHashtable = t == typeof(Hashtable);
|
||||
d.isDataSet = t == typeof(DataSet);
|
||||
d.isDataTable = t == typeof(DataTable);
|
||||
#endif
|
||||
|
||||
d.changeType = GetChangeType(t);
|
||||
d.isEnum = t.IsEnum;
|
||||
d.isDateTime = t == typeof(DateTime) || t == typeof(DateTime?);
|
||||
d.isInt = t == typeof(int) || t == typeof(int?);
|
||||
d.isLong = t == typeof(long) || t == typeof(long?);
|
||||
d.isString = t == typeof(string);
|
||||
d.isBool = t == typeof(bool) || t == typeof(bool?);
|
||||
d.isClass = t.IsClass;
|
||||
|
||||
if (d.isDictionary && d.GenericTypes.Length > 0 && d.GenericTypes[0] == typeof(string))
|
||||
d.isStringDictionary = true;
|
||||
|
||||
#if CUSTOMTYPE
|
||||
if (IsTypeRegistered(t))
|
||||
d.isCustomType = true;
|
||||
#endif
|
||||
return d;
|
||||
}
|
||||
|
||||
private delegate void GenericSetter(object target, object value);
|
||||
|
||||
private static GenericSetter CreateSetMethod(PropertyInfo propertyInfo)
|
||||
{
|
||||
MethodInfo setMethod = propertyInfo.GetSetMethod(nonPublic: true);
|
||||
if (setMethod == null)
|
||||
return null;
|
||||
|
||||
var arguments = new Type[2];
|
||||
arguments[0] = arguments[1] = typeof(object);
|
||||
|
||||
DynamicMethod setter = new DynamicMethod("_", typeof(void), arguments, true);
|
||||
ILGenerator il = setter.GetILGenerator();
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Castclass, propertyInfo.DeclaringType);
|
||||
il.Emit(OpCodes.Ldarg_1);
|
||||
|
||||
if (propertyInfo.PropertyType.IsClass)
|
||||
il.Emit(OpCodes.Castclass, propertyInfo.PropertyType);
|
||||
else
|
||||
il.Emit(OpCodes.Unbox_Any, propertyInfo.PropertyType);
|
||||
|
||||
il.EmitCall(OpCodes.Callvirt, setMethod, null);
|
||||
il.Emit(OpCodes.Ret);
|
||||
|
||||
return (GenericSetter)setter.CreateDelegate(typeof(GenericSetter));
|
||||
}
|
||||
|
||||
internal delegate object GenericGetter(object obj);
|
||||
|
||||
|
||||
private GenericGetter CreateGetMethod(PropertyInfo propertyInfo)
|
||||
{
|
||||
MethodInfo getMethod = propertyInfo.GetGetMethod();
|
||||
if (getMethod == null)
|
||||
return null;
|
||||
|
||||
var arguments = new Type[1];
|
||||
arguments[0] = typeof(object);
|
||||
|
||||
DynamicMethod getter = new DynamicMethod("_", typeof(object), arguments, true);
|
||||
ILGenerator il = getter.GetILGenerator();
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Castclass, propertyInfo.DeclaringType);
|
||||
il.EmitCall(OpCodes.Callvirt, getMethod, null);
|
||||
|
||||
if (!propertyInfo.PropertyType.IsClass)
|
||||
il.Emit(OpCodes.Box, propertyInfo.PropertyType);
|
||||
|
||||
il.Emit(OpCodes.Ret);
|
||||
|
||||
return (GenericGetter)getter.CreateDelegate(typeof(GenericGetter));
|
||||
}
|
||||
|
||||
readonly SafeDictionary<Type, List<Getters>> _getterscache = new SafeDictionary<Type, List<Getters>>();
|
||||
internal List<Getters> GetGetters(Type type)
|
||||
{
|
||||
List<Getters> val = null;
|
||||
if (_getterscache.TryGetValue(type, out val))
|
||||
return val;
|
||||
|
||||
var props = type.GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
|
||||
var getters = new List<Getters>();
|
||||
foreach (var p in props)
|
||||
{
|
||||
if (!p.CanWrite && ShowReadOnlyProperties == false) continue;
|
||||
|
||||
var att = p.GetCustomAttributes(typeof(XmlIgnoreAttribute), false);
|
||||
if (att != null && att.Length > 0)
|
||||
continue;
|
||||
|
||||
GenericGetter g = CreateGetMethod(p);
|
||||
if (g != null)
|
||||
{
|
||||
Getters gg = new Getters();
|
||||
gg.Name = p.Name;
|
||||
gg.Getter = g;
|
||||
gg.propertyType = p.PropertyType;
|
||||
getters.Add(gg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_getterscache.Add(type, getters);
|
||||
return getters;
|
||||
}
|
||||
|
||||
private object ChangeType(object value, Type conversionType)
|
||||
{
|
||||
if (conversionType == typeof(int))
|
||||
return (int)CreateLong((string)value);
|
||||
|
||||
if (conversionType == typeof(long))
|
||||
return CreateLong((string)value);
|
||||
|
||||
if (conversionType == typeof(string))
|
||||
return value;
|
||||
|
||||
if (conversionType == typeof(Guid))
|
||||
return CreateGuid((string)value);
|
||||
|
||||
if (conversionType.IsEnum)
|
||||
return CreateEnum(conversionType, (string)value);
|
||||
|
||||
return Convert.ChangeType(value, conversionType, CultureInfo.InvariantCulture);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
private object ParseDictionary(Dictionary<string, object> d, Dictionary<string, object> globaltypes, Type type)
|
||||
{
|
||||
object tn = "";
|
||||
if (d.TryGetValue("$types", out tn))
|
||||
{
|
||||
UsingGlobalTypes = true;
|
||||
globaltypes = new Dictionary<string, object>();
|
||||
foreach (var kv in (Dictionary<string, object>)tn)
|
||||
{
|
||||
globaltypes.Add((string)kv.Value, kv.Key);
|
||||
}
|
||||
}
|
||||
|
||||
bool found = d.TryGetValue("$type", out tn);
|
||||
#if !SILVERLIGHT
|
||||
if (found == false && type == typeof(object))
|
||||
{
|
||||
return CreateDataset(d, globaltypes);
|
||||
}
|
||||
#endif
|
||||
if (found)
|
||||
{
|
||||
if (UsingGlobalTypes)
|
||||
{
|
||||
object tname = "";
|
||||
if (globaltypes.TryGetValue((string)tn, out tname))
|
||||
tn = tname;
|
||||
}
|
||||
type = GetTypeFromCache((string)tn);
|
||||
}
|
||||
|
||||
if (type == null)
|
||||
throw new Exception("Cannot determine type");
|
||||
|
||||
string typename = type.FullName;
|
||||
object o = FastCreateInstance(type);
|
||||
var props = Getproperties(type, typename);
|
||||
foreach (var name in d.Keys)
|
||||
{
|
||||
if (name == "$map")
|
||||
{
|
||||
ProcessMap(o, props, (Dictionary<string, object>)d[name]);
|
||||
continue;
|
||||
}
|
||||
myPropInfo pi;
|
||||
if (props.TryGetValue(name, out pi) == false)
|
||||
continue;
|
||||
if (pi.filled)
|
||||
{
|
||||
object v = d[name];
|
||||
|
||||
if (v != null)
|
||||
{
|
||||
object oset = null;
|
||||
|
||||
if (pi.isInt)
|
||||
oset = (int)CreateLong((string)v);
|
||||
#if CUSTOMTYPE
|
||||
else if (pi.isCustomType)
|
||||
oset = CreateCustom((string)v, pi.pt);
|
||||
#endif
|
||||
else if (pi.isLong)
|
||||
oset = CreateLong((string)v);
|
||||
|
||||
else if (pi.isString)
|
||||
oset = v;
|
||||
|
||||
else if (pi.isBool)
|
||||
oset = (bool)v;
|
||||
|
||||
else if (pi.isGenericType && pi.isValueType == false && pi.isDictionary == false)
|
||||
#if SILVERLIGHT
|
||||
oset = CreateGenericList((List<object>)v, pi.pt, pi.bt, globaltypes);
|
||||
#else
|
||||
oset = CreateGenericList((ArrayList)v, pi.pt, pi.bt, globaltypes);
|
||||
#endif
|
||||
else if (pi.isByteArray)
|
||||
oset = Convert.FromBase64String((string)v);
|
||||
|
||||
else if (pi.isArray && pi.isValueType == false)
|
||||
#if SILVERLIGHT
|
||||
oset = CreateArray((List<object>)v, pi.pt, pi.bt, globaltypes);
|
||||
#else
|
||||
oset = CreateArray((ArrayList)v, pi.pt, pi.bt, globaltypes);
|
||||
#endif
|
||||
else if (pi.isGuid)
|
||||
oset = CreateGuid((string)v);
|
||||
#if !SILVERLIGHT
|
||||
else if (pi.isDataSet)
|
||||
oset = CreateDataset((Dictionary<string, object>)v, globaltypes);
|
||||
|
||||
else if (pi.isDataTable)
|
||||
oset = CreateDataTable((Dictionary<string, object>)v, globaltypes);
|
||||
#endif
|
||||
|
||||
else if (pi.isStringDictionary)
|
||||
oset = CreateStringKeyDictionary((Dictionary<string, object>)v, pi.pt, pi.GenericTypes, globaltypes);
|
||||
|
||||
#if !SILVERLIGHT
|
||||
else if (pi.isDictionary || pi.isHashtable)
|
||||
oset = CreateDictionary((ArrayList)v, pi.pt, pi.GenericTypes, globaltypes);
|
||||
#else
|
||||
else if (pi.isDictionary)
|
||||
oset = CreateDictionary((List<object>)v, pi.pt, pi.GenericTypes, globaltypes);
|
||||
#endif
|
||||
|
||||
else if (pi.isEnum)
|
||||
oset = CreateEnum(pi.pt, (string)v);
|
||||
|
||||
else if (pi.isDateTime)
|
||||
oset = CreateDateTime((string)v);
|
||||
|
||||
else if (pi.isClass && v is Dictionary<string, object>)
|
||||
oset = ParseDictionary((Dictionary<string, object>)v, globaltypes, pi.pt);
|
||||
|
||||
else if (pi.isValueType)
|
||||
oset = ChangeType(v, pi.changeType);
|
||||
|
||||
#if SILVERLIGHT
|
||||
else if (v is List<object>)
|
||||
oset = CreateArray((List<object>)v, pi.pt, typeof(object), globaltypes);
|
||||
#else
|
||||
else if (v is ArrayList)
|
||||
oset = CreateArray((ArrayList)v, pi.pt, typeof(object), globaltypes);
|
||||
#endif
|
||||
else
|
||||
oset = v;
|
||||
|
||||
if (pi.CanWrite)
|
||||
pi.setter(o, oset);
|
||||
}
|
||||
}
|
||||
}
|
||||
return o;
|
||||
}
|
||||
|
||||
#if CUSTOMTYPE
|
||||
private object CreateCustom(string v, Type type)
|
||||
{
|
||||
Deserialize d;
|
||||
_customDeserializer.TryGetValue(type, out d);
|
||||
return d(v);
|
||||
}
|
||||
#endif
|
||||
|
||||
private void ProcessMap(object obj, SafeDictionary<string, myPropInfo> props, Dictionary<string, object> dic)
|
||||
{
|
||||
foreach (var kv in dic)
|
||||
{
|
||||
myPropInfo p = props[kv.Key];
|
||||
object o = p.getter(obj);
|
||||
Type t = Type.GetType((string)kv.Value);
|
||||
if (t == typeof(Guid))
|
||||
p.setter(obj, CreateGuid((string)o));
|
||||
}
|
||||
}
|
||||
|
||||
private long CreateLong(string s)
|
||||
{
|
||||
long num = 0;
|
||||
bool neg = false;
|
||||
foreach (var cc in s)
|
||||
{
|
||||
if (cc == '-')
|
||||
neg = true;
|
||||
else if (cc == '+')
|
||||
neg = false;
|
||||
else
|
||||
{
|
||||
num *= 10;
|
||||
num += (cc - '0');
|
||||
}
|
||||
}
|
||||
|
||||
return neg ? -num : num;
|
||||
}
|
||||
|
||||
private object CreateEnum(Type pt, string v)
|
||||
{
|
||||
// TODO : optimize create enum
|
||||
#if !SILVERLIGHT
|
||||
return Enum.Parse(pt, v);
|
||||
#else
|
||||
return Enum.Parse(pt, v, true);
|
||||
#endif
|
||||
}
|
||||
|
||||
private Guid CreateGuid(string s)
|
||||
{
|
||||
if (s.Length > 30)
|
||||
return new Guid(s);
|
||||
return new Guid(Convert.FromBase64String(s));
|
||||
}
|
||||
|
||||
private DateTime CreateDateTime(string value)
|
||||
{
|
||||
bool utc = false;
|
||||
// 0123456789012345678
|
||||
// datetime format = yyyy-MM-dd HH:mm:ss
|
||||
int year = (int)CreateLong(value.Substring(0, 4));
|
||||
int month = (int)CreateLong(value.Substring(5, 2));
|
||||
int day = (int)CreateLong(value.Substring(8, 2));
|
||||
int hour = (int)CreateLong(value.Substring(11, 2));
|
||||
int min = (int)CreateLong(value.Substring(14, 2));
|
||||
int sec = (int)CreateLong(value.Substring(17, 2));
|
||||
|
||||
if (value.EndsWith("Z"))
|
||||
utc = true;
|
||||
|
||||
if (UseUTCDateTime == false && utc == false)
|
||||
return new DateTime(year, month, day, hour, min, sec);
|
||||
return new DateTime(year, month, day, hour, min, sec, DateTimeKind.Utc).ToLocalTime();
|
||||
}
|
||||
|
||||
#if SILVERLIGHT
|
||||
private object CreateArray(List<object> data, Type pt, Type bt, Dictionary<string, object> globalTypes)
|
||||
{
|
||||
Array col = Array.CreateInstance(bt, data.Count);
|
||||
// create an array of objects
|
||||
for (int i = 0; i < data.Count; i++)// each (object ob in data)
|
||||
{
|
||||
object ob = data[i];
|
||||
if (ob is IDictionary)
|
||||
col.SetValue(ParseDictionary((Dictionary<string, object>)ob, globalTypes, bt), i);
|
||||
else
|
||||
col.SetValue(ChangeType(ob, bt), i);
|
||||
}
|
||||
|
||||
return col;
|
||||
}
|
||||
#else
|
||||
private object CreateArray(ArrayList data, Type pt, Type bt, Dictionary<string, object> globalTypes)
|
||||
{
|
||||
ArrayList col = new ArrayList();
|
||||
// create an array of objects
|
||||
foreach (var ob in data)
|
||||
{
|
||||
if (ob is IDictionary)
|
||||
col.Add(ParseDictionary((Dictionary<string, object>)ob, globalTypes, bt));
|
||||
else
|
||||
col.Add(ChangeType(ob, bt));
|
||||
}
|
||||
return col.ToArray(bt);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#if SILVERLIGHT
|
||||
private object CreateGenericList(List<object> data, Type pt, Type bt, Dictionary<string, object> globalTypes)
|
||||
#else
|
||||
private object CreateGenericList(ArrayList data, Type pt, Type bt, Dictionary<string, object> globalTypes)
|
||||
#endif
|
||||
{
|
||||
IList col = (IList)FastCreateInstance(pt);
|
||||
// create an array of objects
|
||||
foreach (var ob in data)
|
||||
{
|
||||
if (ob is IDictionary)
|
||||
col.Add(ParseDictionary((Dictionary<string, object>)ob, globalTypes, bt));
|
||||
#if SILVERLIGHT
|
||||
else if (ob is List<object>)
|
||||
col.Add(((List<object>)ob).ToArray());
|
||||
#else
|
||||
else if (ob is ArrayList)
|
||||
col.Add(((ArrayList)ob).ToArray());
|
||||
#endif
|
||||
else
|
||||
col.Add(ChangeType(ob, bt));
|
||||
}
|
||||
return col;
|
||||
}
|
||||
|
||||
private object CreateStringKeyDictionary(Dictionary<string, object> reader, Type pt, Type[] types, Dictionary<string, object> globalTypes)
|
||||
{
|
||||
var col = (IDictionary)FastCreateInstance(pt);
|
||||
Type t1 = null;
|
||||
Type t2 = null;
|
||||
if (types != null)
|
||||
{
|
||||
t1 = types[0];
|
||||
t2 = types[1];
|
||||
}
|
||||
|
||||
foreach (var values in reader)
|
||||
{
|
||||
var key = values.Key;//ChangeType(values.Key, t1);
|
||||
object val = null;
|
||||
if (values.Value is Dictionary<string, object>)
|
||||
val = ParseDictionary((Dictionary<string, object>)values.Value, globalTypes, t2);
|
||||
else
|
||||
val = ChangeType(values.Value, t2);
|
||||
col.Add(key, val);
|
||||
}
|
||||
|
||||
return col;
|
||||
}
|
||||
|
||||
#if SILVERLIGHT
|
||||
private object CreateDictionary(List<object> reader, Type pt, Type[] types, Dictionary<string, object> globalTypes)
|
||||
#else
|
||||
private object CreateDictionary(ArrayList reader, Type pt, Type[] types, Dictionary<string, object> globalTypes)
|
||||
#endif
|
||||
{
|
||||
IDictionary col = (IDictionary)FastCreateInstance(pt);
|
||||
Type t1 = null;
|
||||
Type t2 = null;
|
||||
if (types != null)
|
||||
{
|
||||
t1 = types[0];
|
||||
t2 = types[1];
|
||||
}
|
||||
|
||||
foreach (Dictionary<string, object> values in reader)
|
||||
{
|
||||
object key = values["k"];
|
||||
object val = values["v"];
|
||||
|
||||
if (key is Dictionary<string, object>)
|
||||
key = ParseDictionary((Dictionary<string, object>)key, globalTypes, t1);
|
||||
else
|
||||
key = ChangeType(key, t1);
|
||||
|
||||
if (val is Dictionary<string, object>)
|
||||
val = ParseDictionary((Dictionary<string, object>)val, globalTypes, t2);
|
||||
else
|
||||
val = ChangeType(val, t2);
|
||||
|
||||
col.Add(key, val);
|
||||
}
|
||||
|
||||
return col;
|
||||
}
|
||||
|
||||
private Type GetChangeType(Type conversionType)
|
||||
{
|
||||
if (conversionType.IsGenericType && conversionType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
|
||||
return conversionType.GetGenericArguments()[0];
|
||||
|
||||
return conversionType;
|
||||
}
|
||||
#if !SILVERLIGHT
|
||||
private DataSet CreateDataset(Dictionary<string, object> reader, Dictionary<string, object> globalTypes)
|
||||
{
|
||||
DataSet ds = new DataSet();
|
||||
ds.EnforceConstraints = false;
|
||||
ds.BeginInit();
|
||||
|
||||
// read dataset schema here
|
||||
ReadSchema(reader, ds, globalTypes);
|
||||
|
||||
foreach (var pair in reader)
|
||||
{
|
||||
if (pair.Key == "$type" || pair.Key == "$schema") continue;
|
||||
|
||||
ArrayList rows = (ArrayList)pair.Value;
|
||||
if (rows == null) continue;
|
||||
|
||||
DataTable dt = ds.Tables[pair.Key];
|
||||
ReadDataTable(rows, dt);
|
||||
}
|
||||
|
||||
ds.EndInit();
|
||||
|
||||
return ds;
|
||||
}
|
||||
|
||||
private void ReadSchema(Dictionary<string, object> reader, DataSet ds, Dictionary<string, object> globalTypes)
|
||||
{
|
||||
var schema = reader["$schema"];
|
||||
|
||||
if (schema is string)
|
||||
{
|
||||
TextReader tr = new StringReader((string)schema);
|
||||
ds.ReadXmlSchema(tr);
|
||||
}
|
||||
else
|
||||
{
|
||||
DatasetSchema ms = (DatasetSchema)ParseDictionary((Dictionary<string, object>)schema, globalTypes, typeof(DatasetSchema));
|
||||
ds.DataSetName = ms.Name;
|
||||
for (int i = 0; i < ms.Info.Count; i += 3)
|
||||
{
|
||||
if (ds.Tables.Contains(ms.Info[i]) == false)
|
||||
ds.Tables.Add(ms.Info[i]);
|
||||
ds.Tables[ms.Info[i]].Columns.Add(ms.Info[i + 1], Type.GetType(ms.Info[i + 2]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadDataTable(ArrayList rows, DataTable dt)
|
||||
{
|
||||
dt.BeginInit();
|
||||
dt.BeginLoadData();
|
||||
var guidcols = new List<int>();
|
||||
var datecol = new List<int>();
|
||||
|
||||
foreach (DataColumn c in dt.Columns)
|
||||
{
|
||||
if (c.DataType == typeof(Guid) || c.DataType == typeof(Guid?))
|
||||
guidcols.Add(c.Ordinal);
|
||||
if (UseUTCDateTime && (c.DataType == typeof(DateTime) || c.DataType == typeof(DateTime?)))
|
||||
datecol.Add(c.Ordinal);
|
||||
}
|
||||
|
||||
foreach (ArrayList row in rows)
|
||||
{
|
||||
var v = new object[row.Count];
|
||||
row.CopyTo(v, 0);
|
||||
foreach (var i in guidcols)
|
||||
{
|
||||
string s = (string)v[i];
|
||||
if (s != null && s.Length < 36)
|
||||
v[i] = new Guid(Convert.FromBase64String(s));
|
||||
}
|
||||
if (UseUTCDateTime)
|
||||
{
|
||||
foreach (var i in datecol)
|
||||
{
|
||||
string s = (string)v[i];
|
||||
if (s != null)
|
||||
v[i] = CreateDateTime(s);
|
||||
}
|
||||
}
|
||||
dt.Rows.Add(v);
|
||||
}
|
||||
|
||||
dt.EndLoadData();
|
||||
dt.EndInit();
|
||||
}
|
||||
|
||||
DataTable CreateDataTable(Dictionary<string, object> reader, Dictionary<string, object> globalTypes)
|
||||
{
|
||||
var dt = new DataTable();
|
||||
|
||||
// read dataset schema here
|
||||
var schema = reader["$schema"];
|
||||
|
||||
if (schema is string)
|
||||
{
|
||||
TextReader tr = new StringReader((string)schema);
|
||||
dt.ReadXmlSchema(tr);
|
||||
}
|
||||
else
|
||||
{
|
||||
var ms = (DatasetSchema)ParseDictionary((Dictionary<string, object>)schema, globalTypes, typeof(DatasetSchema));
|
||||
dt.TableName = ms.Info[0];
|
||||
for (int i = 0; i < ms.Info.Count; i += 3)
|
||||
{
|
||||
dt.Columns.Add(ms.Info[i + 1], Type.GetType(ms.Info[i + 2]));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var pair in reader)
|
||||
{
|
||||
if (pair.Key == "$type" || pair.Key == "$schema")
|
||||
continue;
|
||||
|
||||
var rows = (ArrayList)pair.Value;
|
||||
if (rows == null)
|
||||
continue;
|
||||
|
||||
if (!dt.TableName.Equals(pair.Key, StringComparison.InvariantCultureIgnoreCase))
|
||||
continue;
|
||||
|
||||
ReadDataTable(rows, dt);
|
||||
}
|
||||
|
||||
return dt;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -1,409 +0,0 @@
|
||||
//http://fastjson.codeplex.com/
|
||||
//http://fastjson.codeplex.com/license
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace NzbDrone.Common.Exceptron.fastJSON
|
||||
{
|
||||
/// <summary>
|
||||
/// This class encodes and decodes JSON strings.
|
||||
/// Spec. details, see http://www.json.org/
|
||||
///
|
||||
/// JSON uses Arrays and Objects. These correspond here to the datatypes ArrayList and Hashtable.
|
||||
/// All numbers are parsed to doubles.
|
||||
/// </summary>
|
||||
internal class JsonParser
|
||||
{
|
||||
enum Token
|
||||
{
|
||||
None = -1, // Used to denote no Lookahead available
|
||||
Curly_Open,
|
||||
Curly_Close,
|
||||
Squared_Open,
|
||||
Squared_Close,
|
||||
Colon,
|
||||
Comma,
|
||||
String,
|
||||
Number,
|
||||
True,
|
||||
False,
|
||||
Null
|
||||
}
|
||||
|
||||
readonly char[] json;
|
||||
readonly StringBuilder s = new StringBuilder();
|
||||
Token lookAheadToken = Token.None;
|
||||
int index;
|
||||
|
||||
internal JsonParser(string json)
|
||||
{
|
||||
this.json = json.ToCharArray();
|
||||
}
|
||||
|
||||
public object Decode()
|
||||
{
|
||||
return ParseValue();
|
||||
}
|
||||
|
||||
private Dictionary<string, object> ParseObject()
|
||||
{
|
||||
var table = new Dictionary<string, object>();
|
||||
|
||||
ConsumeToken(); // {
|
||||
|
||||
while (true)
|
||||
{
|
||||
switch (LookAhead())
|
||||
{
|
||||
|
||||
case Token.Comma:
|
||||
ConsumeToken();
|
||||
break;
|
||||
|
||||
case Token.Curly_Close:
|
||||
ConsumeToken();
|
||||
return table;
|
||||
|
||||
default:
|
||||
{
|
||||
|
||||
// name
|
||||
string name = ParseString();
|
||||
|
||||
// :
|
||||
if (NextToken() != Token.Colon)
|
||||
{
|
||||
throw new Exception("Expected colon at index " + index);
|
||||
}
|
||||
|
||||
// value
|
||||
object value = ParseValue();
|
||||
|
||||
table[name] = value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if SILVERLIGHT
|
||||
private List<object> ParseArray()
|
||||
{
|
||||
List<object> array = new List<object>();
|
||||
#else
|
||||
private ArrayList ParseArray()
|
||||
{
|
||||
ArrayList array = new ArrayList();
|
||||
#endif
|
||||
ConsumeToken(); // [
|
||||
|
||||
while (true)
|
||||
{
|
||||
switch (LookAhead())
|
||||
{
|
||||
|
||||
case Token.Comma:
|
||||
ConsumeToken();
|
||||
break;
|
||||
|
||||
case Token.Squared_Close:
|
||||
ConsumeToken();
|
||||
return array;
|
||||
|
||||
default:
|
||||
{
|
||||
array.Add(ParseValue());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private object ParseValue()
|
||||
{
|
||||
switch (LookAhead())
|
||||
{
|
||||
case Token.Number:
|
||||
return ParseNumber();
|
||||
|
||||
case Token.String:
|
||||
return ParseString();
|
||||
|
||||
case Token.Curly_Open:
|
||||
return ParseObject();
|
||||
|
||||
case Token.Squared_Open:
|
||||
return ParseArray();
|
||||
|
||||
case Token.True:
|
||||
ConsumeToken();
|
||||
return true;
|
||||
|
||||
case Token.False:
|
||||
ConsumeToken();
|
||||
return false;
|
||||
|
||||
case Token.Null:
|
||||
ConsumeToken();
|
||||
return null;
|
||||
}
|
||||
|
||||
throw new Exception("Unrecognized token at index" + index);
|
||||
}
|
||||
|
||||
private string ParseString()
|
||||
{
|
||||
ConsumeToken(); // "
|
||||
|
||||
s.Length = 0;
|
||||
|
||||
int runIndex = -1;
|
||||
|
||||
while (index < json.Length)
|
||||
{
|
||||
var c = json[index++];
|
||||
|
||||
if (c == '"')
|
||||
{
|
||||
if (runIndex != -1)
|
||||
{
|
||||
if (s.Length == 0)
|
||||
return new string(json, runIndex, index - runIndex - 1);
|
||||
|
||||
s.Append(json, runIndex, index - runIndex - 1);
|
||||
}
|
||||
return s.ToString();
|
||||
}
|
||||
|
||||
if (c != '\\')
|
||||
{
|
||||
if (runIndex == -1)
|
||||
runIndex = index - 1;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (index == json.Length) break;
|
||||
|
||||
if (runIndex != -1)
|
||||
{
|
||||
s.Append(json, runIndex, index - runIndex - 1);
|
||||
runIndex = -1;
|
||||
}
|
||||
|
||||
switch (json[index++])
|
||||
{
|
||||
case '"':
|
||||
s.Append('"');
|
||||
break;
|
||||
|
||||
case '\\':
|
||||
s.Append('\\');
|
||||
break;
|
||||
|
||||
case '/':
|
||||
s.Append('/');
|
||||
break;
|
||||
|
||||
case 'b':
|
||||
s.Append('\b');
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
s.Append('\f');
|
||||
break;
|
||||
|
||||
case 'n':
|
||||
s.Append('\n');
|
||||
break;
|
||||
|
||||
case 'r':
|
||||
s.Append('\r');
|
||||
break;
|
||||
|
||||
case 't':
|
||||
s.Append('\t');
|
||||
break;
|
||||
|
||||
case 'u':
|
||||
{
|
||||
int remainingLength = json.Length - index;
|
||||
if (remainingLength < 4) break;
|
||||
|
||||
// parse the 32 bit hex into an integer codepoint
|
||||
uint codePoint = ParseUnicode(json[index], json[index + 1], json[index + 2], json[index + 3]);
|
||||
s.Append((char)codePoint);
|
||||
|
||||
// skip 4 chars
|
||||
index += 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception("Unexpectedly reached end of string");
|
||||
}
|
||||
|
||||
private uint ParseSingleChar(char c1, uint multipliyer)
|
||||
{
|
||||
uint p1 = 0;
|
||||
if (c1 >= '0' && c1 <= '9')
|
||||
p1 = (uint)(c1 - '0') * multipliyer;
|
||||
else if (c1 >= 'A' && c1 <= 'F')
|
||||
p1 = (uint)((c1 - 'A') + 10) * multipliyer;
|
||||
else if (c1 >= 'a' && c1 <= 'f')
|
||||
p1 = (uint)((c1 - 'a') + 10) * multipliyer;
|
||||
return p1;
|
||||
}
|
||||
|
||||
private uint ParseUnicode(char c1, char c2, char c3, char c4)
|
||||
{
|
||||
uint p1 = ParseSingleChar(c1, 0x1000);
|
||||
uint p2 = ParseSingleChar(c2, 0x100);
|
||||
uint p3 = ParseSingleChar(c3, 0x10);
|
||||
uint p4 = ParseSingleChar(c4, 1);
|
||||
|
||||
return p1 + p2 + p3 + p4;
|
||||
}
|
||||
|
||||
private string ParseNumber()
|
||||
{
|
||||
ConsumeToken();
|
||||
|
||||
// Need to start back one place because the first digit is also a token and would have been consumed
|
||||
var startIndex = index - 1;
|
||||
|
||||
do
|
||||
{
|
||||
var c = json[index];
|
||||
|
||||
if ((c >= '0' && c <= '9') || c == '.' || c == '-' || c == '+' || c == 'e' || c == 'E')
|
||||
{
|
||||
if (++index == json.Length) throw new Exception("Unexpected end of string whilst parsing number");
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
} while (true);
|
||||
|
||||
return new string(json, startIndex, index - startIndex);
|
||||
}
|
||||
|
||||
private Token LookAhead()
|
||||
{
|
||||
if (lookAheadToken != Token.None) return lookAheadToken;
|
||||
|
||||
return lookAheadToken = NextTokenCore();
|
||||
}
|
||||
|
||||
private void ConsumeToken()
|
||||
{
|
||||
lookAheadToken = Token.None;
|
||||
}
|
||||
|
||||
private Token NextToken()
|
||||
{
|
||||
var result = lookAheadToken != Token.None ? lookAheadToken : NextTokenCore();
|
||||
|
||||
lookAheadToken = Token.None;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Token NextTokenCore()
|
||||
{
|
||||
char c;
|
||||
|
||||
// Skip past whitespace
|
||||
do
|
||||
{
|
||||
c = json[index];
|
||||
|
||||
if (c > ' ') break;
|
||||
if (c != ' ' && c != '\t' && c != '\n' && c != '\r') break;
|
||||
|
||||
} while (++index < json.Length);
|
||||
|
||||
if (index == json.Length)
|
||||
{
|
||||
throw new Exception("Reached end of string unexpectedly");
|
||||
}
|
||||
|
||||
c = json[index];
|
||||
|
||||
index++;
|
||||
|
||||
//if (c >= '0' && c <= '9')
|
||||
// return Token.Number;
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case '{':
|
||||
return Token.Curly_Open;
|
||||
|
||||
case '}':
|
||||
return Token.Curly_Close;
|
||||
|
||||
case '[':
|
||||
return Token.Squared_Open;
|
||||
|
||||
case ']':
|
||||
return Token.Squared_Close;
|
||||
|
||||
case ',':
|
||||
return Token.Comma;
|
||||
|
||||
case '"':
|
||||
return Token.String;
|
||||
|
||||
case '0': case '1': case '2': case '3': case '4':
|
||||
case '5': case '6': case '7': case '8': case '9':
|
||||
case '-': case '+': case '.':
|
||||
return Token.Number;
|
||||
|
||||
case ':':
|
||||
return Token.Colon;
|
||||
|
||||
case 'f':
|
||||
if (json.Length - index >= 4 &&
|
||||
json[index + 0] == 'a' &&
|
||||
json[index + 1] == 'l' &&
|
||||
json[index + 2] == 's' &&
|
||||
json[index + 3] == 'e')
|
||||
{
|
||||
index += 4;
|
||||
return Token.False;
|
||||
}
|
||||
break;
|
||||
|
||||
case 't':
|
||||
if (json.Length - index >= 3 &&
|
||||
json[index + 0] == 'r' &&
|
||||
json[index + 1] == 'u' &&
|
||||
json[index + 2] == 'e')
|
||||
{
|
||||
index += 3;
|
||||
return Token.True;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'n':
|
||||
if (json.Length - index >= 3 &&
|
||||
json[index + 0] == 'u' &&
|
||||
json[index + 1] == 'l' &&
|
||||
json[index + 2] == 'l')
|
||||
{
|
||||
index += 3;
|
||||
return Token.Null;
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
throw new Exception("Could not find token at index " + --index);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,519 +0,0 @@
|
||||
//http://fastjson.codeplex.com/
|
||||
//http://fastjson.codeplex.com/license
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace NzbDrone.Common.Exceptron.fastJSON
|
||||
{
|
||||
internal class JSONSerializer
|
||||
{
|
||||
private readonly StringBuilder _output = new StringBuilder();
|
||||
readonly bool useMinimalDataSetSchema;
|
||||
readonly bool fastguid = true;
|
||||
readonly bool useExtension = true;
|
||||
readonly bool serializeNulls = true;
|
||||
readonly int _MAX_DEPTH = 10;
|
||||
readonly bool _Indent;
|
||||
readonly bool _useGlobalTypes = true;
|
||||
int _current_depth;
|
||||
private readonly Dictionary<string, int> _globalTypes = new Dictionary<string, int>();
|
||||
|
||||
internal JSONSerializer(bool UseMinimalDataSetSchema, bool UseFastGuid, bool UseExtensions, bool SerializeNulls, bool IndentOutput)
|
||||
{
|
||||
useMinimalDataSetSchema = UseMinimalDataSetSchema;
|
||||
fastguid = UseFastGuid;
|
||||
useExtension = UseExtensions;
|
||||
_Indent = IndentOutput;
|
||||
serializeNulls = SerializeNulls;
|
||||
if (useExtension == false)
|
||||
_useGlobalTypes = false;
|
||||
}
|
||||
|
||||
internal string ConvertToJSON(object obj)
|
||||
{
|
||||
WriteValue(obj);
|
||||
|
||||
string str = "";
|
||||
if (_useGlobalTypes)
|
||||
{
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.Append("{\"$types\":{");
|
||||
bool pendingSeparator = false;
|
||||
foreach (var kv in _globalTypes)
|
||||
{
|
||||
if (pendingSeparator) sb.Append(',');
|
||||
pendingSeparator = true;
|
||||
sb.Append("\"");
|
||||
sb.Append(kv.Key);
|
||||
sb.Append("\":\"");
|
||||
sb.Append(kv.Value);
|
||||
sb.Append("\"");
|
||||
}
|
||||
sb.Append("},");
|
||||
str = sb + _output.ToString();
|
||||
}
|
||||
else
|
||||
str = _output.ToString();
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
private void WriteValue(object obj)
|
||||
{
|
||||
if (obj == null || obj is DBNull)
|
||||
_output.Append("null");
|
||||
|
||||
else if (obj is string || obj is char)
|
||||
WriteString((string)obj);
|
||||
|
||||
else if (obj is Guid)
|
||||
WriteGuid((Guid)obj);
|
||||
|
||||
else if (obj is bool)
|
||||
_output.Append(((bool)obj) ? "true" : "false"); // conform to standard
|
||||
|
||||
else if (
|
||||
obj is int || obj is long || obj is double ||
|
||||
obj is decimal || obj is float ||
|
||||
obj is byte || obj is short ||
|
||||
obj is sbyte || obj is ushort ||
|
||||
obj is uint || obj is ulong
|
||||
)
|
||||
_output.Append(((IConvertible)obj).ToString(NumberFormatInfo.InvariantInfo));
|
||||
|
||||
else if (obj is DateTime)
|
||||
WriteDateTime((DateTime)obj);
|
||||
|
||||
else if (obj is IDictionary && obj.GetType().IsGenericType && obj.GetType().GetGenericArguments()[0] == typeof(string))
|
||||
WriteStringDictionary((IDictionary)obj);
|
||||
|
||||
else if (obj is IDictionary)
|
||||
WriteDictionary((IDictionary)obj);
|
||||
#if !SILVERLIGHT
|
||||
else if (obj is DataSet)
|
||||
WriteDataset((DataSet)obj);
|
||||
|
||||
else if (obj is DataTable)
|
||||
WriteDataTable((DataTable)obj);
|
||||
#endif
|
||||
else if (obj is byte[])
|
||||
WriteBytes((byte[])obj);
|
||||
|
||||
else if (obj is Array || obj is IList || obj is ICollection)
|
||||
WriteArray((IEnumerable)obj);
|
||||
|
||||
else if (obj is Enum)
|
||||
WriteEnum((Enum)obj);
|
||||
|
||||
#if CUSTOMTYPE
|
||||
else if (JSON.Instance.IsTypeRegistered(obj.GetType()))
|
||||
WriteCustom(obj);
|
||||
#endif
|
||||
else
|
||||
WriteObject(obj);
|
||||
}
|
||||
|
||||
#if CUSTOMTYPE
|
||||
private void WriteCustom(object obj)
|
||||
{
|
||||
Serialize s;
|
||||
JSON.Instance._customSerializer.TryGetValue(obj.GetType(), out s);
|
||||
WriteStringFast(s(obj));
|
||||
}
|
||||
#endif
|
||||
|
||||
private void WriteEnum(Enum e)
|
||||
{
|
||||
// TODO : optimize enum write
|
||||
WriteStringFast(e.ToString());
|
||||
}
|
||||
|
||||
private void WriteGuid(Guid g)
|
||||
{
|
||||
if (fastguid == false)
|
||||
WriteStringFast(g.ToString());
|
||||
else
|
||||
WriteBytes(g.ToByteArray());
|
||||
}
|
||||
|
||||
private void WriteBytes(byte[] bytes)
|
||||
{
|
||||
#if !SILVERLIGHT
|
||||
WriteStringFast(Convert.ToBase64String(bytes, 0, bytes.Length, Base64FormattingOptions.None));
|
||||
#else
|
||||
WriteStringFast(Convert.ToBase64String(bytes, 0, bytes.Length));
|
||||
#endif
|
||||
}
|
||||
|
||||
private void WriteDateTime(DateTime dateTime)
|
||||
{
|
||||
// datetime format standard : yyyy-MM-dd HH:mm:ss
|
||||
DateTime dt = dateTime;
|
||||
if (JSON.Instance.UseUTCDateTime)
|
||||
dt = dateTime.ToUniversalTime();
|
||||
|
||||
_output.Append("\"");
|
||||
_output.Append(dt.Year.ToString("0000", NumberFormatInfo.InvariantInfo));
|
||||
_output.Append("-");
|
||||
_output.Append(dt.Month.ToString("00", NumberFormatInfo.InvariantInfo));
|
||||
_output.Append("-");
|
||||
_output.Append(dt.Day.ToString("00", NumberFormatInfo.InvariantInfo));
|
||||
_output.Append(" ");
|
||||
_output.Append(dt.Hour.ToString("00", NumberFormatInfo.InvariantInfo));
|
||||
_output.Append(":");
|
||||
_output.Append(dt.Minute.ToString("00", NumberFormatInfo.InvariantInfo));
|
||||
_output.Append(":");
|
||||
_output.Append(dt.Second.ToString("00", NumberFormatInfo.InvariantInfo));
|
||||
|
||||
if (JSON.Instance.UseUTCDateTime)
|
||||
_output.Append("Z");
|
||||
|
||||
_output.Append("\"");
|
||||
}
|
||||
#if !SILVERLIGHT
|
||||
private DatasetSchema GetSchema(DataTable ds)
|
||||
{
|
||||
if (ds == null) return null;
|
||||
|
||||
DatasetSchema m = new DatasetSchema();
|
||||
m.Info = new List<string>();
|
||||
m.Name = ds.TableName;
|
||||
|
||||
foreach (DataColumn c in ds.Columns)
|
||||
{
|
||||
m.Info.Add(ds.TableName);
|
||||
m.Info.Add(c.ColumnName);
|
||||
m.Info.Add(c.DataType.ToString());
|
||||
}
|
||||
// TODO : serialize relations and constraints here
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
private DatasetSchema GetSchema(DataSet ds)
|
||||
{
|
||||
if (ds == null) return null;
|
||||
|
||||
DatasetSchema m = new DatasetSchema();
|
||||
m.Info = new List<string>();
|
||||
m.Name = ds.DataSetName;
|
||||
|
||||
foreach (DataTable t in ds.Tables)
|
||||
{
|
||||
foreach (DataColumn c in t.Columns)
|
||||
{
|
||||
m.Info.Add(t.TableName);
|
||||
m.Info.Add(c.ColumnName);
|
||||
m.Info.Add(c.DataType.ToString());
|
||||
}
|
||||
}
|
||||
// TODO : serialize relations and constraints here
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
private string GetXmlSchema(DataTable dt)
|
||||
{
|
||||
using (var writer = new StringWriter())
|
||||
{
|
||||
dt.WriteXmlSchema(writer);
|
||||
return dt.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
private void WriteDataset(DataSet ds)
|
||||
{
|
||||
_output.Append('{');
|
||||
if (useExtension)
|
||||
{
|
||||
WritePair("$schema", useMinimalDataSetSchema ? (object)GetSchema(ds) : ds.GetXmlSchema());
|
||||
_output.Append(',');
|
||||
}
|
||||
bool tablesep = false;
|
||||
foreach (DataTable table in ds.Tables)
|
||||
{
|
||||
if (tablesep) _output.Append(",");
|
||||
tablesep = true;
|
||||
WriteDataTableData(table);
|
||||
}
|
||||
// end dataset
|
||||
_output.Append('}');
|
||||
}
|
||||
|
||||
private void WriteDataTableData(DataTable table)
|
||||
{
|
||||
_output.Append('\"');
|
||||
_output.Append(table.TableName);
|
||||
_output.Append("\":[");
|
||||
DataColumnCollection cols = table.Columns;
|
||||
bool rowseparator = false;
|
||||
foreach (DataRow row in table.Rows)
|
||||
{
|
||||
if (rowseparator) _output.Append(",");
|
||||
rowseparator = true;
|
||||
_output.Append('[');
|
||||
|
||||
bool pendingSeperator = false;
|
||||
foreach (DataColumn column in cols)
|
||||
{
|
||||
if (pendingSeperator) _output.Append(',');
|
||||
WriteValue(row[column]);
|
||||
pendingSeperator = true;
|
||||
}
|
||||
_output.Append(']');
|
||||
}
|
||||
|
||||
_output.Append(']');
|
||||
}
|
||||
|
||||
void WriteDataTable(DataTable dt)
|
||||
{
|
||||
_output.Append('{');
|
||||
if (useExtension)
|
||||
{
|
||||
WritePair("$schema", useMinimalDataSetSchema ? (object)GetSchema(dt) : GetXmlSchema(dt));
|
||||
_output.Append(',');
|
||||
}
|
||||
|
||||
WriteDataTableData(dt);
|
||||
|
||||
// end datatable
|
||||
_output.Append('}');
|
||||
}
|
||||
#endif
|
||||
bool _firstWritten;
|
||||
private void WriteObject(object obj)
|
||||
{
|
||||
Indent();
|
||||
if (_useGlobalTypes == false)
|
||||
_output.Append('{');
|
||||
else
|
||||
{
|
||||
if (_firstWritten)
|
||||
_output.Append("{");
|
||||
}
|
||||
_firstWritten = true;
|
||||
_current_depth++;
|
||||
if (_current_depth > _MAX_DEPTH)
|
||||
throw new Exception("Serializer encountered maximum depth of " + _MAX_DEPTH);
|
||||
|
||||
|
||||
var map = new Dictionary<string, string>();
|
||||
Type t = obj.GetType();
|
||||
bool append = false;
|
||||
if (useExtension)
|
||||
{
|
||||
if (_useGlobalTypes == false)
|
||||
WritePairFast("$type", JSON.Instance.GetTypeAssemblyName(t));
|
||||
else
|
||||
{
|
||||
int dt = 0;
|
||||
string ct = JSON.Instance.GetTypeAssemblyName(t);
|
||||
if (_globalTypes.TryGetValue(ct, out dt) == false)
|
||||
{
|
||||
dt = _globalTypes.Count + 1;
|
||||
_globalTypes.Add(ct, dt);
|
||||
}
|
||||
WritePairFast("$type", dt.ToString());
|
||||
}
|
||||
append = true;
|
||||
}
|
||||
|
||||
var g = JSON.Instance.GetGetters(t);
|
||||
foreach (var p in g)
|
||||
{
|
||||
if (append)
|
||||
_output.Append(',');
|
||||
object o = p.Getter(obj);
|
||||
if ((o == null || o is DBNull) && serializeNulls == false)
|
||||
append = false;
|
||||
else
|
||||
{
|
||||
WritePair(p.Name, o);
|
||||
if (o != null && useExtension)
|
||||
{
|
||||
Type tt = o.GetType();
|
||||
if (tt == typeof(object))
|
||||
map.Add(p.Name, tt.ToString());
|
||||
}
|
||||
append = true;
|
||||
}
|
||||
}
|
||||
if (map.Count > 0 && useExtension)
|
||||
{
|
||||
_output.Append(",\"$map\":");
|
||||
WriteStringDictionary(map);
|
||||
}
|
||||
_current_depth--;
|
||||
Indent();
|
||||
_output.Append('}');
|
||||
_current_depth--;
|
||||
|
||||
}
|
||||
|
||||
private void Indent()
|
||||
{
|
||||
Indent(false);
|
||||
}
|
||||
|
||||
private void Indent(bool dec)
|
||||
{
|
||||
if (_Indent)
|
||||
{
|
||||
_output.Append("\r\n");
|
||||
for (int i = 0; i < _current_depth - (dec ? 1 : 0); i++)
|
||||
_output.Append("\t");
|
||||
}
|
||||
}
|
||||
|
||||
private void WritePairFast(string name, string value)
|
||||
{
|
||||
if ((value == null) && serializeNulls == false)
|
||||
return;
|
||||
Indent();
|
||||
WriteStringFast(name);
|
||||
|
||||
_output.Append(':');
|
||||
|
||||
WriteStringFast(value);
|
||||
}
|
||||
|
||||
private void WritePair(string name, object value)
|
||||
{
|
||||
if ((value == null || value is DBNull) && serializeNulls == false)
|
||||
return;
|
||||
Indent();
|
||||
WriteStringFast(name);
|
||||
|
||||
_output.Append(':');
|
||||
|
||||
WriteValue(value);
|
||||
}
|
||||
|
||||
private void WriteArray(IEnumerable array)
|
||||
{
|
||||
Indent();
|
||||
_output.Append('[');
|
||||
|
||||
bool pendingSeperator = false;
|
||||
|
||||
foreach (var obj in array)
|
||||
{
|
||||
Indent();
|
||||
if (pendingSeperator) _output.Append(',');
|
||||
|
||||
WriteValue(obj);
|
||||
|
||||
pendingSeperator = true;
|
||||
}
|
||||
Indent();
|
||||
_output.Append(']');
|
||||
}
|
||||
|
||||
private void WriteStringDictionary(IDictionary dic)
|
||||
{
|
||||
Indent();
|
||||
_output.Append('{');
|
||||
|
||||
bool pendingSeparator = false;
|
||||
|
||||
foreach (DictionaryEntry entry in dic)
|
||||
{
|
||||
if (pendingSeparator) _output.Append(',');
|
||||
|
||||
WritePair((string)entry.Key, entry.Value);
|
||||
|
||||
pendingSeparator = true;
|
||||
}
|
||||
Indent();
|
||||
_output.Append('}');
|
||||
}
|
||||
|
||||
private void WriteDictionary(IDictionary dic)
|
||||
{
|
||||
Indent();
|
||||
_output.Append('[');
|
||||
|
||||
bool pendingSeparator = false;
|
||||
|
||||
foreach (DictionaryEntry entry in dic)
|
||||
{
|
||||
if (pendingSeparator) _output.Append(',');
|
||||
Indent();
|
||||
_output.Append('{');
|
||||
WritePair("k", entry.Key);
|
||||
_output.Append(",");
|
||||
WritePair("v", entry.Value);
|
||||
Indent();
|
||||
_output.Append('}');
|
||||
|
||||
pendingSeparator = true;
|
||||
}
|
||||
Indent();
|
||||
_output.Append(']');
|
||||
}
|
||||
|
||||
private void WriteStringFast(string s)
|
||||
{
|
||||
//Indent();
|
||||
_output.Append('\"');
|
||||
_output.Append(s);
|
||||
_output.Append('\"');
|
||||
}
|
||||
|
||||
private void WriteString(string s)
|
||||
{
|
||||
//Indent();
|
||||
_output.Append('\"');
|
||||
|
||||
int runIndex = -1;
|
||||
|
||||
for (var index = 0; index < s.Length; ++index)
|
||||
{
|
||||
var c = s[index];
|
||||
|
||||
if (c >= ' ' && c < 128 && c != '\"' && c != '\\')
|
||||
{
|
||||
if (runIndex == -1)
|
||||
{
|
||||
runIndex = index;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (runIndex != -1)
|
||||
{
|
||||
_output.Append(s, runIndex, index - runIndex);
|
||||
runIndex = -1;
|
||||
}
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case '\t': _output.Append("\\t"); break;
|
||||
case '\r': _output.Append("\\r"); break;
|
||||
case '\n': _output.Append("\\n"); break;
|
||||
case '"':
|
||||
case '\\': _output.Append('\\'); _output.Append(c); break;
|
||||
default:
|
||||
_output.Append("\\u");
|
||||
_output.Append(((int)c).ToString("X4", NumberFormatInfo.InvariantInfo));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (runIndex != -1)
|
||||
{
|
||||
_output.Append(s, runIndex, s.Length - runIndex);
|
||||
}
|
||||
|
||||
_output.Append('\"');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
//http://fastjson.codeplex.com/
|
||||
//http://fastjson.codeplex.com/license
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NzbDrone.Common.Exceptron.fastJSON
|
||||
{
|
||||
internal class SafeDictionary<TKey, TValue>
|
||||
{
|
||||
private readonly object _Padlock = new object();
|
||||
private readonly Dictionary<TKey, TValue> _Dictionary = new Dictionary<TKey, TValue>();
|
||||
|
||||
|
||||
internal bool TryGetValue(TKey key, out TValue value)
|
||||
{
|
||||
return _Dictionary.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
internal TValue this[TKey key] => _Dictionary[key];
|
||||
|
||||
internal void Add(TKey key, TValue value)
|
||||
{
|
||||
lock (_Padlock)
|
||||
{
|
||||
if (_Dictionary.ContainsKey(key) == false)
|
||||
_Dictionary.Add(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||