1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-03-28 18:05:41 -04:00

Compare commits

...

155 Commits

Author SHA1 Message Date
Taloth Saldono
a7e368d27b Fixed btn tests. 2015-06-18 20:34:06 +02:00
Taloth Saldono
c72f9c8f27 Fixed: BTN will now use http/https for grabbing downloads as specified by the settings instead of by the feed. (Workaround for BTN https policy on mono) 2015-06-18 20:21:13 +02:00
Taloth Saldono
83ff02545e Prevent ProgressMessageTarget from ever reading the command from the database. 2015-06-04 19:15:47 +02:00
Mark McDowall
2d25c74758 Fixed: Premature cleanup of completed tasks cache causes tasks to get stuck till reboot. 2015-06-04 19:15:17 +02:00
Taloth Saldono
4d924688d8 Fixed: A bug caused way too much data being read during MediaInfo discovery (often the entire file).
Fixed: Further optimized the MediaInfo discovery to reduce the read data to a couple of dozen kbytes, but less detailed.
2015-06-03 22:14:36 +02:00
Taloth Saldono
3b805c4591 Fixed: Now also ratelimiting indexer api requests instead of only nzb downloads. 2015-06-03 22:14:35 +02:00
Taloth Saldono
e5278a0243 Added advanced torznab option to disable rageid lookups for trackers only supporting title queries. 2015-05-19 21:29:57 +02:00
Mark McDowall
7c246abc88 Fix torrent blacklisting when InfoHash is available 2015-05-17 16:20:51 -07:00
Mark McDowall
475f4244c4 Recent folders for add series now show clickable cursor 2015-05-17 16:10:10 -07:00
Mark McDowall
f57dea7f1f New: Store last 5 used folders from manual import 2015-05-17 16:07:27 -07:00
Taloth Saldono
f1a5261e0a Properly dispose filestream after getting mediainfo. 2015-05-17 18:48:00 +02:00
Taloth Saldono
fe5cb9503c Updated kickass url... again 2015-05-16 01:11:39 +02:00
Taloth
944a775625 Merge pull request #540 from larsjohnsen/source-misc-xbuild-support
Compilation: Misc changes to support XBuild
2015-05-15 19:34:46 +02:00
Lars Johnsen
c8c17bce7e Compilation: Misc changes to support XBuild 2015-05-15 19:07:52 +02:00
Mark McDowall
366e3ed0be Merge pull request #539 from larsjohnsen/source-case-inconistancies
Compilation: Fix case inconsistencies
2015-05-15 09:10:05 -07:00
Taloth
cef6eb7509 Merge pull request #544 from Sonarr/mediainfo-unicode
Integrated MediaInfo wrapper to be able to properly handle Unicode
2015-05-15 10:40:45 +02:00
Mark McDowall
f88e2e2b79 Fix tests 2015-05-14 18:35:13 -07:00
Mark McDowall
57bcc9f4c1 Don't filter excluded files twice 2015-05-14 18:33:44 -07:00
Mark McDowall
69dd1c6ec4 Test to make sure we scan files in root of series folder (no season folders) 2015-05-14 18:23:57 -07:00
Mark McDowall
761a106fa9 Use HTTPS for piwik when loading via HTTPS 2015-05-14 13:24:24 -07:00
Taloth Saldono
5cd2d71e6f Integrated MediaInfo wrapper to be able to properly handle Unicode on Linux. 2015-05-14 13:31:05 +02:00
Mark McDowall
96578ca59b Stricter rejection of series subfolders
Fixed: Exclude .@__thumb folders from series disk scans

Fixes #538
2015-05-13 21:13:13 -07:00
Mark McDowall
213f905767 Only make manual import cells clickable when previous steps have been done 2015-05-13 08:45:40 -07:00
Mark McDowall
9d980a8ac7 Manual Import sends progress messages 2015-05-13 08:05:23 -07:00
Mark McDowall
c4e1a732dd Remove Kodi specific settings from PHT Settings 2015-05-13 08:01:39 -07:00
Mark McDowall
9f73b2b7f0 DotSolutions update 2015-05-12 17:08:10 -07:00
Mark McDowall
149c149094 Select input for select series in manual import 2015-05-12 17:07:56 -07:00
Mark McDowall
ee224cb422 Modal Regions inherit from a common base 2015-05-12 16:57:30 -07:00
Mark McDowall
335be1c85d Interval for RSS is minutes 2015-05-12 14:40:52 -07:00
Lars Johnsen
a79fc94a54 Compilation: Fix case inconsistencies 2015-05-12 23:17:51 +02:00
Mark McDowall
c3acfe34fe Fixed: Exclude OS X Metadata files when scanning for files
Fixes #533
2015-05-12 11:05:11 -07:00
Taloth Saldono
6e7a2af86b Ignore unicode test for now, fails on tc. 2015-05-12 01:42:11 +02:00
Keivan Beigi
cce280d260 HashAlgorithm.ComputerHash isn't thread safe, 2015-05-11 16:07:09 -07:00
Taloth Saldono
852f97012f Fixed broken test. 2015-05-12 01:05:15 +02:00
Taloth Saldono
f221b00795 Kickass Verified Only flag no longer an Advanced option to increase visibility. 2015-05-12 00:30:49 +02:00
Taloth Saldono
429298c68c Fixed manual import of unknown episodes. 2015-05-12 00:30:47 +02:00
Taloth Saldono
af060d73cc Updated MediaInfo code for syno/linux. 2015-05-12 00:30:45 +02:00
Taloth Saldono
e98a174884 Fixed: Fetching multiple pages for kickass to get more releases on the recent/rss feed due to small page size. 2015-05-12 00:30:43 +02:00
Mark McDowall
92a23d0f8b Casing for button text 2015-05-11 10:04:55 -07:00
Mark McDowall
d5ba11bd51 Merge pull request #245 from Sonarr/skyhook-search
Use skyhook for searching
2015-05-10 17:33:22 -07:00
Mark McDowall
a8aac36379 Use skyhook for searching 2015-05-10 17:32:52 -07:00
Thirrian
23c6da4746 Title case for buttons 2015-05-10 11:07:04 +02:00
Taloth Saldono
cf9391a7a3 Updated Container to handle Singleton Implementations instead of Singleton Interfaces. 2015-05-09 21:36:48 +02:00
Taloth Saldono
d0bf539a73 Fixed: A season pack import taking a long time should no longer cause the download to be deleted prematurely. 2015-05-09 10:10:16 +02:00
Mark McDowall
95bd82778f Transform buttons to title case 2015-05-08 07:50:46 -07:00
Mark McDowall
b73413f189 Prefix relative dates with "in" where appropriate 2015-05-07 16:39:48 -07:00
Mark McDowall
df4604057e Releases instead of reports (but no results found) 2015-05-07 10:17:26 -07:00
Taloth Saldono
1e2ba691ed Don't run DownloadCompletedEvent if DownloadItem not Completed. 2015-05-05 21:53:20 +02:00
Taloth Saldono
6abda8adef New: Added HD4Free.xyz to Torznab presets since that site now supports it. 2015-05-05 21:49:16 +02:00
Mark McDowall
84128482f4 Its a good idea to remove testing elements before merging 2015-05-05 12:45:35 -07:00
Mark McDowall
a184021621 Merge pull request #244 from Thirrian/sort-exception-fix
Fix for #242
2015-05-05 09:44:50 -07:00
Thirrian
c4f8e44f55 Fix for #242
Missing comma!
2015-05-05 18:04:44 +02:00
Mark McDowall
b359e1c175 Merge pull request #215 from Sonarr/manual-import
Manual Import
2015-05-05 08:42:24 -07:00
Mark McDowall
6dd22e7dcb New: Manual Import episodes 2015-05-05 08:41:39 -07:00
Mark McDowall
29ca1bc9da Merge pull request #238 from Sonarr/blacklisting-v2
Improved Blacklisting
2015-05-05 08:40:36 -07:00
Mark McDowall
bc03ad2a18 Blacklisting torrents and using more info to evaluate matches
New: Blacklisting torrents manually
New: Details on why a release was blacklisted in the UI
New: Blacklist matching take into account indexer, size, date and name
2015-05-05 08:39:41 -07:00
Mark McDowall
14f49489a7 Merge pull request #243 from Thirrian/tiny-ui-fix
Move error div inside body tag
2015-05-05 08:38:07 -07:00
Mark McDowall
4356da039f Merge pull request #242 from Thirrian/sort-exception
Add sort key for series "A.D. The Bible Continues"
2015-05-05 08:37:49 -07:00
Mark McDowall
587aff602a Fixed: Don't delete downloads unless a file was imported 2015-05-05 08:37:11 -07:00
Mark McDowall
1275d8098d New: Limit grabs to 1 per second to reduce rapid API calls 2015-05-05 08:15:38 -07:00
Mark McDowall
0c6ca6971d Fixed: Do not replace a file unless it contains the same episodes 2015-05-05 07:29:38 -07:00
Thirrian
f5fde97f68 Move error div inside body tag 2015-05-05 14:13:22 +02:00
Thirrian
50dc4c4f3d Add sort key for series "A.D. The Bible Continues" 2015-05-05 13:55:50 +02:00
Taloth Saldono
c08d8252ff Fixed some tests. 2015-05-04 01:30:16 +02:00
Taloth Saldono
2a83088045 Changed the way the Database is registered with TinyIoC to make Logdb and future cachedb more accessible. 2015-05-04 00:50:10 +02:00
Taloth Saldono
4ca8178ca8 Add db name to Vacuum log message. 2015-05-03 20:52:07 +02:00
Mark McDowall
bb48491eb2 Merge pull request #230 from Chao-Man/develop
Fixed scrolling performance issues on Webkit based browsers.
2015-05-02 10:25:51 -07:00
Chao Man
0534fb4330 Fixed scrolling performance issues on Webkit based browsers. (Opera, Chrome, Safari)
Update theme.less

Rebased and added markus101's changes.
2015-05-02 18:40:05 +10:00
Mark McDowall
8b7eedf6f9 Actually make it lower case... 2015-05-02 01:23:04 -07:00
Mark McDowall
6ab629ea98 Partial updates for command updates 2015-05-01 22:03:20 -07:00
Taloth Saldono
97cbdfdc5c Fixed: Nzbget will now properly remove data from original directory if Remove option is enabled. (nzbToMedia transcoding) 2015-05-02 00:28:47 +02:00
Taloth Saldono
25c77711cd Log partial indexer response on parser error. 2015-05-01 23:51:31 +02:00
Mark McDowall
2e6cf2b7f6 Fixed: Parsing some anime releases with multiple absolute episode numbers 2015-04-30 07:08:55 -07:00
Mark McDowall
6592310f2b Permissions can cause OWIN port registration to fail 2015-04-28 07:31:22 -07:00
Mark McDowall
918fcac2aa Fixed: Generic SignalR messages no longer treated as errors 2015-04-28 07:28:05 -07:00
Mark McDowall
4b7ee3cb9e Fixed: Monitoring options not be applied when adding a new series to an empty root folder 2015-04-28 07:13:11 -07:00
Mark McDowall
27246de623 Fixed: Ignore @eaDir inside Series folders 2015-04-27 17:07:22 -07:00
Mark McDowall
b1a0e759ef Fixed: Long sets of required/ignored words would overflow the view in Manual Search 2015-04-27 16:57:38 -07:00
Mark McDowall
f90fdef50d Couple name fixes 2015-04-26 01:21:54 -07:00
Mark McDowall
702b4429ac Order provider based settings by name 2015-04-26 00:25:40 -07:00
Mark McDowall
4eff8d88d1 New: Show age in minutes when less than 2 hours old (manual search/history) 2015-04-26 00:11:23 -07:00
Mark McDowall
235a986679 Display names for Download clients 2015-04-25 09:25:33 -07:00
Mark McDowall
c3e0dbc173 Display names for Indexers 2015-04-25 09:25:18 -07:00
Mark McDowall
e296d94417 Set default Metadata name 2015-04-25 09:10:43 -07:00
Mark McDowall
0e865fff8c No longer titlecases notifications, indexers, etc 2015-04-25 09:03:07 -07:00
Mark McDowall
9a629c2fc6 Display names for Notifications 2015-04-25 09:02:17 -07:00
Mark McDowall
ecd941a6e5 Fixing scene mappings 2015-04-23 06:36:51 -07:00
Mark McDowall
2feb583e45 Show reload when already on updates page 2015-04-22 14:49:34 -07:00
Taloth Saldono
23daae05cc Remove invalid scene mappings. 2015-04-22 20:18:04 +02:00
Taloth Saldono
b4e8a39c2c New: Added Color-Impaired mode to UI settings. 2015-04-22 19:10:07 +02:00
Mark McDowall
2f7e3c1c3c Merge pull request #240 from Royal2000H/develop
Pushbullet settings typo
2015-04-22 09:21:13 -07:00
Mark McDowall
8a0e873eb5 Fixed: URL Base for favicon and Apple Touch icons 2015-04-22 07:52:19 -07:00
Mark McDowall
10214bff42 Cleanse some names 2015-04-22 07:52:07 -07:00
Roy Handelsman
1fc99fd24e Fixed: Pushbullet settings typo 2015-04-22 00:03:42 -04:00
Mark McDowall
e40508e5e9 Fixed: Don't save invalid scene mappings into database 2015-04-21 16:53:32 -07:00
Mark McDowall
65f1dbde00 Fixed: Torznab parsing when enclosure is magent link 2015-04-19 13:13:14 -07:00
Mark McDowall
4c9f13cb26 Fixed: Testing indexers, connections and download clients 2015-04-18 22:56:36 -07:00
Mark McDowall
f831dbd789 Merge pull request #237 from Sonarr/osx-package-execute
Set permissions on Sonarr.app (OS X)
2015-04-18 11:14:55 -07:00
Mark McDowall
f18ad21b48 Fixed: Set permissions on Sonarr.app (OS X) 2015-04-18 11:12:53 -07:00
Mark McDowall
61ae7dc189 Merge pull request #236 from Sonarr/db-locks
DB locking due to Progress Messaging
2015-04-18 11:09:16 -07:00
Mark McDowall
e304a615d0 Fixed: DB locking due to Progress Messaging 2015-04-18 11:08:07 -07:00
Mark McDowall
c12f16b6d3 Mapped Network Drive Validator
New: Prevent adding Mapped Network Drives when Running as a Windows Services
2015-04-15 23:50:19 -07:00
Mark McDowall
c43296ffe9 Merge pull request #231 from Sonarr/unmonitored-calendar
Unmonitored episodes on calendar
2015-04-14 20:37:02 -07:00
Mark McDowall
ab6233dd3f New: Option to show unmonitored episodes on calendar 2015-04-14 20:35:55 -07:00
Mark McDowall
2a4fd2bbde Fixed: Better error messaging when import fails due to inaccessible path 2015-04-14 20:35:07 -07:00
Mark McDowall
c3d15015fe Fixed: Table pagers show correct loading icon 2015-04-14 20:35:06 -07:00
Mark McDowall
f30e7bc701 jshint in WebStorm 10 2015-04-14 20:35:05 -07:00
Taloth Saldono
ee87537848 Response cookies not stored by default. 2015-04-13 23:44:24 +02:00
Taloth Saldono
d4532c3856 Fixed: MediaInfo now also works on linux with unicode filenames. 2015-04-11 13:28:37 +02:00
Taloth Saldono
20e40f73b3 Made optional resource properties nullable. 2015-04-11 09:15:15 +02:00
Taloth Saldono
923488bc02 Hard test on dev nzbget version as requested. 2015-04-11 09:07:35 +02:00
Taloth Saldono
b62d36bdbe Fixed: If Nzbget failed to add an nzb, Sonarr will try another but not blacklist it. 2015-04-11 09:03:35 +02:00
Mark McDowall
62f4fc5e58 Fixed: Some anime season 1 parsing 2015-04-10 07:32:34 -07:00
Mark McDowall
cfefed34fc API endpoint to parse a release title 2015-04-10 07:30:06 -07:00
Mark McDowall
c58d607349 Don't throw error when episode title matching doesn't find a match 2015-04-09 16:48:13 -07:00
Mark McDowall
f13a4b5aa5 New: Sort queue by series, episode and episode title 2015-04-08 16:43:51 -07:00
Mark McDowall
9cf575c096 New: Toggle selected on Wanted: Missing to change monitored status 2015-04-07 16:49:36 -07:00
Mark McDowall
2c52ac1a7b Fixed: Rename preview for Specials 2015-04-07 16:19:06 -07:00
Mark McDowall
7e0c833ad0 New: Show quality in dropdowns with best at top (same as profiles) 2015-04-07 16:18:56 -07:00
Mark McDowall
7f38617d76 Fixed: Wrap long release names in history details 2015-04-07 15:54:40 -07:00
Mark McDowall
8aa6969aee Fixed: Improved special episode parsing for multiple matching titles 2015-04-06 18:43:29 -07:00
Mark McDowall
0adea0ded6 Search all missing fixes
Fixed: Searching for missing episodes excludes unmonitored episodes
Fixed: Searching for missing episodes episodes with files
2015-04-06 17:05:12 -07:00
Taloth Saldono
ccfd66260d Fixed: BitMeTv cookie will now also be used for the fetching the torrent file. 2015-04-02 21:06:05 +02:00
Taloth Saldono
a6d2283be8 New: Added Advanced option to Nyaa to change query parameters for category and filter. 2015-04-02 19:52:38 +02:00
Taloth Saldono
b92cc6fb15 Fixed: No longer possible to add protocol to a Host field (that's what Url fields are for) 2015-04-02 19:52:37 +02:00
Taloth Saldono
f2ec02876b Fixed notification enable logic and test when On Upgrade is disabled. 2015-04-02 19:52:37 +02:00
Mark McDowall
7378a98e07 Fixed icon colours 2015-03-30 00:02:47 -07:00
Taloth
42a3ff2625 Merge pull request #206 from Sonarr/osxfullfsync
Changed sqlite to use full fsync on osx
2015-03-28 12:23:24 +01:00
Taloth Saldono
4448e87e28 Fixed: NzbGet development version no longer fails validation check. 2015-03-26 20:09:03 +01:00
Mark McDowall
90b047f0d4 Fixed: Searching for unmonitored anime episodes during season/all missing searches 2015-03-26 09:06:20 -07:00
Mark McDowall
adfaa00ce1 Toggle cell use spinForPromise 2015-03-26 08:03:31 -07:00
Mark McDowall
755a42ea45 Use cache to check for running or started commands 2015-03-25 16:47:05 -07:00
Mark McDowall
210524b51a Fixed: Scene numbered season searches when some episode weren't monitored 2015-03-25 16:47:04 -07:00
Mark McDowall
a1a91878ad New: Choose the latest season when adding a new series 2015-03-25 16:47:03 -07:00
Mark McDowall
216286db5e Fixed: navbar hover mobile styling 2015-03-25 16:47:01 -07:00
Taloth Saldono
061c40c8f4 It is Not an Error Message 2015-03-25 01:39:40 +01:00
Taloth Saldono
15eeb19cd5 New: Synology Media Indexer support in Connect. 2015-03-24 20:09:12 +01:00
Taloth Saldono
93c6047cd5 Added Nzbget version check for 12.0 or higher. 2015-03-23 20:56:35 +01:00
Taloth Saldono
db4746bef7 Failed DeleteStatus now only a Warning, also added null check to handle older NzbGet version. 2015-03-23 20:56:32 +01:00
Taloth Saldono
971e159fa4 Replaced a couple more NzbDrone with Sonarr. Left a couple that implied process name. 2015-03-23 20:56:28 +01:00
Mark McDowall
a4deea2333 Merge pull request #229 from BrendenCA/fixtypo
Fixed a typo
2015-03-22 21:37:44 -07:00
BrendenCA
6114952012 Fixed a typo 2015-03-23 09:33:53 +05:30
Taloth Saldono
fcc1439754 Ugly indexer release name cleaned up before sending to Sab. 2015-03-21 00:55:30 +01:00
Mark McDowall
06a2cb0de4 New: Restrict ports that Sonarr will allow for its webserver 2015-03-18 23:15:20 -07:00
Taloth Saldono
9dd66879a2 Fixed: Better handling for Remote NAS errors. 2015-03-19 01:17:52 +01:00
Taloth Saldono
5d03c94b26 Fixed typo 2015-03-19 01:17:51 +01:00
Mark McDowall
679455713e Fixed: sorting on episode list when new episodes are added during refresh 2015-03-18 16:14:44 -07:00
Mark McDowall
9aeda7aaba Fixed: Legitimate API redirects 2015-03-18 15:27:36 -07:00
Mark McDowall
ca8e16a5be Merge pull request #227 from c0unt0/develop
Allow startup on case sensitive file systems
2015-03-18 15:00:32 -07:00
Alex
52ec1cf1b1 Allow startup on case sensitive file systems
Fixed EXE_PATH to match NzbDrone.exe’s capitalisation.
2015-03-18 21:03:19 +00:00
Mark McDowall
1efb7446c9 Log full path when moving or copying 2015-03-18 13:51:21 -07:00
Mark McDowall
c5f2c2823e Separate log messages for hardlinking and copying 2015-03-18 13:18:34 -07:00
Mark McDowall
beb4aee4c9 Merge pull request #190 from Sonarr/command-queue
Command queue
2015-03-16 22:20:53 -07:00
Mark McDowall
638e3ca898 Command queue
New: Adding multiple series will queue them instead of running all at once
New: Slower scheduled tasks won't be block others from running
2015-03-16 22:07:02 -07:00
Taloth Saldono
dea58ed663 Fixed: Changed sqlite to use full fsync on osx to reduce the chance of corruption at the cost of some performance. 2015-02-18 19:28:10 +01:00
519 changed files with 7508 additions and 3327 deletions

View File

@@ -114,8 +114,8 @@ Function PackageMono()
get-childitem $outputFolderMono -File -Filter sqlite3.* -Recurse | foreach ($_) {remove-item $_.fullname}
get-childitem $outputFolderMono -File -Filter MediaInfo.* -Recurse | foreach ($_) {remove-item $_.fullname}
Write-Host "Adding MediaInfoDotNet.dll.config (for dllmap)"
Copy-Item "$sourceFolder\MediaInfoDotNet.dll.config" $outputFolderMono
Write-Host "Adding NzbDrone.Core.dll.config (for dllmap)"
Copy-Item "$sourceFolder\NzbDrone.Core\NzbDrone.Core.dll.config" $outputFolderMono
Write-Host Renaming NzbDrone.Console.exe to NzbDrone.exe
Get-ChildItem $outputFolderMono -File -Filter "NzbDrone.exe*" -Recurse | foreach ($_) {remove-item $_.fullname}
@@ -212,8 +212,8 @@ Function PackageTests()
CleanFolder $testPackageFolder $true
Write-Host "Adding MediaInfoDotNet.dll.config (for dllmap)"
Copy-Item "$sourceFolder\MediaInfoDotNet.dll.config" -Destination $testPackageFolder -Force
Write-Host "Adding NzbDrone.Core.dll.config (for dllmap)"
Copy-Item "$sourceFolder\NzbDrone.Core\NzbDrone.Core.dll.config" -Destination $testPackageFolder -Force
Write-Host "##teamcity[progressFinish 'Creating Test Package']"
}

View File

@@ -8,7 +8,7 @@ gulp.task('copyJs', function () {
return gulp.src(
[
paths.src.root + "polyfills.js",
paths.src.root + "JsLibraries\\handlebars.runtime.js",
paths.src.root + "JsLibraries/handlebars.runtime.js",
])
.pipe(cache('copyJs'))
.pipe(print())

View File

@@ -15,6 +15,7 @@ gulp.task('less', function () {
paths.src.root + 'AddSeries/addSeries.less',
paths.src.root + 'Calendar/calendar.less',
paths.src.root + 'Cells/cells.less',
paths.src.root + 'ManualImport/manualimport.less',
paths.src.root + 'Settings/settings.less',
paths.src.root + 'System/Logs/logs.less',
paths.src.root + 'System/Update/update.less',

View File

@@ -1 +1 @@
require('./gulp/gulpfile.js');
require('./gulp/gulpFile.js');

View File

@@ -4,7 +4,7 @@
DIR=$(cd "$(dirname "$0")"; pwd)
#change these values to match your app
EXE_PATH="$DIR/nzbdrone.exe"
EXE_PATH="$DIR/NzbDrone.exe"
APPNAME="Sonarr"
#set up environment

View File

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

View File

@@ -144,7 +144,7 @@
<PreBuildEvent>
</PreBuildEvent>
</PropertyGroup>
<Import Project="$(SolutionDir)\.nuget\nuget.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">

View File

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<dllmap os="osx" dll="MediaInfo.dll" target="libmediainfo.0.dylib"/>
<dllmap os="linux" dll="MediaInfo.dll" target="libmediainfo.so.0" />
<dllmap os="freebsd" dll="MediaInfo.dll" target="libmediainfo.so.0" />
<dllmap os="solaris" dll="MediaInfo.dll" target="libmediainfo.so.0.0.0" />
</configuration>

View File

@@ -116,14 +116,12 @@ namespace NzbDrone.Api.Test.MappingTests
profileResource.InjectTo<Profile>();
}
[Test]
public void should_map_tracked_command()
{
var profileResource = new ApplicationUpdateCommand();
profileResource.InjectTo<CommandResource>();
var commandResource = new CommandModel { Body = new ApplicationUpdateCommand() };
commandResource.InjectTo<CommandResource>();
}
}

View File

@@ -64,7 +64,7 @@
<HintPath>..\packages\Moq.4.0.10827\lib\NET40\Moq.dll</HintPath>
</Reference>
<Reference Include="Omu.ValueInjecter">
<HintPath>..\packages\valueinjecter.2.3.3\lib\net35\Omu.ValueInjecter.dll</HintPath>
<HintPath>..\packages\ValueInjecter.2.3.3\lib\net35\Omu.ValueInjecter.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
@@ -104,7 +104,7 @@
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\nuget.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
@@ -112,4 +112,4 @@
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>

View File

@@ -4,5 +4,5 @@
<package id="Moq" version="4.0.10827" />
<package id="NBuilder" version="3.0.1.1" targetFramework="net40" />
<package id="NUnit" version="2.6.3" targetFramework="net40" />
<package id="valueinjecter" version="2.3.3" targetFramework="net40" />
</packages>
<package id="ValueInjecter" version="2.3.3" targetFramework="net40" />
</packages>

View File

@@ -74,8 +74,8 @@ namespace NzbDrone.Api.Authentication
{
if (context.Request.IsApiRequest())
{
if ((context.Response.StatusCode == HttpStatusCode.SeeOther &&
context.Response.ContentType.Equals("text/html", StringComparison.InvariantCultureIgnoreCase)) ||
if ((context.Response.StatusCode == HttpStatusCode.SeeOther &&
context.Response.Headers["Location"].StartsWith("/login", StringComparison.InvariantCultureIgnoreCase)) ||
context.Response.StatusCode == HttpStatusCode.Unauthorized)
{
context.Response = new { Error = "Unauthorized" }.AsResponse(HttpStatusCode.Unauthorized);

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using NzbDrone.Api.REST;
using NzbDrone.Core.Qualities;
using NzbDrone.Api.Series;
using NzbDrone.Core.Indexers;
namespace NzbDrone.Api.Blacklist
{
@@ -13,6 +14,9 @@ namespace NzbDrone.Api.Blacklist
public string SourceTitle { get; set; }
public QualityModel Quality { get; set; }
public DateTime Date { get; set; }
public DownloadProtocol Protocol { get; set; }
public string Indexer { get; set; }
public string Message { get; set; }
public SeriesResource Series { get; set; }
}

View File

@@ -31,7 +31,7 @@ namespace NzbDrone.Api.Calendar
if (queryStart.HasValue) start = DateTime.Parse(queryStart.Value);
if (queryEnd.HasValue) end = DateTime.Parse(queryEnd.Value);
var episodes = _episodeService.EpisodesBetweenDates(start, end);
var episodes = _episodeService.EpisodesBetweenDates(start, end, false);
var icalCalendar = new iCalendar();
foreach (var episode in episodes.OrderBy(v => v.AirDateUtc.Value))

View File

@@ -23,14 +23,17 @@ namespace NzbDrone.Api.Calendar
{
var start = DateTime.Today;
var end = DateTime.Today.AddDays(2);
var includeUnmonitored = false;
var queryStart = Request.Query.Start;
var queryEnd = Request.Query.End;
var queryIncludeUnmonitored = Request.Query.Unmonitored;
if (queryStart.HasValue) start = DateTime.Parse(queryStart.Value);
if (queryEnd.HasValue) end = DateTime.Parse(queryEnd.Value);
if (queryIncludeUnmonitored.HasValue) includeUnmonitored = Convert.ToBoolean(queryIncludeUnmonitored.Value);
var resources = ToListResource(() => _episodeService.EpisodesBetweenDates(start, end));
var resources = ToListResource(() => _episodeService.EpisodesBetweenDates(start, end, includeUnmonitored));
return resources.OrderBy(e => e.AirDateUtc).ToList();
}

View File

@@ -53,11 +53,9 @@ namespace NzbDrone.Api.ClientSchema
}
}
return result;
return result.OrderBy(r => r.Order).ToList();
}
public static object ReadFormSchema(List<Field> fields, Type targetType, object defaults = null)
{
Ensure.That(targetType, () => targetType).IsNotNull();

View File

@@ -4,10 +4,9 @@ using System.Linq;
using NzbDrone.Api.Extensions;
using NzbDrone.Api.Mapping;
using NzbDrone.Api.Validation;
using NzbDrone.Common.Composition;
using NzbDrone.Common;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Commands.Tracking;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.ProgressMessaging;
using NzbDrone.SignalR;
@@ -15,58 +14,55 @@ using NzbDrone.SignalR;
namespace NzbDrone.Api.Commands
{
public class CommandModule : NzbDroneRestModuleWithSignalR<CommandResource, Command>, IHandle<CommandUpdatedEvent>
public class CommandModule : NzbDroneRestModuleWithSignalR<CommandResource, CommandModel>, IHandle<CommandUpdatedEvent>
{
private readonly ICommandExecutor _commandExecutor;
private readonly IContainer _container;
private readonly ITrackCommands _trackCommands;
private readonly IManageCommandQueue _commandQueueManager;
private readonly IServiceFactory _serviceFactory;
public CommandModule(ICommandExecutor commandExecutor,
public CommandModule(IManageCommandQueue commandQueueManager,
IBroadcastSignalRMessage signalRBroadcaster,
IContainer container,
ITrackCommands trackCommands)
IServiceFactory serviceFactory)
: base(signalRBroadcaster)
{
_commandExecutor = commandExecutor;
_container = container;
_trackCommands = trackCommands;
_commandQueueManager = commandQueueManager;
_serviceFactory = serviceFactory;
GetResourceById = GetCommand;
CreateResource = StartCommand;
GetResourceAll = GetAllCommands;
GetResourceAll = GetStartedCommands;
PostValidator.RuleFor(c => c.Name).NotBlank();
}
private CommandResource GetCommand(int id)
{
return _trackCommands.GetById(id).InjectTo<CommandResource>();
return _commandQueueManager.Get(id).InjectTo<CommandResource>();
}
private int StartCommand(CommandResource commandResource)
{
var commandType =
_container.GetImplementations(typeof(Command))
.Single(c => c.Name.Replace("Command", "")
.Equals(commandResource.Name, StringComparison.InvariantCultureIgnoreCase));
_serviceFactory.GetImplementations(typeof (Command))
.Single(c => c.Name.Replace("Command", "")
.Equals(commandResource.Name, StringComparison.InvariantCultureIgnoreCase));
dynamic command = Request.Body.FromJson(commandType);
command.Manual = true;
command.Trigger = CommandTrigger.Manual;
var trackedCommand = (Command)_commandExecutor.PublishCommandAsync(command);
var trackedCommand = _commandQueueManager.Push(command, CommandPriority.Normal, CommandTrigger.Manual);
return trackedCommand.Id;
}
private List<CommandResource> GetAllCommands()
private List<CommandResource> GetStartedCommands()
{
return ToListResource(_trackCommands.RunningCommands);
return ToListResource(_commandQueueManager.GetStarted());
}
public void Handle(CommandUpdatedEvent message)
{
if (message.Command.SendUpdatesToClient)
if (message.Command.Body.SendUpdatesToClient)
{
BroadcastResourceChange(ModelAction.Updated, message.Command.Id);
BroadcastResourceChange(ModelAction.Updated, message.Command.InjectTo<CommandResource>());
}
}
}

View File

@@ -1,6 +1,7 @@
using System;
using Newtonsoft.Json;
using NzbDrone.Api.REST;
using NzbDrone.Core.Messaging.Commands.Tracking;
using NzbDrone.Core.Messaging.Commands;
namespace NzbDrone.Api.Commands
{
@@ -8,11 +9,75 @@ namespace NzbDrone.Api.Commands
{
public String Name { get; set; }
public String Message { get; set; }
public DateTime StartedOn { get; set; }
public DateTime StateChangeTime { get; set; }
public Boolean SendUpdatesToClient { get; set; }
public CommandStatus State { get; set; }
public Command Body { get; set; }
public CommandPriority Priority { get; set; }
public CommandStatus Status { get; set; }
public DateTime Queued { get; set; }
public DateTime? Started { get; set; }
public DateTime? Ended { get; set; }
public TimeSpan? Duration { get; set; }
public string Exception { get; set; }
public CommandTrigger Trigger { get; set; }
[JsonIgnore]
public string CompletionMessage { get; set; }
//Legacy
public CommandStatus State
{
get
{
return Status;
}
set { }
}
public Boolean Manual
{
get
{
return Trigger == CommandTrigger.Manual;
}
set { }
}
public DateTime StartedOn
{
get
{
return Queued;
}
set { }
}
public DateTime? StateChangeTime
{
get
{
if (Started.HasValue) return Started.Value;
return Ended;
}
set { }
}
public Boolean SendUpdatesToClient
{
get
{
if (Body != null) return Body.SendUpdatesToClient;
return false;
}
set { }
}
public DateTime? LastExecutionTime { get; set; }
public Boolean Manual { get; set; }
}
}
}

View File

@@ -7,16 +7,19 @@ namespace NzbDrone.Api.Config
{
public class DownloadClientConfigModule : NzbDroneConfigModule<DownloadClientConfigResource>
{
public DownloadClientConfigModule(IConfigService configService, RootFolderValidator rootFolderValidator, PathExistsValidator pathExistsValidator)
public DownloadClientConfigModule(IConfigService configService,
RootFolderValidator rootFolderValidator,
PathExistsValidator pathExistsValidator,
MappedNetworkDriveValidator mappedNetworkDriveValidator)
: base(configService)
{
SharedValidator.RuleFor(c => c.DownloadedEpisodesFolder)
.Cascade(CascadeMode.StopOnFirstFailure)
.IsValidPath()
.SetValidator(rootFolderValidator)
.SetValidator(mappedNetworkDriveValidator)
.SetValidator(pathExistsValidator)
.When(c => !String.IsNullOrWhiteSpace(c.DownloadedEpisodesFolder));
}
}
}

View File

@@ -14,5 +14,7 @@ namespace NzbDrone.Api.Config
public String LongDateFormat { get; set; }
public String TimeFormat { get; set; }
public Boolean ShowRelativeDates { get; set; }
public Boolean EnableColorImpairedMode { get; set; }
}
}

View File

@@ -24,7 +24,6 @@ namespace NzbDrone.Api.Episodes
public Nullable<Int32> SceneAbsoluteEpisodeNumber { get; set; }
public Nullable<Int32> SceneEpisodeNumber { get; set; }
public Nullable<Int32> SceneSeasonNumber { get; set; }
public Int32 TvDbEpisodeId { get; set; }
public DateTime? EndTime { get; set; }
public DateTime? GrabDate { get; set; }
public String SeriesTitle { get; set; }

View File

@@ -1,19 +1,31 @@
using System;
using System.IO;
using System.Linq;
using Nancy;
using NzbDrone.Api.Extensions;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.MediaFiles;
namespace NzbDrone.Api.FileSystem
{
public class FileSystemModule : NzbDroneApiModule
{
private readonly IFileSystemLookupService _fileSystemLookupService;
private readonly IDiskProvider _diskProvider;
private readonly IDiskScanService _diskScanService;
public FileSystemModule(IFileSystemLookupService fileSystemLookupService)
public FileSystemModule(IFileSystemLookupService fileSystemLookupService,
IDiskProvider diskProvider,
IDiskScanService diskScanService)
: base("/filesystem")
{
_fileSystemLookupService = fileSystemLookupService;
_diskProvider = diskProvider;
_diskScanService = diskScanService;
Get["/"] = x => GetContents();
Get["/type"] = x => GetEntityType();
Get["/mediafiles"] = x => GetMediaFiles();
}
private Response GetContents()
@@ -29,5 +41,36 @@ namespace NzbDrone.Api.FileSystem
return _fileSystemLookupService.LookupContents((string)pathQuery.Value, includeFiles).AsResponse();
}
private Response GetEntityType()
{
var pathQuery = Request.Query.path;
var path = (string)pathQuery.Value;
if (_diskProvider.FileExists(path))
{
return new { type = "file" }.AsResponse();
}
//Return folder even if it doesn't exist on disk to avoid leaking anything from the UI about the underlying system
return new { type = "folder" }.AsResponse();
}
private Response GetMediaFiles()
{
var pathQuery = Request.Query.path;
var path = (string)pathQuery.Value;
if (!_diskProvider.FolderExists(path))
{
return new string[0].AsResponse();
}
return _diskScanService.GetVideoFiles(path).Select(f => new {
Path = f,
RelativePath = path.GetRelativePath(f),
Name = Path.GetFileName(f)
}).AsResponse();
}
}
}

View File

@@ -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("(?<=(?:href|src)=\").*?(css|js)(?=\")", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex ReplaceRegex = new Regex("(?<=(?:href|src)=\").*?(css|js|png|ico|ics)(?=\")", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static String API_KEY;
private static String URL_BASE;

View File

@@ -18,7 +18,6 @@ namespace NzbDrone.Api.History
public Boolean QualityCutoffNotMet { get; set; }
public DateTime Date { get; set; }
public string Indexer { get; set; }
public string NzbInfoUrl { get; set; }
public string ReleaseGroup { get; set; }
public string DownloadId { get; set; }

View File

@@ -43,10 +43,12 @@ namespace NzbDrone.Api.Indexers
_prioritizeDownloadDecision = prioritizeDownloadDecision;
_downloadService = downloadService;
_logger = logger;
GetResourceAll = GetReleases;
Post["/"] = x => DownloadRelease(this.Bind<ReleaseResource>());
PostValidator.RuleFor(s => s.DownloadAllowed).Equal(true);
PostValidator.RuleFor(s => s.Guid).NotEmpty();
_remoteEpisodeCache = cacheManager.GetCache<RemoteEpisode>(GetType(), "remoteEpisodes");
}

View File

@@ -0,0 +1,40 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.MediaFiles.EpisodeImport.Manual;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Api.ManualImport
{
public class ManualImportModule : NzbDroneRestModule<ManualImportResource>
{
private readonly IManualImportService _manualImportService;
public ManualImportModule(IManualImportService manualImportService)
: base("/manualimport")
{
_manualImportService = manualImportService;
GetResourceAll = GetMediaFiles;
}
private List<ManualImportResource> GetMediaFiles()
{
var folderQuery = Request.Query.folder;
var folder = (string)folderQuery.Value;
var downloadIdQuery = Request.Query.downloadId;
var downloadId = (string)downloadIdQuery.Value;
return ToListResource(_manualImportService.GetMediaFiles(folder, downloadId)).Select(AddQualityWeight).ToList();
}
private ManualImportResource AddQualityWeight(ManualImportResource item)
{
item.QualityWeight = Quality.DefaultQualityDefinitions.Single(q => q.Quality == item.Quality.Quality).Weight;
item.QualityWeight += item.Quality.Revision.Real * 10;
item.QualityWeight += item.Quality.Revision.Version;
return item;
}
}
}

View File

@@ -0,0 +1,32 @@
using System.Collections.Generic;
using NzbDrone.Api.Episodes;
using NzbDrone.Api.REST;
using NzbDrone.Api.Series;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Api.ManualImport
{
public class ManualImportResource : RestResource
{
public string Path { get; set; }
public string RelativePath { get; set; }
public string Name { get; set; }
public long Size { get; set; }
public SeriesResource Series { get; set; }
public int? SeasonNumber { get; set; }
public List<EpisodeResource> Episodes { get; set; }
public QualityModel Quality { get; set; }
public int QualityWeight { get; set; }
public string DownloadId { get; set; }
public IEnumerable<Rejection> Rejections { get; set; }
public int Id
{
get
{
return Path.GetHashCode();
}
}
}
}

View File

@@ -5,11 +5,11 @@ namespace NzbDrone.Api.Notifications
{
public class NotificationResource : ProviderResource
{
public String Link { get; set; }
public Boolean OnGrab { get; set; }
public Boolean OnDownload { get; set; }
public Boolean OnUpgrade { get; set; }
public String TestCommand { get; set; }
public HashSet<Int32> Tags { get; set; }
public string Link { get; set; }
public bool OnGrab { get; set; }
public bool OnDownload { get; set; }
public bool OnUpgrade { get; set; }
public string TestCommand { get; set; }
public HashSet<int> Tags { get; set; }
}
}

View File

@@ -100,6 +100,10 @@
<Compile Include="Extensions\AccessControlHeaders.cs" />
<Compile Include="Extensions\Pipelines\CorsPipeline.cs" />
<Compile Include="Frontend\Mappers\LoginHtmlMapper.cs" />
<Compile Include="Parse\ParseModule.cs" />
<Compile Include="Parse\ParseResource.cs" />
<Compile Include="ManualImport\ManualImportModule.cs" />
<Compile Include="ManualImport\ManualImportResource.cs" />
<Compile Include="Profiles\Delay\DelayProfileModule.cs" />
<Compile Include="Profiles\Delay\DelayProfileResource.cs" />
<Compile Include="Profiles\Delay\DelayProfileValidator.cs" />
@@ -260,7 +264,7 @@
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\nuget.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
@@ -268,4 +272,4 @@
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>

View File

@@ -0,0 +1,49 @@
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Api.Parse
{
public class ParseModule : NzbDroneRestModule<ParseResource>
{
private readonly IParsingService _parsingService;
public ParseModule(IParsingService parsingService)
{
_parsingService = parsingService;
GetResourceSingle = Parse;
}
private ParseResource Parse()
{
var title = Request.Query.Title.Value;
var parsedEpisodeInfo = Parser.ParseTitle(title);
if (parsedEpisodeInfo == null)
{
return null;
}
var remoteEpisode = _parsingService.Map(parsedEpisodeInfo);
if (remoteEpisode == null)
{
remoteEpisode = new RemoteEpisode
{
ParsedEpisodeInfo = parsedEpisodeInfo
};
return new ParseResource
{
Title = title,
ParsedEpisodeInfo = parsedEpisodeInfo
};
}
var resource = ToResource(remoteEpisode);
resource.Title = title;
return resource;
}
}
}

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using NzbDrone.Api.Episodes;
using NzbDrone.Api.REST;
using NzbDrone.Api.Series;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Api.Parse
{
public class ParseResource : RestResource
{
public string Title { get; set; }
public ParsedEpisodeInfo ParsedEpisodeInfo { get; set; }
public SeriesResource Series { get; set; }
public List<EpisodeResource> Episodes { get; set; }
}
}

View File

@@ -55,9 +55,9 @@ namespace NzbDrone.Api
private List<TProviderResource> GetAll()
{
var providerDefinitions = _providerFactory.All();
var providerDefinitions = _providerFactory.All().OrderBy(p => p.ImplementationName);
var result = new List<TProviderResource>(providerDefinitions.Count);
var result = new List<TProviderResource>(providerDefinitions.Count());
foreach (var definition in providerDefinitions)
{
@@ -69,14 +69,17 @@ namespace NzbDrone.Api
result.Add(providerResource);
}
return result;
return result.OrderBy(p => p.Name).ToList();
}
private int CreateProvider(TProviderResource providerResource)
{
var providerDefinition = GetDefinition(providerResource, false);
Test(providerDefinition, false);
if (providerDefinition.Enable)
{
Test(providerDefinition, false);
}
providerDefinition = _providerFactory.Create(providerDefinition);
@@ -87,7 +90,10 @@ namespace NzbDrone.Api
{
var providerDefinition = GetDefinition(providerResource, false);
Test(providerDefinition, false);
if (providerDefinition.Enable)
{
Test(providerDefinition, false);
}
_providerFactory.Update(providerDefinition);
}
@@ -118,7 +124,7 @@ namespace NzbDrone.Api
private Response GetTemplates()
{
var defaultDefinitions = _providerFactory.GetDefaultDefinitions().ToList();
var defaultDefinitions = _providerFactory.GetDefaultDefinitions().OrderBy(p => p.ImplementationName).ToList();
var result = new List<TProviderResource>(defaultDefinitions.Count());
@@ -166,8 +172,6 @@ namespace NzbDrone.Api
protected virtual void Test(TProviderDefinition definition, bool includeWarnings)
{
if (!definition.Enable) return;
var validationResult = _providerFactory.Test(definition);
VerifyValidationResult(validationResult, includeWarnings);

View File

@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using NzbDrone.Api.ClientSchema;
using NzbDrone.Api.REST;
@@ -7,11 +6,12 @@ namespace NzbDrone.Api
{
public class ProviderResource : RestResource
{
public String Name { get; set; }
public string Name { get; set; }
public List<Field> Fields { get; set; }
public String Implementation { get; set; }
public String ConfigContract { get; set; }
public String InfoLink { get; set; }
public string ImplementationName { get; set; }
public string Implementation { get; set; }
public string ConfigContract { get; set; }
public string InfoLink { get; set; }
public List<ProviderResource> Presets { get; set; }
}

View File

@@ -11,7 +11,9 @@ namespace NzbDrone.Api.RemotePathMappings
{
private readonly IRemotePathMappingService _remotePathMappingService;
public RemotePathMappingModule(IRemotePathMappingService remotePathMappingService, PathExistsValidator pathExistsValidator)
public RemotePathMappingModule(IRemotePathMappingService remotePathMappingService,
PathExistsValidator pathExistsValidator,
MappedNetworkDriveValidator mappedNetworkDriveValidator)
{
_remotePathMappingService = remotePathMappingService;
@@ -31,6 +33,7 @@ namespace NzbDrone.Api.RemotePathMappings
SharedValidator.RuleFor(c => c.LocalPath)
.Cascade(CascadeMode.StopOnFirstFailure)
.IsValidPath()
.SetValidator(mappedNetworkDriveValidator)
.SetValidator(pathExistsValidator);
}

View File

@@ -15,7 +15,8 @@ namespace NzbDrone.Api.RootFolders
IBroadcastSignalRMessage signalRBroadcaster,
RootFolderValidator rootFolderValidator,
PathExistsValidator pathExistsValidator,
DroneFactoryValidator droneFactoryValidator)
DroneFactoryValidator droneFactoryValidator,
MappedNetworkDriveValidator mappedNetworkDriveValidator)
: base(signalRBroadcaster)
{
_rootFolderService = rootFolderService;
@@ -29,8 +30,9 @@ namespace NzbDrone.Api.RootFolders
.Cascade(CascadeMode.StopOnFirstFailure)
.IsValidPath()
.SetValidator(rootFolderValidator)
.SetValidator(pathExistsValidator)
.SetValidator(droneFactoryValidator);
.SetValidator(droneFactoryValidator)
.SetValidator(mappedNetworkDriveValidator)
.SetValidator(pathExistsValidator);
}
private RootFolderResource GetRootFolder(int id)

View File

@@ -13,7 +13,7 @@ namespace NzbDrone.Api.Series
private readonly ISearchForNewSeries _searchProxy;
public SeriesLookupModule(ISearchForNewSeries searchProxy)
: base("/Series/lookup")
: base("/series/lookup")
{
_searchProxy = searchProxy;
Get["/"] = x => Search();

View File

@@ -28,9 +28,9 @@ namespace NzbDrone.Api.Series
}
}
public Int32 EpisodeCount { get; set; }
public Int32 EpisodeFileCount { get; set; }
public Int64 SizeOnDisk { get; set; }
public Int32? EpisodeCount { get; set; }
public Int32? EpisodeFileCount { get; set; }
public Int64? SizeOnDisk { get; set; }
public SeriesStatusType Status { get; set; }
public String ProfileName { get; set; }
public String Overview { get; set; }

View File

@@ -15,14 +15,14 @@ namespace NzbDrone.Api.System
private readonly IRuntimeInfo _runtimeInfo;
private readonly IRouteCacheProvider _routeCacheProvider;
private readonly IConfigFileProvider _configFileProvider;
private readonly IDatabase _database;
private readonly IMainDatabase _database;
private readonly ILifecycleService _lifecycleService;
public SystemModule(IAppFolderInfo appFolderInfo,
IRuntimeInfo runtimeInfo,
IRouteCacheProvider routeCacheProvider,
IConfigFileProvider configFileProvider,
IDatabase database,
IMainDatabase database,
ILifecycleService lifecycleService)
: base("system")
{

View File

@@ -8,4 +8,4 @@
<package id="Newtonsoft.Json" version="6.0.6" targetFramework="net40" />
<package id="NLog" version="2.1.0" targetFramework="net40" />
<package id="ValueInjecter" version="2.3.3" targetFramework="net40" />
</packages>
</packages>

View File

@@ -12,30 +12,43 @@ using NzbDrone.Host;
using NzbDrone.Test.Common;
using FluentAssertions;
using System.Linq;
using NzbDrone.Common.Composition;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Download.TrackedDownloads;
namespace NzbDrone.App.Test
{
[TestFixture]
public class ContainerFixture : TestBase
{
StartupContext args = new StartupContext("first", "second");
private IContainer _container;
[SetUp]
public void SetUp()
{
var args = new StartupContext("first", "second");
_container = MainAppContainerBuilder.BuildContainer(args);
_container.Register<IMainDatabase>(new MainDatabase(null));
}
[Test]
public void should_be_able_to_resolve_indexers()
{
MainAppContainerBuilder.BuildContainer(args).Resolve<IEnumerable<IIndexer>>().Should().NotBeEmpty();
_container.Resolve<IEnumerable<IIndexer>>().Should().NotBeEmpty();
}
[Test]
public void should_be_able_to_resolve_downloadclients()
{
MainAppContainerBuilder.BuildContainer(args).Resolve<IEnumerable<IDownloadClient>>().Should().NotBeEmpty();
_container.Resolve<IEnumerable<IDownloadClient>>().Should().NotBeEmpty();
}
[Test]
public void container_should_inject_itself()
{
var factory = MainAppContainerBuilder.BuildContainer(args).Resolve<IServiceFactory>();
var factory = _container.Resolve<IServiceFactory>();
factory.Build<IIndexerFactory>().Should().NotBeNull();
}
@@ -44,22 +57,36 @@ namespace NzbDrone.App.Test
public void should_resolve_command_executor_by_name()
{
var genericExecutor = typeof(IExecute<>).MakeGenericType(typeof(RssSyncCommand));
var container = MainAppContainerBuilder.BuildContainer(args);
var executor = container.Resolve(genericExecutor);
var executor = _container.Resolve(genericExecutor);
executor.Should().NotBeNull();
executor.Should().BeAssignableTo<IExecute<RssSyncCommand>>();
}
[Test]
[Ignore("need to fix this at some point")]
public void should_return_same_instance_of_singletons()
{
var container = MainAppContainerBuilder.BuildContainer(args);
var first = _container.ResolveAll<IHandle<ApplicationShutdownRequested>>().OfType<Scheduler>().Single();
var second = _container.ResolveAll<IHandle<ApplicationShutdownRequested>>().OfType<Scheduler>().Single();
var first = container.ResolveAll<IHandle<ApplicationShutdownRequested>>().OfType<Scheduler>().Single();
var second = container.ResolveAll<IHandle<ApplicationShutdownRequested>>().OfType<Scheduler>().Single();
first.Should().BeSameAs(second);
}
[Test]
public void should_return_same_instance_of_singletons_by_different_same_interface()
{
var first = _container.ResolveAll<IHandle<EpisodeGrabbedEvent>>().OfType<DownloadMonitoringService>().Single();
var second = _container.ResolveAll<IHandle<EpisodeGrabbedEvent>>().OfType<DownloadMonitoringService>().Single();
first.Should().BeSameAs(second);
}
[Test]
public void should_return_same_instance_of_singletons_by_different_interfaces()
{
var first = _container.ResolveAll<IHandle<EpisodeGrabbedEvent>>().OfType<DownloadMonitoringService>().Single();
var second = (DownloadMonitoringService)_container.Resolve<IExecute<CheckForFinishedDownloadCommand>>();
first.Should().BeSameAs(second);
}

View File

@@ -104,10 +104,16 @@
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\nuget.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
<PropertyGroup>
<PostBuildEvent>xcopy /s /y "$(SolutionDir)\..\_output\NzbDrone.Mono.*" "$(TargetDir)"
xcopy /s /y "$(SolutionDir)\..\_output\NzbDrone.Windows.*" "$(TargetDir)"</PostBuildEvent>
<PostBuildEvent Condition="('$(OS)' == 'Windows_NT')">
xcopy /s /y "$(SolutionDir)\..\_output\NzbDrone.Mono.*" "$(TargetDir)"
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)
</PostBuildEvent>
</PropertyGroup>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
@@ -116,4 +122,4 @@ xcopy /s /y "$(SolutionDir)\..\_output\NzbDrone.Windows.*" "$(TargetDir)"</Pos
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>

View File

@@ -4,9 +4,12 @@ using System.IO;
using System.Net;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Http;
using NzbDrone.Test.Common;
using NzbDrone.Test.Common.Categories;
using NLog;
using NzbDrone.Common.TPL;
namespace NzbDrone.Common.Test.Http
{
@@ -14,6 +17,13 @@ namespace NzbDrone.Common.Test.Http
[IntegrationTest]
public class HttpClientFixture : TestBase<HttpClient>
{
[SetUp]
public void SetUp()
{
Mocker.SetConstant<ICacheManager>(Mocker.Resolve<CacheManager>());
Mocker.SetConstant<IRateLimitService>(Mocker.Resolve<RateLimitService>());
}
[Test]
public void should_execute_simple_get()
{
@@ -100,7 +110,6 @@ namespace NzbDrone.Common.Test.Http
response.Resource.Headers[header].ToString().Should().Be(value);
}
[Test]
public void should_not_download_file_with_error()
{
@@ -111,7 +120,102 @@ namespace NzbDrone.Common.Test.Http
File.Exists(file).Should().BeFalse();
ExceptionVerification.ExpectedWarns(1);
}
[Test]
public void should_send_cookie()
{
var request = new HttpRequest("http://eu.httpbin.org/get");
request.AddCookie("my", "cookie");
var response = Subject.Get<HttpBinResource>(request);
response.Resource.Headers.Should().ContainKey("Cookie");
var cookie = response.Resource.Headers["Cookie"].ToString();
cookie.Should().Contain("my=cookie");
}
public void GivenOldCookie()
{
var oldRequest = new HttpRequest("http://eu.httpbin.org/get");
oldRequest.AddCookie("my", "cookie");
var oldClient = new HttpClient(Mocker.Resolve<ICacheManager>(), Mocker.Resolve<IRateLimitService>(), Mocker.Resolve<Logger>());
oldClient.Should().NotBeSameAs(Subject);
var oldResponse = oldClient.Get<HttpBinResource>(oldRequest);
oldResponse.Resource.Headers.Should().ContainKey("Cookie");
}
[Test]
public void should_preserve_cookie_during_session()
{
GivenOldCookie();
var request = new HttpRequest("http://eu.httpbin.org/get");
var response = Subject.Get<HttpBinResource>(request);
response.Resource.Headers.Should().ContainKey("Cookie");
var cookie = response.Resource.Headers["Cookie"].ToString();
cookie.Should().Contain("my=cookie");
}
[Test]
public void should_not_send_cookie_to_other_host()
{
GivenOldCookie();
var request = new HttpRequest("http://httpbin.org/get");
var response = Subject.Get<HttpBinResource>(request);
response.Resource.Headers.Should().NotContainKey("Cookie");
}
[Test]
public void should_not_store_response_cookie()
{
var requestSet = new HttpRequest("http://eu.httpbin.org/cookies/set?my=cookie");
requestSet.AllowAutoRedirect = false;
var responseSet = Subject.Get(requestSet);
var request = new HttpRequest("http://eu.httpbin.org/get");
var response = Subject.Get<HttpBinResource>(request);
response.Resource.Headers.Should().NotContainKey("Cookie");
ExceptionVerification.IgnoreErrors();
}
[Test]
public void should_store_response_cookie()
{
var requestSet = new HttpRequest("http://eu.httpbin.org/cookies/set?my=cookie");
requestSet.AllowAutoRedirect = false;
requestSet.StoreResponseCookie = true;
var responseSet = Subject.Get(requestSet);
var request = new HttpRequest("http://eu.httpbin.org/get");
var response = Subject.Get<HttpBinResource>(request);
response.Resource.Headers.Should().ContainKey("Cookie");
var cookie = response.Resource.Headers["Cookie"].ToString();
cookie.Should().Contain("my=cookie");
ExceptionVerification.IgnoreErrors();
}
}

View File

@@ -90,6 +90,7 @@
<Compile Include="ServiceFactoryFixture.cs" />
<Compile Include="ServiceProviderTests.cs" />
<Compile Include="TPLTests\DebouncerFixture.cs" />
<Compile Include="TPLTests\RateLimitServiceFixture.cs" />
<Compile Include="WebClientTests.cs" />
</ItemGroup>
<ItemGroup>
@@ -111,6 +112,14 @@
<Project>{95C11A9E-56ED-456A-8447-2C89C1139266}</Project>
<Name>NzbDrone.Host</Name>
</ProjectReference>
<ProjectReference Include="..\NzbDrone.Mono\NzbDrone.Mono.csproj">
<Project>{15ad7579-a314-4626-b556-663f51d97cd1}</Project>
<Name>NzbDrone.Mono</Name>
</ProjectReference>
<ProjectReference Include="..\NzbDrone.Windows\NzbDrone.Windows.csproj">
<Project>{911284d3-f130-459e-836c-2430b6fbf21d}</Project>
<Name>NzbDrone.Windows</Name>
</ProjectReference>
<ProjectReference Include="..\NzbDrone\NzbDrone.csproj">
<Project>{D12F7F2F-8A3C-415F-88FA-6DD061A84869}</Project>
<Name>NzbDrone</Name>
@@ -131,7 +140,7 @@
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\nuget.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">

View File

@@ -2,6 +2,7 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Host;
@@ -16,11 +17,12 @@ namespace NzbDrone.Common.Test
public void event_handlers_should_be_unique()
{
var container = MainAppContainerBuilder.BuildContainer(new StartupContext());
container.Register<IMainDatabase>(new MainDatabase(null));
container.Resolve<IAppFolderFactory>().Register();
Mocker.SetConstant(container);
var handlers = Subject.BuildAll<IHandle<ApplicationShutdownRequested>>()
var handlers = Subject.BuildAll<IHandle<ApplicationStartedEvent>>()
.Select(c => c.GetType().FullName);
handlers.Should().OnlyHaveUniqueItems();

View File

@@ -40,7 +40,7 @@ namespace NzbDrone.Common.Test.TPLTests
}
[Test]
public void should_throttle_cals()
public void should_throttle_calls()
{
var counter = new Counter();
var debounceFunction = new Debouncer(counter.Hit, TimeSpan.FromMilliseconds(50));
@@ -64,6 +64,62 @@ namespace NzbDrone.Common.Test.TPLTests
}
[Test]
public void should_hold_the_call_while_paused()
{
var counter = new Counter();
var debounceFunction = new Debouncer(counter.Hit, TimeSpan.FromMilliseconds(50));
debounceFunction.Pause();
debounceFunction.Execute();
debounceFunction.Execute();
Thread.Sleep(100);
counter.Count.Should().Be(0);
debounceFunction.Execute();
debounceFunction.Execute();
Thread.Sleep(100);
counter.Count.Should().Be(0);
debounceFunction.Resume();
Thread.Sleep(20);
counter.Count.Should().Be(0);
Thread.Sleep(100);
counter.Count.Should().Be(1);
}
[Test]
public void should_handle_pause_reentrancy()
{
var counter = new Counter();
var debounceFunction = new Debouncer(counter.Hit, TimeSpan.FromMilliseconds(50));
debounceFunction.Pause();
debounceFunction.Pause();
debounceFunction.Execute();
debounceFunction.Execute();
debounceFunction.Resume();
Thread.Sleep(100);
counter.Count.Should().Be(0);
debounceFunction.Resume();
Thread.Sleep(100);
counter.Count.Should().Be(1);
}
}
}

View File

@@ -0,0 +1,94 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using NUnit.Framework;
using NzbDrone.Common.Cache;
using NzbDrone.Common.TPL;
using NzbDrone.Test.Common;
using FluentAssertions;
namespace NzbDrone.Common.Test.TPLTests
{
[TestFixture]
public class RateLimitServiceFixture : TestBase<RateLimitService>
{
private DateTime _epoch;
[SetUp]
public void SetUp()
{
// Make sure it's there so we don't affect measurements.
Subject.GetType();
_epoch = DateTime.UtcNow;
}
private ConcurrentDictionary<string, DateTime> GetRateLimitStore()
{
var cache = Mocker.Resolve<ICacheManager>()
.GetCache<ConcurrentDictionary<string, DateTime>>(typeof(RateLimitService), "rateLimitStore");
return cache.Get("rateLimitStore", () => new ConcurrentDictionary<string, DateTime>());
}
private void GivenExisting(string key, DateTime dateTime)
{
GetRateLimitStore().AddOrUpdate(key, dateTime, (s, i) => dateTime);
}
[Test]
public void should_not_delay_if_unset()
{
var watch = Stopwatch.StartNew();
Subject.WaitAndPulse("me", TimeSpan.FromMilliseconds(100));
watch.Stop();
watch.ElapsedMilliseconds.Should().BeLessThan(100);
}
[Test]
public void should_not_delay_unrelated_key()
{
GivenExisting("other", _epoch + TimeSpan.FromMilliseconds(200));
var watch = Stopwatch.StartNew();
Subject.WaitAndPulse("me", TimeSpan.FromMilliseconds(100));
watch.Stop();
watch.ElapsedMilliseconds.Should().BeLessThan(50);
}
[Test]
public void should_wait_for_existing()
{
GivenExisting("me", _epoch + TimeSpan.FromMilliseconds(200));
var watch = Stopwatch.StartNew();
Subject.WaitAndPulse("me", TimeSpan.FromMilliseconds(400));
watch.Stop();
watch.ElapsedMilliseconds.Should().BeInRange(195, 250);
}
[Test]
public void should_extend_delay()
{
GivenExisting("me", _epoch + TimeSpan.FromMilliseconds(200));
Subject.WaitAndPulse("me", TimeSpan.FromMilliseconds(100));
(GetRateLimitStore()["me"] - _epoch).Should().BeGreaterOrEqualTo(TimeSpan.FromMilliseconds(300));
}
[Test]
public void should_add_delay()
{
Subject.WaitAndPulse("me", TimeSpan.FromMilliseconds(100));
(GetRateLimitStore()["me"] - _epoch).Should().BeGreaterOrEqualTo(TimeSpan.FromMilliseconds(100));
}
}
}

View File

@@ -100,6 +100,14 @@ namespace NzbDrone.Common.Cache
_store.Clear();
}
public void ClearExpired()
{
foreach (var cached in _store.Where(c => c.Value.IsExpired()))
{
Remove(cached.Key);
}
}
public ICollection<T> Values
{
get

View File

@@ -6,6 +6,7 @@ namespace NzbDrone.Common.Cache
public interface ICached
{
void Clear();
void ClearExpired();
void Remove(string key);
int Count { get; }
}

View File

@@ -51,7 +51,9 @@ namespace NzbDrone.Common.Composition
public void RegisterSingleton(Type service, Type implementation)
{
_container.Register(service, implementation).AsSingleton();
var factory = CreateSingletonImplementationFactory(implementation);
_container.Register(service, factory);
}
public IEnumerable<T> ResolveAll<T>() where T : class
@@ -59,9 +61,23 @@ namespace NzbDrone.Common.Composition
return _container.ResolveAll<T>();
}
public void RegisterAllAsSingleton(Type registrationType, IEnumerable<Type> implementationList)
public void RegisterAllAsSingleton(Type service, IEnumerable<Type> implementationList)
{
_container.RegisterMultiple(registrationType, implementationList).AsSingleton();
foreach (var implementation in implementationList)
{
var factory = CreateSingletonImplementationFactory(implementation);
_container.Register(service, factory, implementation.FullName);
}
}
private Func<TinyIoCContainer, NamedParameterOverloads, object> CreateSingletonImplementationFactory(Type implementation)
{
const string singleImplPrefix = "singleImpl_";
_container.Register(implementation, implementation, singleImplPrefix + implementation.FullName).AsSingleton();
return (c, p) => _container.Resolve(implementation, singleImplPrefix + implementation.FullName);
}
public bool IsTypeRegistered(Type type)

View File

@@ -26,7 +26,7 @@ namespace NzbDrone.Common
Console.WriteLine(" Commands:");
Console.WriteLine(" /{0} Install the application as a Windows Service ({1}).", StartupContext.INSTALL_SERVICE, ServiceProvider.NZBDRONE_SERVICE_NAME);
Console.WriteLine(" /{0} Uninstall already installed Windows Service ({1}).", StartupContext.UNINSTALL_SERVICE, ServiceProvider.NZBDRONE_SERVICE_NAME);
Console.WriteLine(" /{0} Don't open NzbDrone in a browser", StartupContext.NO_BROWSER);
Console.WriteLine(" /{0} Don't open Sonarr in a browser", StartupContext.NO_BROWSER);
Console.WriteLine(" <No Arguments> Run application in console mode.");
}

View File

@@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
@@ -8,12 +6,15 @@ namespace NzbDrone.Common.Crypto
{
public static class HashConverter
{
private static SHA1 HashAlgorithm = SHA1.Create();
private static readonly SHA1 Sha1 = SHA1.Create();
public static int GetHashInt31(string target)
{
var hash = HashAlgorithm.ComputeHash(Encoding.Default.GetBytes(target));
byte[] hash;
lock (Sha1)
{
hash = Sha1.ComputeHash(Encoding.Default.GetBytes(target));
}
return BitConverter.ToInt32(hash, 0) & 0x7fffffff;
}
}

View File

@@ -0,0 +1,14 @@
using System;
namespace NzbDrone.Common.Disk
{
public class RelativeFileSystemModel
{
public string Name { get; set; }
public string Path { get; set; }
public string RelativePath { get; set; }
public string Extension { get; set; }
public long Size { get; set; }
public DateTime? LastModified { get; set; }
}
}

View File

@@ -37,5 +37,10 @@ namespace NzbDrone.Common.Extensions
{
return !source.All(predicate);
}
public static List<TResult> SelectList<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> predicate)
{
return source.Select(predicate).ToList();
}
}
}

View File

@@ -3,8 +3,10 @@ using System.Diagnostics;
using System.IO;
using System.Net;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.TPL;
namespace NzbDrone.Common.Http
{
@@ -22,15 +24,25 @@ namespace NzbDrone.Common.Http
public class HttpClient : IHttpClient
{
private readonly Logger _logger;
private readonly IRateLimitService _rateLimitService;
private readonly ICached<CookieContainer> _cookieContainerCache;
public HttpClient(Logger logger)
public HttpClient(ICacheManager cacheManager, IRateLimitService rateLimitService, Logger logger)
{
_logger = logger;
_rateLimitService = rateLimitService;
ServicePointManager.DefaultConnectionLimit = 12;
_cookieContainerCache = cacheManager.GetCache<CookieContainer>(typeof(HttpClient));
}
public HttpResponse Execute(HttpRequest request)
{
if (request.RateLimit != TimeSpan.Zero)
{
_rateLimitService.WaitAndPulse(request.Url.Host, request.RateLimit);
}
_logger.Trace(request);
var webRequest = (HttpWebRequest)WebRequest.Create(request.Url);
@@ -59,15 +71,29 @@ namespace NzbDrone.Common.Http
AddRequestHeaders(webRequest, request.Headers);
}
var cookieContainer = _cookieContainerCache.Get("container", () => new CookieContainer());
if (request.Cookies.Count != 0)
{
webRequest.CookieContainer = new CookieContainer();
foreach (var pair in request.Cookies)
{
webRequest.CookieContainer.Add(new Cookie(pair.Key, pair.Value, "/", request.Url.Host));
cookieContainer.Add(new Cookie(pair.Key, pair.Value, "/", request.Url.Host)
{
Expires = DateTime.UtcNow.AddHours(1)
});
}
}
if (request.StoreResponseCookie)
{
webRequest.CookieContainer = cookieContainer;
}
else
{
webRequest.CookieContainer = new CookieContainer();
webRequest.CookieContainer.Add(cookieContainer.GetCookies(request.Url));
}
if (!request.Body.IsNullOrWhiteSpace())
{
var bytes = request.Headers.GetEncodingFromContentType().GetBytes(request.Body.ToCharArray());

View File

@@ -46,6 +46,8 @@ namespace NzbDrone.Common.Http
public bool SuppressHttpError { get; set; }
public bool AllowAutoRedirect { get; set; }
public Dictionary<string, string> Cookies { get; private set; }
public bool StoreResponseCookie { get; set; }
public TimeSpan RateLimit { get; set; }
public override string ToString()
{

View File

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

View File

@@ -72,6 +72,7 @@
<Compile Include="ConvertBase32.cs" />
<Compile Include="Crypto\HashProvider.cs" />
<Compile Include="Disk\FileSystemLookupService.cs" />
<Compile Include="Disk\RelativeFileSystemModel.cs" />
<Compile Include="Disk\FileSystemModel.cs" />
<Compile Include="Disk\FileSystemResult.cs" />
<Compile Include="Extensions\DictionaryExtensions.cs" />
@@ -177,7 +178,6 @@
<Compile Include="Processes\ProcessProvider.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Properties\SharedAssemblyInfo.cs" />
<Compile Include="RateGate.cs" />
<Compile Include="Reflection\ReflectionExtensions.cs" />
<Compile Include="Extensions\ResourceExtensions.cs" />
<Compile Include="Security\X509CertificateValidationPolicy.cs" />
@@ -189,6 +189,7 @@
<Compile Include="TinyIoC.cs" />
<Compile Include="TPL\Debouncer.cs" />
<Compile Include="TPL\LimitedConcurrencyLevelTaskScheduler.cs" />
<Compile Include="TPL\RateLimitService.cs" />
<Compile Include="TPL\TaskExtensions.cs" />
<Compile Include="Extensions\TryParseExtensions.cs" />
</ItemGroup>
@@ -212,7 +213,7 @@
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\nuget.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">

View File

@@ -1,197 +0,0 @@
/*
* Code from: http://www.jackleitch.net/2010/10/better-rate-limiting-with-dot-net/
*/
using System;
using System.Collections.Concurrent;
using System.Threading;
namespace NzbDrone.Common
{
/// <summary>
/// Used to control the rate of some occurrence per unit of time.
/// </summary>
/// <remarks>
/// <para>
/// To control the rate of an action using a <see cref="RateGate"/>,
/// code should simply call <see cref="WaitToProceed()"/> prior to
/// performing the action. <see cref="WaitToProceed()"/> will block
/// the current thread until the action is allowed based on the rate
/// limit.
/// </para>
/// <para>
/// This class is thread safe. A single <see cref="RateGate"/> instance
/// may be used to control the rate of an occurrence across multiple
/// threads.
/// </para>
/// </remarks>
public class RateGate : IDisposable
{
// Semaphore used to count and limit the number of occurrences per
// unit time.
private readonly SemaphoreSlim _semaphore;
// Times (in millisecond ticks) at which the semaphore should be exited.
private readonly ConcurrentQueue<int> _exitTimes;
// Timer used to trigger exiting the semaphore.
private readonly Timer _exitTimer;
// Whether this instance is disposed.
private bool _isDisposed;
/// <summary>
/// Number of occurrences allowed per unit of time.
/// </summary>
public int Occurrences { get; private set; }
/// <summary>
/// The length of the time unit, in milliseconds.
/// </summary>
public int TimeUnitMilliseconds { get; private set; }
/// <summary>
/// Initializes a <see cref="RateGate"/> with a rate of <paramref name="occurrences"/>
/// per <paramref name="timeUnit"/>.
/// </summary>
/// <param name="occurrences">Number of occurrences allowed per unit of time.</param>
/// <param name="timeUnit">Length of the time unit.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// If <paramref name="occurrences"/> or <paramref name="timeUnit"/> is negative.
/// </exception>
public RateGate(int occurrences, TimeSpan timeUnit)
{
// Check the arguments.
if (occurrences <= 0)
throw new ArgumentOutOfRangeException("occurrences", "Number of occurrences must be a positive integer");
if (timeUnit != timeUnit.Duration())
throw new ArgumentOutOfRangeException("timeUnit", "Time unit must be a positive span of time");
if (timeUnit >= TimeSpan.FromMilliseconds(UInt32.MaxValue))
throw new ArgumentOutOfRangeException("timeUnit", "Time unit must be less than 2^32 milliseconds");
Occurrences = occurrences;
TimeUnitMilliseconds = (int)timeUnit.TotalMilliseconds;
// Create the semaphore, with the number of occurrences as the maximum count.
_semaphore = new SemaphoreSlim(Occurrences, Occurrences);
// Create a queue to hold the semaphore exit times.
_exitTimes = new ConcurrentQueue<int>();
// Create a timer to exit the semaphore. Use the time unit as the original
// interval length because that's the earliest we will need to exit the semaphore.
_exitTimer = new Timer(ExitTimerCallback, null, TimeUnitMilliseconds, -1);
}
// Callback for the exit timer that exits the semaphore based on exit times
// in the queue and then sets the timer for the nextexit time.
private void ExitTimerCallback(object state)
{
// While there are exit times that are passed due still in the queue,
// exit the semaphore and dequeue the exit time.
int exitTime;
while (_exitTimes.TryPeek(out exitTime)
&& unchecked(exitTime - Environment.TickCount) <= 0)
{
_semaphore.Release();
_exitTimes.TryDequeue(out exitTime);
}
// Try to get the next exit time from the queue and compute
// the time until the next check should take place. If the
// queue is empty, then no exit times will occur until at least
// one time unit has passed.
int timeUntilNextCheck;
if (_exitTimes.TryPeek(out exitTime))
timeUntilNextCheck = unchecked(exitTime - Environment.TickCount);
else
timeUntilNextCheck = TimeUnitMilliseconds;
// Set the timer.
_exitTimer.Change(timeUntilNextCheck, -1);
}
/// <summary>
/// Blocks the current thread until allowed to proceed or until the
/// specified timeout elapses.
/// </summary>
/// <param name="millisecondsTimeout">Number of milliseconds to wait, or -1 to wait indefinitely.</param>
/// <returns>true if the thread is allowed to proceed, or false if timed out</returns>
public bool WaitToProceed(int millisecondsTimeout)
{
// Check the arguments.
if (millisecondsTimeout < -1)
throw new ArgumentOutOfRangeException("millisecondsTimeout");
CheckDisposed();
// Block until we can enter the semaphore or until the timeout expires.
var entered = _semaphore.Wait(millisecondsTimeout);
// If we entered the semaphore, compute the corresponding exit time
// and add it to the queue.
if (entered)
{
var timeToExit = unchecked(Environment.TickCount + TimeUnitMilliseconds);
_exitTimes.Enqueue(timeToExit);
}
return entered;
}
/// <summary>
/// Blocks the current thread until allowed to proceed or until the
/// specified timeout elapses.
/// </summary>
/// <param name="timeout"></param>
/// <returns>true if the thread is allowed to proceed, or false if timed out</returns>
public bool WaitToProceed(TimeSpan timeout)
{
return WaitToProceed((int)timeout.TotalMilliseconds);
}
/// <summary>
/// Blocks the current thread indefinitely until allowed to proceed.
/// </summary>
public void WaitToProceed()
{
WaitToProceed(Timeout.Infinite);
}
// Throws an ObjectDisposedException if this object is disposed.
private void CheckDisposed()
{
if (_isDisposed)
throw new ObjectDisposedException("RateGate is already disposed");
}
/// <summary>
/// Releases unmanaged resources held by an instance of this class.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Releases unmanaged resources held by an instance of this class.
/// </summary>
/// <param name="isDisposing">Whether this object is being disposed.</param>
protected virtual void Dispose(bool isDisposing)
{
if (!_isDisposed)
{
if (isDisposing)
{
// The semaphore and timer both implement IDisposable and
// therefore must be disposed.
_semaphore.Dispose();
_exitTimer.Dispose();
_isDisposed = true;
}
}
}
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.Threading;
namespace NzbDrone.Common.TPL
{
@@ -7,6 +8,9 @@ namespace NzbDrone.Common.TPL
private readonly Action _action;
private readonly System.Timers.Timer _timer;
private volatile int _paused;
private volatile bool _triggered;
public Debouncer(Action action, TimeSpan debounceDuration)
{
_action = action;
@@ -16,13 +20,45 @@ namespace NzbDrone.Common.TPL
void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
_timer.Stop();
_action();
if (_paused == 0)
{
_triggered = false;
_timer.Stop();
_action();
}
}
public void Execute()
{
_timer.Start();
lock (_timer)
{
_triggered = true;
if (_paused == 0)
{
_timer.Start();
}
}
}
public void Pause()
{
lock (_timer)
{
_paused++;
_timer.Stop();
}
}
public void Resume()
{
lock (_timer)
{
_paused--;
if (_paused == 0 && _triggered)
{
_timer.Start();
}
}
}
}
}

View File

@@ -0,0 +1,43 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using NLog;
using NzbDrone.Common.Cache;
namespace NzbDrone.Common.TPL
{
public interface IRateLimitService
{
void WaitAndPulse(string key, TimeSpan interval);
}
public class RateLimitService : IRateLimitService
{
private readonly ConcurrentDictionary<string, DateTime> _rateLimitStore;
private readonly Logger _logger;
public RateLimitService(ICacheManager cacheManager, Logger logger)
{
_rateLimitStore = cacheManager.GetCache<ConcurrentDictionary<string, DateTime>>(GetType(), "rateLimitStore").Get("rateLimitStore", () => new ConcurrentDictionary<string, DateTime>());
_logger = logger;
}
public void WaitAndPulse(string key, TimeSpan interval)
{
var waitUntil = _rateLimitStore.AddOrUpdate(key,
(s) => DateTime.UtcNow + interval,
(s,i) => new DateTime(Math.Max(DateTime.UtcNow.Ticks, i.Ticks), DateTimeKind.Utc) + interval);
waitUntil -= interval;
var delay = waitUntil - DateTime.UtcNow;
if (delay.TotalSeconds > 0.0)
{
_logger.Trace("Rate Limit triggered, delaying '{0}' for {1:0.000} sec", key, delay.TotalSeconds);
System.Threading.Thread.Sleep(delay);
}
}
}
}

View File

@@ -23,7 +23,7 @@ namespace NzbDrone.Console
{
System.Console.WriteLine("");
System.Console.WriteLine("");
Logger.Fatal(exception.Message + ". This can happen if another instance of NzbDrone is already running or another application is using the port assinged to NzbDrone (default: 8989)");
Logger.Fatal(exception.Message + ". This can happen if another instance of Sonarr is already running another application is using the same port (default: 8989) or the user has insufficient permissions");
System.Console.WriteLine("Press any key to exit...");
System.Console.ReadLine();
Environment.Exit(1);

View File

@@ -143,7 +143,7 @@
<PreBuildEvent>
</PreBuildEvent>
</PropertyGroup>
<Import Project="$(SolutionDir)\.nuget\nuget.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">

View File

@@ -47,7 +47,7 @@ namespace NzbDrone.Core.Test.Blacklisting
{
Subject.Insert(_blacklist);
Subject.Blacklisted(_blacklist.SeriesId, _blacklist.SourceTitle.ToUpperInvariant()).Should().HaveCount(1);
Subject.BlacklistedByTitle(_blacklist.SeriesId, _blacklist.SourceTitle.ToUpperInvariant()).Should().HaveCount(1);
}
}
}

View File

@@ -28,9 +28,12 @@ namespace NzbDrone.Core.Test.Blacklisting
};
_event.Data.Add("publishedDate", DateTime.UtcNow.ToString("s") + "Z");
_event.Data.Add("size", "1000");
_event.Data.Add("indexer", "nzbs.org");
_event.Data.Add("protocol", "1");
_event.Data.Add("message", "Marked as failed");
}
[Test]
public void should_add_to_repository()
{
@@ -39,5 +42,17 @@ namespace NzbDrone.Core.Test.Blacklisting
Mocker.GetMock<IBlacklistRepository>()
.Verify(v => v.Insert(It.Is<Blacklist>(b => b.EpisodeIds == _event.EpisodeIds)), Times.Once());
}
[Test]
public void should_add_to_repository_missing_size_and_protocol()
{
Subject.Handle(_event);
_event.Data.Remove("size");
_event.Data.Remove("protocol");
Mocker.GetMock<IBlacklistRepository>()
.Verify(v => v.Insert(It.Is<Blacklist>(b => b.EpisodeIds == _event.EpisodeIds)), Times.Once());
}
}
}

View File

@@ -10,6 +10,7 @@ using NzbDrone.Core.Lifecycle;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Test.Common;
using FluentAssertions;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Core.Test.DataAugmentationFixture.Scene
{
@@ -134,6 +135,48 @@ namespace NzbDrone.Core.Test.DataAugmentationFixture.Scene
.Verify(v => v.All(), Times.Once());
}
[Test]
public void should_not_add_mapping_with_blank_title()
{
GivenProviders(new[] { _provider1 });
var fakeMappings = Builder<SceneMapping>.CreateListOfSize(2)
.TheLast(1)
.With(m => m.Title = null)
.Build()
.ToList();
_provider1.Setup(s => s.GetSceneMappings()).Returns(fakeMappings);
Mocker.GetMock<ISceneMappingRepository>().Setup(c => c.All()).Returns(_fakeMappings);
Subject.Execute(new UpdateSceneMappingCommand());
Mocker.GetMock<ISceneMappingRepository>().Verify(c => c.InsertMany(It.Is<IList<SceneMapping>>(m => !m.Any(s => s.Title.IsNullOrWhiteSpace()))), Times.Once());
ExceptionVerification.ExpectedWarns(1);
}
[Test]
public void should_not_add_mapping_with_blank_search_title()
{
GivenProviders(new[] { _provider1 });
var fakeMappings = Builder<SceneMapping>.CreateListOfSize(2)
.TheLast(1)
.With(m => m.SearchTerm = null)
.Build()
.ToList();
_provider1.Setup(s => s.GetSceneMappings()).Returns(fakeMappings);
Mocker.GetMock<ISceneMappingRepository>().Setup(c => c.All()).Returns(_fakeMappings);
Subject.Execute(new UpdateSceneMappingCommand());
Mocker.GetMock<ISceneMappingRepository>().Verify(c => c.InsertMany(It.Is<IList<SceneMapping>>(m => !m.Any(s => s. SearchTerm.IsNullOrWhiteSpace()))), Times.Once());
ExceptionVerification.ExpectedWarns(1);
}
private void AssertNoUpdate()
{
_provider1.Verify(c => c.GetSceneMappings(), Times.Once());

View File

@@ -123,5 +123,19 @@ namespace NzbDrone.Core.Test.DecisionEngineTests
WithFirstEpisodeUnmonitored();
_monitoredEpisodeSpecification.IsSatisfiedBy(_parseResultSingle, new SeasonSearchCriteria()).Accepted.Should().BeFalse();
}
[Test]
public void should_return_true_if_episode_is_not_monitored_and_monitoredEpisodesOnly_flag_is_false()
{
WithFirstEpisodeUnmonitored();
_monitoredEpisodeSpecification.IsSatisfiedBy(_parseResultSingle, new SingleEpisodeSearchCriteria { MonitoredEpisodesOnly = false }).Accepted.Should().BeTrue();
}
[Test]
public void should_return_false_if_episode_is_not_monitored_and_monitoredEpisodesOnly_flag_is_true()
{
WithFirstEpisodeUnmonitored();
_monitoredEpisodeSpecification.IsSatisfiedBy(_parseResultSingle, new SingleEpisodeSearchCriteria{ MonitoredEpisodesOnly = true}).Accepted.Should().BeFalse();
}
}
}

View File

@@ -0,0 +1,76 @@
using System.Collections.Generic;
using System.Linq;
using FizzWare.NBuilder;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Tv;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.DecisionEngineTests
{
[TestFixture]
public class SameEpisodesSpecificationFixture : CoreTest<SameEpisodesSpecification>
{
private List<Episode> _episodes;
[SetUp]
public void Setup()
{
_episodes = Builder<Episode>.CreateListOfSize(2)
.All()
.With(e => e.EpisodeFileId = 1)
.BuildList();
}
private void GivenEpisodesInFile(List<Episode> episodes)
{
Mocker.GetMock<IEpisodeService>()
.Setup(s => s.GetEpisodesByFileId(It.IsAny<int>()))
.Returns(episodes);
}
[Test]
public void should_not_upgrade_when_new_release_contains_less_episodes()
{
GivenEpisodesInFile(_episodes);
Subject.IsSatisfiedBy(new List<Episode> { _episodes.First() }).Should().BeFalse();
}
[Test]
public void should_upgrade_when_new_release_contains_more_episodes()
{
GivenEpisodesInFile(new List<Episode> { _episodes.First() });
Subject.IsSatisfiedBy(_episodes).Should().BeTrue();
}
[Test]
public void should_upgrade_when_new_release_contains_the_same_episodes()
{
GivenEpisodesInFile(_episodes);
Subject.IsSatisfiedBy(_episodes).Should().BeTrue();
}
[Test]
public void should_upgrade_when_release_contains_the_same_episodes_as_multiple_files()
{
var episodes = Builder<Episode>.CreateListOfSize(2)
.BuildList();
Mocker.GetMock<IEpisodeService>()
.Setup(s => s.GetEpisodesByFileId(episodes.First().EpisodeFileId))
.Returns(new List<Episode> { episodes.First() });
Mocker.GetMock<IEpisodeService>()
.Setup(s => s.GetEpisodesByFileId(episodes.Last().EpisodeFileId))
.Returns(new List<Episode> { episodes.Last() });
Subject.IsSatisfiedBy(episodes).Should().BeTrue();
}
}
}

View File

@@ -151,6 +151,10 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
{
GivenCompletedItem();
Mocker.GetMock<IDiskProvider>()
.Setup(c => c.FolderExists(It.IsAny<string>()))
.Returns(true);
Subject.RemoveItem("_Droned.S01E01.Pilot.1080p.WEB-DL-DRONE_0", true);
Mocker.GetMock<IDiskProvider>()
@@ -158,9 +162,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
}
[Test]
public void RemoveItem_should_throw_if_unknown_item()
public void RemoveItem_should_ignore_if_unknown_item()
{
Assert.Throws<ArgumentException>(() => Subject.RemoveItem("_Droned.S01E01.Pilot.1080p.WEB-DL-DRONE_0", true));
Subject.RemoveItem("_Droned.S01E01.Pilot.1080p.WEB-DL-DRONE_0", true);
Mocker.GetMock<IDiskProvider>()
.Verify(c => c.DeleteFile(It.IsAny<string>()), Times.Never());

View File

@@ -148,6 +148,10 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
{
GivenCompletedItem();
Mocker.GetMock<IDiskProvider>()
.Setup(c => c.FolderExists(It.IsAny<string>()))
.Returns(true);
Subject.RemoveItem("_Droned.S01E01.Pilot.1080p.WEB-DL-DRONE_0", true);
Mocker.GetMock<IDiskProvider>()
@@ -155,9 +159,9 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.Blackhole
}
[Test]
public void RemoveItem_should_throw_if_unknown_item()
public void RemoveItem_should_ignore_if_unknown_item()
{
Assert.Throws<ArgumentException>(() => Subject.RemoveItem("_Droned.S01E01.Pilot.1080p.WEB-DL-DRONE_0", true));
Subject.RemoveItem("_Droned.S01E01.Pilot.1080p.WEB-DL-DRONE_0", true);
Mocker.GetMock<IDiskProvider>()
.Verify(c => c.DeleteFile(It.IsAny<string>()), Times.Never());

View File

@@ -9,6 +9,7 @@ using NzbDrone.Core.Download.Clients.Nzbget;
using NzbDrone.Test.Common;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Common.Disk;
using NzbDrone.Core.Download.Clients;
namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
{
@@ -139,6 +140,22 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
Subject.GetItems().Should().BeEmpty();
}
[Test]
public void RemoveItem_should_delete_folder()
{
GivenQueue(null);
GivenHistory(_completed);
Mocker.GetMock<IDiskProvider>()
.Setup(v => v.FolderExists(It.IsAny<string>()))
.Returns(true);
Subject.RemoveItem("id", true);
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.DeleteFolder(It.IsAny<string>(), true), Times.Once());
}
[Test]
public void queued_item_should_have_required_properties()
{
@@ -213,6 +230,19 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
result.Status.Should().Be(DownloadItemStatus.Failed);
}
[Test]
public void should_report_deletestatus_dupe_as_warning()
{
_completed.DeleteStatus = "DUPE";
GivenQueue(null);
GivenHistory(_completed);
var result = Subject.GetItems().Single();
result.Status.Should().Be(DownloadItemStatus.Warning);
}
[Test]
public void should_report_unpackstatus_freespace_as_warning()
{
@@ -255,7 +285,6 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
items.First().Status.Should().Be(DownloadItemStatus.Failed);
}
[Test]
public void Download_should_return_unique_id()
{
@@ -268,6 +297,16 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
id.Should().NotBeNullOrEmpty();
}
[Test]
public void Download_should_throw_if_failed()
{
GivenFailedDownload();
var remoteEpisode = CreateRemoteEpisode();
Assert.Throws<DownloadClientException>(() => Subject.Download(remoteEpisode));
}
[Test]
public void GetItems_should_ignore_downloads_from_other_categories()
{
@@ -319,5 +358,20 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.NzbgetTests
result.OutputPath.Should().Be(@"O:\mymount\Droned.S01E01.Pilot.1080p.WEB-DL-DRONE".AsOsAgnostic());
}
[TestCase("11.0", false)]
[TestCase("12.0", true)]
[TestCase("11.0-b30ef0134", false)]
[TestCase("13.0-b30ef0134", true)]
public void should_test_version(string version, bool expected)
{
Mocker.GetMock<INzbgetProxy>()
.Setup(v => v.GetVersion(It.IsAny<NzbgetSettings>()))
.Returns(version);
var error = Subject.Test();
error.IsValid.Should().Be(expected);
}
}
}

View File

@@ -231,6 +231,20 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.SabnzbdTests
VerifyFailed(result);
}
[TestCase("[ TOWN ]-[ http://www.town.ag ]-[ ANIME ]-[Usenet Provider >> http://www.ssl- <<] - [Commie] Aldnoah Zero 18 [234C8FC7]", "[ TOWN ]-[ http-++www.town.ag ]-[ ANIME ]-[Usenet Provider http-++www.ssl- ] - [Commie] Aldnoah Zero 18 [234C8FC7].nzb")]
public void Download_should_use_clean_title(string title, string filename)
{
GivenSuccessfulDownload();
var remoteEpisode = CreateRemoteEpisode();
remoteEpisode.Release.Title = title;
var id = Subject.Download(remoteEpisode);
Mocker.GetMock<ISabnzbdProxy>()
.Verify(v => v.DownloadNzb(It.IsAny<byte[]>(), filename, It.IsAny<string>(), It.IsAny<int>(), It.IsAny<SabnzbdSettings>()), Times.Once());
}
[Test]
public void Download_should_return_unique_id()
{

View File

@@ -102,7 +102,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
protected void GivenTvCategory()
{
_settings.TvCategory = "nzbdrone";
_settings.TvCategory = "sonarr";
}
protected void GivenFailedDownload()
@@ -228,7 +228,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
id.Should().NotBeNullOrEmpty();
Mocker.GetMock<ITransmissionProxy>()
.Verify(v => v.AddTorrentFromData(It.IsAny<Byte[]>(), @"C:/Downloads/Finished/transmission/nzbdrone", It.IsAny<TransmissionSettings>()), Times.Once());
.Verify(v => v.AddTorrentFromData(It.IsAny<Byte[]>(), @"C:/Downloads/Finished/transmission/sonarr", It.IsAny<TransmissionSettings>()), Times.Once());
}
[Test]
@@ -246,7 +246,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
id.Should().NotBeNullOrEmpty();
Mocker.GetMock<ITransmissionProxy>()
.Verify(v => v.AddTorrentFromData(It.IsAny<Byte[]>(), @"C:/Downloads/Finished/transmission/nzbdrone", It.IsAny<TransmissionSettings>()), Times.Once());
.Verify(v => v.AddTorrentFromData(It.IsAny<Byte[]>(), @"C:/Downloads/Finished/transmission/sonarr", It.IsAny<TransmissionSettings>()), Times.Once());
}
[TestCase("magnet:?xt=urn:btih:ZPBPA2P6ROZPKRHK44D5OW6NHXU5Z6KR&tr=udp", "CBC2F069FE8BB2F544EAE707D75BCD3DE9DCF951")]
@@ -327,7 +327,7 @@ namespace NzbDrone.Core.Test.Download.DownloadClientTests.TransmissionTests
{
GivenTvCategory();
_downloading.DownloadDir = @"C:/Downloads/Finished/transmission/nzbdrone";
_downloading.DownloadDir = @"C:/Downloads/Finished/transmission/sonarr";
GivenTorrents(new List<TransmissionTorrent>
{

View File

@@ -38,6 +38,7 @@ namespace NzbDrone.Core.Test.Download
var releaseInfo = Builder<ReleaseInfo>.CreateNew()
.With(v => v.DownloadProtocol = Indexers.DownloadProtocol.Usenet)
.With(v => v.DownloadUrl = "http://test.site/download1.ext")
.Build();
_parseResult = Builder<RemoteEpisode>.CreateNew()

View File

@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<rss version="1.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:torznab="http://torznab.com/schemas/2015/feed">
<channel>
<atom:link href="http://localhost:9117/" rel="self" type="application/rss+xml" />
<title>The Pirate Bay</title>
<description>The worlds largest bittorrent indexer</description>
<link>https://thepiratebay.se/</link>
<lanuage>en-us</lanuage>
<category>search</category>
<image>
<url>http://localhost:9117/logos/thepiratebay.png</url>
<title>The Pirate Bay</title>
<link>https://thepiratebay.se/</link>
<description>The Pirate Bay</description>
</image>
<item>
<title>Series Title S05E02 HDTV x264-Xclusive [eztv]</title>
<guid>https://thepiratebay.se/torrent/11811366/Series_Title_S05E02_HDTV_x264-Xclusive_[eztv]</guid>
<comments>https://thepiratebay.se/torrent/11811366/Series_Title_S05E02_HDTV_x264-Xclusive_[eztv]</comments>
<pubDate>Sat, 11 Apr 2015 21:34:00 -0600</pubDate>
<size>388895872</size>
<description>Series Title S05E02 HDTV x264-Xclusive [eztv]</description>
<link>magnet:?xt=urn:btih:9fb267cff5ae5603f07a347676ec3bf3e35f75e1&amp;dn=Game+of+Thrones+S05E02+HDTV+x264-Xclusive+[eztv]&amp;tr=udp:%2F%2Fopen.demonii.com:1337&amp;tr=udp:%2F%2Ftracker.coppersurfer.tk:6969&amp;tr=udp:%2F%2Ftracker.leechers-paradise.org:6969&amp;tr=udp:%2F%2Fexodus.desync.com:6969</link>
<enclosure url="magnet:?xt=urn:btih:9fb267cff5ae5603f07a347676ec3bf3e35f75e1&amp;dn=Game+of+Thrones+S05E02+HDTV+x264-Xclusive+[eztv]&amp;tr=udp:%2F%2Fopen.demonii.com:1337&amp;tr=udp:%2F%2Ftracker.coppersurfer.tk:6969&amp;tr=udp:%2F%2Ftracker.leechers-paradise.org:6969&amp;tr=udp:%2F%2Fexodus.desync.com:6969" length="388895872" type="application/x-bittorrent;x-scheme-handler/magnet" />
<torznab:attr name="magneturl" value="magnet:?xt=urn:btih:9fb267cff5ae5603f07a347676ec3bf3e35f75e1&amp;dn=Game+of+Thrones+S05E02+HDTV+x264-Xclusive+[eztv]&amp;tr=udp:%2F%2Fopen.demonii.com:1337&amp;tr=udp:%2F%2Ftracker.coppersurfer.tk:6969&amp;tr=udp:%2F%2Ftracker.leechers-paradise.org:6969&amp;tr=udp:%2F%2Fexodus.desync.com:6969" />
<torznab:attr name="seeders" value="34128" />
<torznab:attr name="peers" value="36724" />
<torznab:attr name="infohash" value="9fb267cff5ae5603f07a347676ec3bf3e35f75e1" />
<torznab:attr name="minimumratio" value="1" />
<torznab:attr name="minimumseedtime" value="172800" />
</item>
<item>
<title>Series Title S05E03 WEBRip XviD-FUM[ettv]</title>
<guid>https://thepiratebay.se/torrent/11811373/Series_Title_S05E03_WEBRip_XviD-FUM[ettv]</guid>
<comments>https://thepiratebay.se/torrent/11811373/Series_Title_S05E03_WEBRip_XviD-FUM[ettv]</comments>
<pubDate>Sat, 11 Apr 2015 21:42:00 -0600</pubDate>
<size>471722880</size>
<description>Series Title S05E03 WEBRip XviD-FUM[ettv]</description>
<link>magnet:?xt=urn:btih:c1f9f6bade11a4c46028b118053452460ab94be1&amp;dn=Game+of+Thrones+S05E03+WEBRip+XviD-FUM[ettv]&amp;tr=udp:%2F%2Fopen.demonii.com:1337&amp;tr=udp:%2F%2Ftracker.coppersurfer.tk:6969&amp;tr=udp:%2F%2Ftracker.leechers-paradise.org:6969&amp;tr=udp:%2F%2Fexodus.desync.com:6969</link>
<enclosure url="magnet:?xt=urn:btih:c1f9f6bade11a4c46028b118053452460ab94be1&amp;dn=Game+of+Thrones+S05E03+WEBRip+XviD-FUM[ettv]&amp;tr=udp:%2F%2Fopen.demonii.com:1337&amp;tr=udp:%2F%2Ftracker.coppersurfer.tk:6969&amp;tr=udp:%2F%2Ftracker.leechers-paradise.org:6969&amp;tr=udp:%2F%2Fexodus.desync.com:6969" length="471722880" type="application/x-bittorrent;x-scheme-handler/magnet" />
<torznab:attr name="magneturl" value="magnet:?xt=urn:btih:c1f9f6bade11a4c46028b118053452460ab94be1&amp;dn=Game+of+Thrones+S05E03+WEBRip+XviD-FUM[ettv]&amp;tr=udp:%2F%2Fopen.demonii.com:1337&amp;tr=udp:%2F%2Ftracker.coppersurfer.tk:6969&amp;tr=udp:%2F%2Ftracker.leechers-paradise.org:6969&amp;tr=udp:%2F%2Fexodus.desync.com:6969" />
<torznab:attr name="seeders" value="28706" />
<torznab:attr name="peers" value="30894" />
<torznab:attr name="infohash" value="c1f9f6bade11a4c46028b118053452460ab94be1" />
<torznab:attr name="minimumratio" value="1" />
<torznab:attr name="minimumseedtime" value="172800" />
</item>
<item>
<title>Series Title S05E01 HDTV x264-Xclusive</title>
<guid>https://thepiratebay.se/torrent/11811268/Series_Title_S05E01_HDTV_x264-Xclusive</guid>
<comments>https://thepiratebay.se/torrent/11811268/Series_Title_S05E01_HDTV_x264-Xclusive</comments>
<pubDate>Sat, 11 Apr 2015 18:14:00 -0600</pubDate>
<size>312339328</size>
<description>Series Title S05E01 HDTV x264-Xclusive</description>
<link>magnet:?xt=urn:btih:fd8b1062af0d8c2426cb4d180b86815ffa91b479&amp;dn=Game+Of+Thrones+S05E01+HDTV+x264-Xclusive&amp;tr=udp:%2F%2Fopen.demonii.com:1337&amp;tr=udp:%2F%2Ftracker.coppersurfer.tk:6969&amp;tr=udp:%2F%2Ftracker.leechers-paradise.org:6969&amp;tr=udp:%2F%2Fexodus.desync.com:6969</link>
<enclosure url="magnet:?xt=urn:btih:fd8b1062af0d8c2426cb4d180b86815ffa91b479&amp;dn=Game+Of+Thrones+S05E01+HDTV+x264-Xclusive&amp;tr=udp:%2F%2Fopen.demonii.com:1337&amp;tr=udp:%2F%2Ftracker.coppersurfer.tk:6969&amp;tr=udp:%2F%2Ftracker.leechers-paradise.org:6969&amp;tr=udp:%2F%2Fexodus.desync.com:6969" length="312339328" type="application/x-bittorrent;x-scheme-handler/magnet" />
<torznab:attr name="magneturl" value="magnet:?xt=urn:btih:fd8b1062af0d8c2426cb4d180b86815ffa91b479&amp;dn=Game+Of+Thrones+S05E01+HDTV+x264-Xclusive&amp;tr=udp:%2F%2Fopen.demonii.com:1337&amp;tr=udp:%2F%2Ftracker.coppersurfer.tk:6969&amp;tr=udp:%2F%2Ftracker.leechers-paradise.org:6969&amp;tr=udp:%2F%2Fexodus.desync.com:6969" />
<torznab:attr name="seeders" value="26637" />
<torznab:attr name="peers" value="27453" />
<torznab:attr name="infohash" value="fd8b1062af0d8c2426cb4d180b86815ffa91b479" />
<torznab:attr name="minimumratio" value="1" />
<torznab:attr name="minimumseedtime" value="172800" />
</item>
<item>
<title>Series Title S05E04 WEBRip XviD-FUM[ettv]</title>
<guid>https://thepiratebay.se/torrent/11811448/Series_Title_S05E04_WEBRip_XviD-FUM[ettv]</guid>
<comments>https://thepiratebay.se/torrent/11811448/Series_Title_S05E04_WEBRip_XviD-FUM[ettv]</comments>
<pubDate>Sat, 11 Apr 2015 23:10:00 -0600</pubDate>
<size>442970944</size>
<description>Series Title S05E04 WEBRip XviD-FUM[ettv]</description>
<link>magnet:?xt=urn:btih:30abe6b5816c758d327672a0fa58ff2b055ad3fe&amp;dn=Game+of+Thrones+S05E04+WEBRip+XviD-FUM[ettv]&amp;tr=udp:%2F%2Fopen.demonii.com:1337&amp;tr=udp:%2F%2Ftracker.coppersurfer.tk:6969&amp;tr=udp:%2F%2Ftracker.leechers-paradise.org:6969&amp;tr=udp:%2F%2Fexodus.desync.com:6969</link>
<enclosure url="magnet:?xt=urn:btih:30abe6b5816c758d327672a0fa58ff2b055ad3fe&amp;dn=Game+of+Thrones+S05E04+WEBRip+XviD-FUM[ettv]&amp;tr=udp:%2F%2Fopen.demonii.com:1337&amp;tr=udp:%2F%2Ftracker.coppersurfer.tk:6969&amp;tr=udp:%2F%2Ftracker.leechers-paradise.org:6969&amp;tr=udp:%2F%2Fexodus.desync.com:6969" length="442970944" type="application/x-bittorrent;x-scheme-handler/magnet" />
<torznab:attr name="magneturl" value="magnet:?xt=urn:btih:30abe6b5816c758d327672a0fa58ff2b055ad3fe&amp;dn=Game+of+Thrones+S05E04+WEBRip+XviD-FUM[ettv]&amp;tr=udp:%2F%2Fopen.demonii.com:1337&amp;tr=udp:%2F%2Ftracker.coppersurfer.tk:6969&amp;tr=udp:%2F%2Ftracker.leechers-paradise.org:6969&amp;tr=udp:%2F%2Fexodus.desync.com:6969" />
<torznab:attr name="seeders" value="21551" />
<torznab:attr name="peers" value="22711" />
<torznab:attr name="infohash" value="30abe6b5816c758d327672a0fa58ff2b055ad3fe" />
<torznab:attr name="minimumratio" value="1" />
<torznab:attr name="minimumseedtime" value="172800" />
</item>
<item>
<title>Series.Title.S03E19.HDTV.x264-LOL[ettv]</title>
<guid>https://thepiratebay.se/torrent/11817918/Series.Title.S03E19.HDTV.x264-LOL[ettv]</guid>
<comments>https://thepiratebay.se/torrent/11817918/Series.Title.S03E19.HDTV.x264-LOL[ettv]</comments>
<pubDate>Wed, 15 Apr 2015 18:58:00 -0600</pubDate>
<size>243951200</size>
<description>Series.Title.S03E19.HDTV.x264-LOL[ettv]</description>
<link>magnet:?xt=urn:btih:53896c2a6391a69a672041139e023c018c0f4aff&amp;dn=Series.Title.S03E19.HDTV.x264-LOL[ettv]&amp;tr=udp:%2F%2Fopen.demonii.com:1337&amp;tr=udp:%2F%2Ftracker.coppersurfer.tk:6969&amp;tr=udp:%2F%2Ftracker.leechers-paradise.org:6969&amp;tr=udp:%2F%2Fexodus.desync.com:6969</link>
<enclosure url="magnet:?xt=urn:btih:53896c2a6391a69a672041139e023c018c0f4aff&amp;dn=Series.Title.S03E19.HDTV.x264-LOL[ettv]&amp;tr=udp:%2F%2Fopen.demonii.com:1337&amp;tr=udp:%2F%2Ftracker.coppersurfer.tk:6969&amp;tr=udp:%2F%2Ftracker.leechers-paradise.org:6969&amp;tr=udp:%2F%2Fexodus.desync.com:6969" length="243951200" type="application/x-bittorrent;x-scheme-handler/magnet" />
<torznab:attr name="magneturl" value="magnet:?xt=urn:btih:53896c2a6391a69a672041139e023c018c0f4aff&amp;dn=Series.Title.S03E19.HDTV.x264-LOL[ettv]&amp;tr=udp:%2F%2Fopen.demonii.com:1337&amp;tr=udp:%2F%2Ftracker.coppersurfer.tk:6969&amp;tr=udp:%2F%2Ftracker.leechers-paradise.org:6969&amp;tr=udp:%2F%2Fexodus.desync.com:6969" />
<torznab:attr name="seeders" value="15754" />
<torznab:attr name="peers" value="17336" />
<torznab:attr name="infohash" value="53896c2a6391a69a672041139e023c018c0f4aff" />
<torznab:attr name="minimumratio" value="1" />
<torznab:attr name="minimumseedtime" value="172800" />
</item>
</channel>
</rss>

View File

@@ -1,7 +1,9 @@
using System.IO;
using NUnit.Framework;
using NzbDrone.Common.Cache;
using NzbDrone.Common.Cloud;
using NzbDrone.Common.Http;
using NzbDrone.Common.TPL;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.Framework
@@ -16,7 +18,7 @@ namespace NzbDrone.Core.Test.Framework
protected void UseRealHttp()
{
Mocker.SetConstant<IHttpProvider>(new HttpProvider(TestLogger));
Mocker.SetConstant<IHttpClient>(new HttpClient(TestLogger));
Mocker.SetConstant<IHttpClient>(new HttpClient(Mocker.Resolve<CacheManager>(), Mocker.Resolve<RateLimitService>(), TestLogger));
Mocker.SetConstant<IDroneServicesRequestBuilder>(new DroneServicesHttpRequestBuilder());
}
}

View File

@@ -88,9 +88,31 @@ namespace NzbDrone.Core.Test.Framework
protected virtual TestDatabase WithTestDb(Action<MigrationBase> beforeMigration)
{
var factory = Mocker.Resolve<DbFactory>();
var database = factory.Create(MigrationType);
var database = factory.Create(MigrationType, beforeMigration);
Mocker.SetConstant(database);
switch (MigrationType)
{
case MigrationType.Main:
{
var mainDb = new MainDatabase(database);
Mocker.SetConstant<IMainDatabase>(mainDb);
break;
}
case MigrationType.Log:
{
var logDb = new LogDatabase(database);
Mocker.SetConstant<ILogDatabase>(logDb);
break;
}
default:
{
throw new ArgumentException("Invalid MigrationType");
}
}
var testDb = new TestDatabase(database);
return testDb;

View File

@@ -11,20 +11,13 @@ namespace NzbDrone.Core.Test.Framework
{
protected override TestDatabase WithTestDb(Action<MigrationBase> beforeMigration)
{
var factory = Mocker.Resolve<DbFactory>();
var database = factory.Create(MigrationType, m =>
return base.WithTestDb(m =>
{
if (m.GetType() == typeof(TMigration))
{
beforeMigration(m);
}
});
var testDb = new TestDatabase(database);
Mocker.SetConstant(database);
return testDb;
}
[SetUp]

View File

@@ -37,6 +37,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
_xemSeries = Builder<Series>.CreateNew()
.With(v => v.UseSceneNumbering = true)
.With(v => v.Monitored = true)
.Build();
_xemEpisodes = new List<Episode>();
@@ -63,6 +64,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
.With(v => v.EpisodeNumber, episodeNumber)
.With(v => v.SceneSeasonNumber, sceneSeasonNumber)
.With(v => v.SceneEpisodeNumber, sceneEpisodeNumber)
.With(v => v.Monitored = true)
.Build();
_xemEpisodes.Add(episode);
@@ -136,7 +138,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
var allCriteria = WatchForSearchCriteria();
Subject.SeasonSearch(_xemSeries.Id, 1);
Subject.SeasonSearch(_xemSeries.Id, 1, false);
var criteria = allCriteria.OfType<SeasonSearchCriteria>().ToList();
@@ -151,7 +153,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
var allCriteria = WatchForSearchCriteria();
Subject.SeasonSearch(_xemSeries.Id, 2);
Subject.SeasonSearch(_xemSeries.Id, 2, false);
var criteria = allCriteria.OfType<SeasonSearchCriteria>().ToList();
@@ -167,7 +169,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
var allCriteria = WatchForSearchCriteria();
Subject.SeasonSearch(_xemSeries.Id, 4);
Subject.SeasonSearch(_xemSeries.Id, 4, false);
var criteria1 = allCriteria.OfType<SeasonSearchCriteria>().ToList();
var criteria2 = allCriteria.OfType<SingleEpisodeSearchCriteria>().ToList();
@@ -187,7 +189,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
var allCriteria = WatchForSearchCriteria();
Subject.SeasonSearch(_xemSeries.Id, 7);
Subject.SeasonSearch(_xemSeries.Id, 7, false);
var criteria = allCriteria.OfType<SeasonSearchCriteria>().ToList();
@@ -196,19 +198,55 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
}
[Test]
public void season_search_for_anime_should_search_for_each_episode()
public void season_search_for_anime_should_search_for_each_monitored_episode()
{
WithEpisodes();
_xemSeries.SeriesType = SeriesTypes.Anime;
var seasonNumber = 1;
_xemEpisodes.ForEach(e => e.EpisodeFileId = 0);
var seasonNumber = 1;
var allCriteria = WatchForSearchCriteria();
Subject.SeasonSearch(_xemSeries.Id, seasonNumber);
Subject.SeasonSearch(_xemSeries.Id, seasonNumber, true);
var criteria = allCriteria.OfType<AnimeEpisodeSearchCriteria>().ToList();
criteria.Count.Should().Be(_xemEpisodes.Count(e => e.SeasonNumber == seasonNumber));
}
[Test]
public void season_search_for_anime_should_not_search_for_unmonitored_episodes()
{
WithEpisodes();
_xemSeries.SeriesType = SeriesTypes.Anime;
_xemEpisodes.ForEach(e => e.Monitored = false);
_xemEpisodes.ForEach(e => e.EpisodeFileId = 0);
var seasonNumber = 1;
var allCriteria = WatchForSearchCriteria();
Subject.SeasonSearch(_xemSeries.Id, seasonNumber, true);
var criteria = allCriteria.OfType<AnimeEpisodeSearchCriteria>().ToList();
criteria.Count.Should().Be(0);
}
[Test]
public void season_search_for_anime_should_not_search_for_episodes_with_files()
{
WithEpisodes();
_xemSeries.SeriesType = SeriesTypes.Anime;
_xemEpisodes.ForEach(e => e.EpisodeFileId = 1);
var seasonNumber = 1;
var allCriteria = WatchForSearchCriteria();
Subject.SeasonSearch(_xemSeries.Id, seasonNumber, true);
var criteria = allCriteria.OfType<AnimeEpisodeSearchCriteria>().ToList();
criteria.Count.Should().Be(0);
}
}
}

View File

@@ -32,7 +32,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
.Returns(_series);
Mocker.GetMock<ISearchForNzb>()
.Setup(s => s.SeasonSearch(_series.Id, It.IsAny<Int32>()))
.Setup(s => s.SeasonSearch(_series.Id, It.IsAny<Int32>(), false))
.Returns(new List<DownloadDecision>());
Mocker.GetMock<IProcessDownloadDecisions>()
@@ -52,7 +52,7 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
Subject.Execute(new SeriesSearchCommand{ SeriesId = _series.Id });
Mocker.GetMock<ISearchForNzb>()
.Verify(v => v.SeasonSearch(_series.Id, It.IsAny<Int32>()), Times.Exactly(_series.Seasons.Count(s => s.Monitored)));
.Verify(v => v.SeasonSearch(_series.Id, It.IsAny<Int32>(), false), Times.Exactly(_series.Seasons.Count(s => s.Monitored)));
}
[Test]
@@ -68,9 +68,9 @@ namespace NzbDrone.Core.Test.IndexerSearchTests
};
Mocker.GetMock<ISearchForNzb>()
.Setup(s => s.SeasonSearch(_series.Id, It.IsAny<Int32>()))
.Setup(s => s.SeasonSearch(_series.Id, It.IsAny<Int32>(), false))
.Returns(new List<DownloadDecision>())
.Callback<Int32, Int32>((seriesId, seasonNumber) => seasonOrder.Add(seasonNumber));
.Callback<int, int, bool>((seriesId, seasonNumber, missingOnly) => seasonOrder.Add(seasonNumber));
Subject.Execute(new SeriesSearchCommand { SeriesId = _series.Id });

View File

@@ -28,8 +28,7 @@ namespace NzbDrone.Core.Test.IndexerTests.BitMeTvTests
public void should_parse_recent_feed_from_BitMeTv()
{
var recentFeed = ReadAllText(@"Files/RSS/BitMeTv.xml");
Mocker.GetMock<IHttpClient>()
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET)))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));

View File

@@ -21,7 +21,7 @@ namespace NzbDrone.Core.Test.IndexerTests.BroadcastheNetTests
Subject.Definition = new IndexerDefinition()
{
Name = "BroadcastheNet",
Settings = new BroadcastheNetSettings() { ApiKey = "abc" }
Settings = new BroadcastheNetSettings() { ApiKey = "abc", BaseUrl = "https://api.btnapps.net/" }
};
}
@@ -127,5 +127,29 @@ namespace NzbDrone.Core.Test.IndexerTests.BroadcastheNetTests
ExceptionVerification.ExpectedWarns(1);
}
[Test]
public void should_replace_https_http_as_needed()
{
var recentFeed = ReadAllText(@"Files/Indexers/BroadcastheNet/RecentFeed.json");
(Subject.Definition.Settings as BroadcastheNetSettings).BaseUrl = "http://api.btnapps.net/";
recentFeed = recentFeed.Replace("http:", "https:");
Mocker.GetMock<IHttpClient>()
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.POST)))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
var releases = Subject.FetchRecent();
releases.Should().HaveCount(2);
releases.First().Should().BeOfType<TorrentInfo>();
var torrentInfo = releases.First() as TorrentInfo;
torrentInfo.DownloadUrl.Should().Be("http://broadcasthe.net/torrents.php?action=download&id=123&authkey=123&torrent_pass=123");
torrentInfo.InfoUrl.Should().Be("http://broadcasthe.net/torrents.php?id=237457&torrentid=123");
}
}
}

View File

@@ -9,6 +9,14 @@ namespace NzbDrone.Core.Test.IndexerTests
{
public class TestIndexer : HttpIndexerBase<TestIndexerSettings>
{
public override string Name
{
get
{
return "Test Indexer";
}
}
public override DownloadProtocol Protocol { get { return DownloadProtocol.Usenet; } }
public Int32 _supportedPageSize;

View File

@@ -57,5 +57,35 @@ namespace NzbDrone.Core.Test.IndexerTests.TorznabTests
releaseInfo.Seeders.Should().Be(7);
releaseInfo.Peers.Should().Be(7);
}
[Test]
public void should_parse_recent_feed_from_torznab_tpb()
{
var recentFeed = ReadAllText(@"Files/RSS/torznab_tpb.xml");
Mocker.GetMock<IHttpClient>()
.Setup(o => o.Execute(It.Is<HttpRequest>(v => v.Method == HttpMethod.GET)))
.Returns<HttpRequest>(r => new HttpResponse(r, new HttpHeader(), recentFeed));
var releases = Subject.FetchRecent();
releases.Should().HaveCount(5);
releases.First().Should().BeOfType<TorrentInfo>();
var releaseInfo = releases.First() as TorrentInfo;
releaseInfo.Title.Should().Be("Series Title S05E02 HDTV x264-Xclusive [eztv]");
releaseInfo.DownloadProtocol.Should().Be(DownloadProtocol.Torrent);
releaseInfo.MagnetUrl.Should().Be("magnet:?xt=urn:btih:9fb267cff5ae5603f07a347676ec3bf3e35f75e1&dn=Game+of+Thrones+S05E02+HDTV+x264-Xclusive+[eztv]&tr=udp:%2F%2Fopen.demonii.com:1337&tr=udp:%2F%2Ftracker.coppersurfer.tk:6969&tr=udp:%2F%2Ftracker.leechers-paradise.org:6969&tr=udp:%2F%2Fexodus.desync.com:6969");
releaseInfo.DownloadUrl.Should().Be("magnet:?xt=urn:btih:9fb267cff5ae5603f07a347676ec3bf3e35f75e1&dn=Game+of+Thrones+S05E02+HDTV+x264-Xclusive+[eztv]&tr=udp:%2F%2Fopen.demonii.com:1337&tr=udp:%2F%2Ftracker.coppersurfer.tk:6969&tr=udp:%2F%2Ftracker.leechers-paradise.org:6969&tr=udp:%2F%2Fexodus.desync.com:6969");
releaseInfo.InfoUrl.Should().Be("https://thepiratebay.se/torrent/11811366/Series_Title_S05E02_HDTV_x264-Xclusive_[eztv]");
releaseInfo.CommentUrl.Should().Be("https://thepiratebay.se/torrent/11811366/Series_Title_S05E02_HDTV_x264-Xclusive_[eztv]");
releaseInfo.Indexer.Should().Be(Subject.Definition.Name);
releaseInfo.PublishDate.Should().Be(DateTime.Parse("Sat, 11 Apr 2015 21:34:00 -0600").ToUniversalTime());
releaseInfo.Size.Should().Be(388895872);
releaseInfo.InfoHash.Should().Be("9fb267cff5ae5603f07a347676ec3bf3e35f75e1");
releaseInfo.Seeders.Should().Be(34128);
releaseInfo.Peers.Should().Be(36724);
}
}
}

View File

@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using FizzWare.NBuilder;
@@ -7,10 +6,7 @@ using Moq;
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.Commands;
using NzbDrone.Core.MediaFiles.EpisodeImport;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using NzbDrone.Test.Common;
@@ -32,8 +28,6 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.GetParentFolder(It.IsAny<string>()))
.Returns((string path) => Directory.GetParent(path).FullName);
}
private void GivenParentFolderExists()
@@ -61,8 +55,8 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
ExceptionVerification.ExpectedWarns(1);
Mocker.GetMock<ICommandExecutor>()
.Verify(v => v.PublishCommand(It.IsAny<CleanMediaFileDb>()), Times.Never());
Mocker.GetMock<IMediaFileTableCleanupService>()
.Verify(v => v.Clean(It.IsAny<Series>()), Times.Never());
}
[Test]
@@ -80,8 +74,8 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
ExceptionVerification.ExpectedWarns(1);
Mocker.GetMock<ICommandExecutor>()
.Verify(v => v.PublishCommand(It.IsAny<CleanMediaFileDb>()), Times.Never());
Mocker.GetMock<IMediaFileTableCleanupService>()
.Verify(v => v.Clean(It.IsAny<Series>()), Times.Never());
}
[Test]
@@ -182,5 +176,91 @@ namespace NzbDrone.Core.Test.MediaFiles.DiskScanServiceTests
Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _series), Times.Once());
}
[Test]
public void should_not_scan_Synology_eaDir()
{
GivenParentFolderExists();
GivenFiles(new List<string>
{
Path.Combine(_series.Path, "@eaDir", "file1.mkv").AsOsAgnostic(),
Path.Combine(_series.Path, "Season 1", "s01e01.mkv").AsOsAgnostic()
});
Subject.Scan(_series);
Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _series), Times.Once());
}
[Test]
public void should_not_scan_thumb_folder()
{
GivenParentFolderExists();
GivenFiles(new List<string>
{
Path.Combine(_series.Path, ".@__thumb", "file1.mkv").AsOsAgnostic(),
Path.Combine(_series.Path, "Season 1", "s01e01.mkv").AsOsAgnostic()
});
Subject.Scan(_series);
Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _series), Times.Once());
}
[Test]
public void should_scan_dotHack_folder()
{
GivenParentFolderExists();
_series.Path = @"C:\Test\TV\.hack".AsOsAgnostic();
GivenFiles(new List<string>
{
Path.Combine(_series.Path, "Season 1", "file1.mkv").AsOsAgnostic(),
Path.Combine(_series.Path, "Season 1", "s01e01.mkv").AsOsAgnostic()
});
Subject.Scan(_series);
Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 2), _series), Times.Once());
}
[Test]
public void should_find_files_at_root_of_series_folder()
{
GivenParentFolderExists();
GivenFiles(new List<string>
{
Path.Combine(_series.Path, "file1.mkv").AsOsAgnostic(),
Path.Combine(_series.Path, "s01e01.mkv").AsOsAgnostic()
});
Subject.Scan(_series);
Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 2), _series), Times.Once());
}
[Test]
public void should_exclude_osx_metadata_files()
{
GivenParentFolderExists();
GivenFiles(new List<string>
{
Path.Combine(_series.Path, "._24 The Status Quo Combustion.mp4").AsOsAgnostic(),
Path.Combine(_series.Path, "24 The Status Quo Combustion.mkv").AsOsAgnostic()
});
Subject.Scan(_series);
Mocker.GetMock<IMakeImportDecision>()
.Verify(v => v.GetImportDecisions(It.Is<List<string>>(l => l.Count == 1), _series), Times.Once());
}
}
}

View File

@@ -202,6 +202,9 @@ namespace NzbDrone.Core.Test.MediaFiles
Mocker.GetMock<IDiskProvider>().Setup(c => c.FolderExists(It.IsAny<string>()))
.Returns(false);
Mocker.GetMock<IDiskProvider>().Setup(c => c.FileExists(It.IsAny<string>()))
.Returns(true);
var fileName = @"C:\folder\file.mkv".AsOsAgnostic();
var result = Subject.ProcessPath(fileName);
@@ -275,7 +278,7 @@ namespace NzbDrone.Core.Test.MediaFiles
imported.Add(new ImportDecision(localEpisode));
var result = Subject.ProcessPath(fileName);
Subject.ProcessPath(fileName);
Mocker.GetMock<IMakeImportDecision>()
.Verify(s => s.GetImportDecisions(It.IsAny<List<String>>(), It.IsAny<Series>(), It.Is<ParsedEpisodeInfo>(v => v.AbsoluteEpisodeNumbers.First() == 9), true), Times.Once());
@@ -291,6 +294,9 @@ namespace NzbDrone.Core.Test.MediaFiles
Mocker.GetMock<IDiskProvider>().Setup(c => c.FolderExists(fileName))
.Returns(false);
Mocker.GetMock<IDiskProvider>().Setup(c => c.FileExists(fileName))
.Returns(true);
var localEpisode = new LocalEpisode();
var imported = new List<ImportDecision>();
@@ -302,6 +308,61 @@ namespace NzbDrone.Core.Test.MediaFiles
.Verify(s => s.GetImportDecisions(It.IsAny<List<String>>(), It.IsAny<Series>(), null, true), Times.Once());
}
[Test]
public void should_not_process_if_file_and_folder_do_not_exist()
{
var folderName = @"C:\media\ba09030e-1234-1234-1234-123456789abc\[HorribleSubs] Maria the Virgin Witch - 09 [720p]".AsOsAgnostic();
Mocker.GetMock<IDiskProvider>().Setup(c => c.FolderExists(folderName))
.Returns(false);
Mocker.GetMock<IDiskProvider>().Setup(c => c.FileExists(folderName))
.Returns(false);
Subject.ProcessPath(folderName).Should().BeEmpty();
Mocker.GetMock<IParsingService>()
.Verify(v => v.GetSeries(It.IsAny<string>()), Times.Never());
ExceptionVerification.ExpectedErrors(1);
}
[Test]
public void should_not_delete_if_no_files_were_imported()
{
GivenValidSeries();
var localEpisode = new LocalEpisode();
var imported = new List<ImportDecision>();
imported.Add(new ImportDecision(localEpisode));
Mocker.GetMock<IMakeImportDecision>()
.Setup(s => s.GetImportDecisions(It.IsAny<List<String>>(), It.IsAny<Series>(), null, true))
.Returns(imported);
Mocker.GetMock<IImportApprovedEpisodes>()
.Setup(s => s.Import(It.IsAny<List<ImportDecision>>(), true, null))
.Returns(new List<ImportResult>());
Mocker.GetMock<IDetectSample>()
.Setup(s => s.IsSample(It.IsAny<Series>(),
It.IsAny<QualityModel>(),
It.IsAny<String>(),
It.IsAny<Int64>(),
It.IsAny<Int32>()))
.Returns(true);
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.GetFileSize(It.IsAny<string>()))
.Returns(15.Megabytes());
Subject.ProcessRootFolder(new DirectoryInfo(_droneFactory));
Mocker.GetMock<IDiskProvider>()
.Verify(v => v.DeleteFolder(It.IsAny<String>(), true), Times.Never());
}
private void VerifyNoImport()
{
Mocker.GetMock<IImportApprovedEpisodes>().Verify(c => c.Import(It.IsAny<List<ImportDecision>>(), true, null),

View File

@@ -63,6 +63,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
{
Series = _series,
Quality = _quality,
Episodes = new List<Episode> { new Episode() },
Path = @"C:\Test\Unsorted\The.Office.S03E115.DVDRip.XviD-OSiTV.avi"
};
@@ -207,7 +208,7 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
Mocker.GetMock<IParsingService>()
.Setup(c => c.GetLocalEpisode(It.IsAny<String>(), It.IsAny<Series>(), It.IsAny<ParsedEpisodeInfo>(), It.IsAny<Boolean>()))
.Throws(new EpisodeNotFoundException("Episode not found"));
.Returns(new LocalEpisode() { Path = "test" });
_videoFiles = new List<String>
{
@@ -218,10 +219,13 @@ namespace NzbDrone.Core.Test.MediaFiles.EpisodeImport
GivenVideoFiles(_videoFiles);
Subject.GetImportDecisions(_videoFiles, _series);
var decisions = Subject.GetImportDecisions(_videoFiles, _series);
Mocker.GetMock<IParsingService>()
.Verify(c => c.GetLocalEpisode(It.IsAny<String>(), It.IsAny<Series>(), It.IsAny<ParsedEpisodeInfo>(), It.IsAny<Boolean>()), Times.Exactly(_videoFiles.Count));
decisions.Should().HaveCount(3);
decisions.First().Rejections.Should().NotBeEmpty();
}
[Test]

View File

@@ -6,7 +6,6 @@ using Moq;
using NUnit.Framework;
using NzbDrone.Common.Disk;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.Commands;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
@@ -16,6 +15,7 @@ namespace NzbDrone.Core.Test.MediaFiles
{
private const string DELETED_PATH = "ANY FILE WITH THIS PATH IS CONSIDERED DELETED!";
private List<Episode> _episodes;
private Series _series;
[SetUp]
public void SetUp()
@@ -24,9 +24,8 @@ namespace NzbDrone.Core.Test.MediaFiles
.Build()
.ToList();
Mocker.GetMock<ISeriesService>()
.Setup(s => s.GetSeries(It.IsAny<Int32>()))
.Returns(Builder<Series>.CreateNew().Build());
_series = Builder<Series>.CreateNew()
.Build();
Mocker.GetMock<IDiskProvider>()
.Setup(e => e.FileExists(It.Is<String>(c => !c.Contains(DELETED_PATH))))
@@ -61,7 +60,7 @@ namespace NzbDrone.Core.Test.MediaFiles
GivenEpisodeFiles(episodeFiles);
Subject.Execute(new CleanMediaFileDb(0));
Subject.Clean(_series);
Mocker.GetMock<IEpisodeService>().Verify(c => c.UpdateEpisode(It.IsAny<Episode>()), Times.Never());
}
@@ -76,7 +75,7 @@ namespace NzbDrone.Core.Test.MediaFiles
GivenEpisodeFiles(episodeFiles);
Subject.Execute(new CleanMediaFileDb(0));
Subject.Clean(_series);
Mocker.GetMock<IMediaFileService>().Verify(c => c.Delete(It.Is<EpisodeFile>(e => e.RelativePath == DELETED_PATH), DeleteMediaFileReason.MissingFromDisk), Times.Exactly(2));
}
@@ -92,7 +91,7 @@ namespace NzbDrone.Core.Test.MediaFiles
GivenEpisodeFiles(episodeFiles);
GivenFilesAreNotAttachedToEpisode();
Subject.Execute(new CleanMediaFileDb(0));
Subject.Clean(_series);
Mocker.GetMock<IMediaFileService>().Verify(c => c.Delete(It.IsAny<EpisodeFile>(), DeleteMediaFileReason.NoLinkedEpisodes), Times.Exactly(10));
}
@@ -102,7 +101,7 @@ namespace NzbDrone.Core.Test.MediaFiles
{
GivenEpisodeFiles(new List<EpisodeFile>());
Subject.Execute(new CleanMediaFileDb(0));
Subject.Clean(_series);
Mocker.GetMock<IEpisodeService>().Verify(c => c.UpdateEpisode(It.Is<Episode>(e => e.EpisodeFileId == 0)), Times.Exactly(10));
}
@@ -117,7 +116,7 @@ namespace NzbDrone.Core.Test.MediaFiles
GivenEpisodeFiles(episodeFiles);
Subject.Execute(new CleanMediaFileDb(0));
Subject.Clean(_series);
Mocker.GetMock<IEpisodeService>().Verify(c => c.UpdateEpisode(It.IsAny<Episode>()), Times.Never());
}

View File

@@ -19,6 +19,10 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.FileExists(It.IsAny<string>()))
.Returns(true);
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.OpenReadStream(It.IsAny<string>()))
.Returns<string>(s => new FileStream(s, FileMode.Open, FileAccess.Read));
}
[Test]
@@ -54,5 +58,47 @@ namespace NzbDrone.Core.Test.MediaFiles.MediaInfo
info.Width.Should().Be(480);
}
[Test]
public void get_info_unicode()
{
var srcPath = Path.Combine(Directory.GetCurrentDirectory(), "Files", "Media", "H264_sample.mp4");
var tempPath = GetTempFilePath();
Directory.CreateDirectory(tempPath);
var path = Path.Combine(tempPath, "H264_Pok\u00E9mon.mkv");
File.Copy(srcPath, path);
var info = Subject.GetMediaInfo(path);
info.AudioBitrate.Should().Be(128000);
info.AudioChannels.Should().Be(2);
info.AudioFormat.Should().Be("AAC");
info.AudioLanguages.Should().Be("English");
info.AudioProfile.Should().Be("LC");
info.Height.Should().Be(320);
info.RunTime.Seconds.Should().Be(10);
info.ScanType.Should().Be("Progressive");
info.Subtitles.Should().Be("");
info.VideoBitrate.Should().Be(193329);
info.VideoCodec.Should().Be("AVC");
info.VideoFps.Should().Be(24);
info.Width.Should().Be(480);
}
[Test]
public void should_dispose_file_after_scanning_mediainfo()
{
var path = Path.Combine(Directory.GetCurrentDirectory(), "Files", "Media", "H264_sample.mp4");
var info = Subject.GetMediaInfo(path);
var stream = new FileStream(path, FileMode.Open, FileAccess.Write);
stream.Close();
}
}
}

View File

@@ -1,121 +1,121 @@
using System;
using System.Collections.Generic;
using Moq;
using NUnit.Framework;
using NzbDrone.Common;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Commands.Tracking;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.Messaging.Commands
{
[TestFixture]
public class CommandExecutorFixture : TestBase<CommandExecutor>
{
private Mock<IExecute<CommandA>> _executorA;
private Mock<IExecute<CommandB>> _executorB;
[SetUp]
public void Setup()
{
_executorA = new Mock<IExecute<CommandA>>();
_executorB = new Mock<IExecute<CommandB>>();
Mocker.GetMock<IServiceFactory>()
.Setup(c => c.Build(typeof(IExecute<CommandA>)))
.Returns(_executorA.Object);
Mocker.GetMock<IServiceFactory>()
.Setup(c => c.Build(typeof(IExecute<CommandB>)))
.Returns(_executorB.Object);
Mocker.GetMock<ITrackCommands>()
.Setup(c => c.FindExisting(It.IsAny<Command>()))
.Returns<Command>(null);
}
[Test]
public void should_publish_command_to_executor()
{
var commandA = new CommandA();
Subject.PublishCommand(commandA);
_executorA.Verify(c => c.Execute(commandA), Times.Once());
}
[Test]
public void should_publish_command_by_with_optional_arg_using_name()
{
Mocker.GetMock<IServiceFactory>().Setup(c => c.GetImplementations(typeof(Command)))
.Returns(new List<Type> { typeof(CommandA), typeof(CommandB) });
Subject.PublishCommand(typeof(CommandA).FullName);
_executorA.Verify(c => c.Execute(It.IsAny<CommandA>()), Times.Once());
}
[Test]
public void should_not_publish_to_incompatible_executor()
{
var commandA = new CommandA();
Subject.PublishCommand(commandA);
_executorA.Verify(c => c.Execute(commandA), Times.Once());
_executorB.Verify(c => c.Execute(It.IsAny<CommandB>()), Times.Never());
}
[Test]
public void broken_executor_should_throw_the_exception()
{
var commandA = new CommandA();
_executorA.Setup(c => c.Execute(It.IsAny<CommandA>()))
.Throws(new NotImplementedException());
Assert.Throws<NotImplementedException>(() => Subject.PublishCommand(commandA));
}
[Test]
public void broken_executor_should_publish_executed_event()
{
var commandA = new CommandA();
_executorA.Setup(c => c.Execute(It.IsAny<CommandA>()))
.Throws(new NotImplementedException());
Assert.Throws<NotImplementedException>(() => Subject.PublishCommand(commandA));
VerifyEventPublished<CommandExecutedEvent>();
}
[Test]
public void should_publish_executed_event_on_success()
{
var commandA = new CommandA();
Subject.PublishCommand(commandA);
VerifyEventPublished<CommandExecutedEvent>();
}
}
public class CommandA : Command
{
public CommandA(int id = 0)
{
}
}
public class CommandB : Command
{
public CommandB()
{
}
}
}
//using System;
//using System.Collections.Generic;
//using Moq;
//using NUnit.Framework;
//using NzbDrone.Common;
//using NzbDrone.Core.Messaging.Commands;
//using NzbDrone.Core.Messaging.Commands.Tracking;
//using NzbDrone.Core.Messaging.Events;
//using NzbDrone.Test.Common;
//
//namespace NzbDrone.Core.Test.Messaging.Commands
//{
// [TestFixture]
// public class CommandExecutorFixture : TestBase<CommandExecutor>
// {
// private Mock<IExecute<CommandA>> _executorA;
// private Mock<IExecute<CommandB>> _executorB;
//
// [SetUp]
// public void Setup()
// {
// _executorA = new Mock<IExecute<CommandA>>();
// _executorB = new Mock<IExecute<CommandB>>();
//
// Mocker.GetMock<IServiceFactory>()
// .Setup(c => c.Build(typeof(IExecute<CommandA>)))
// .Returns(_executorA.Object);
//
// Mocker.GetMock<IServiceFactory>()
// .Setup(c => c.Build(typeof(IExecute<CommandB>)))
// .Returns(_executorB.Object);
//
//
// Mocker.GetMock<ITrackCommands>()
// .Setup(c => c.FindExisting(It.IsAny<Command>()))
// .Returns<Command>(null);
// }
//
// [Test]
// public void should_publish_command_to_executor()
// {
// var commandA = new CommandA();
//
// Subject.Push(commandA);
//
// _executorA.Verify(c => c.Execute(commandA), Times.Once());
// }
//
// [Test]
// public void should_publish_command_by_with_optional_arg_using_name()
// {
// Mocker.GetMock<IServiceFactory>().Setup(c => c.GetImplementations(typeof(Command)))
// .Returns(new List<Type> { typeof(CommandA), typeof(CommandB) });
//
// Subject.Push(typeof(CommandA).FullName);
// _executorA.Verify(c => c.Execute(It.IsAny<CommandA>()), Times.Once());
// }
//
//
// [Test]
// public void should_not_publish_to_incompatible_executor()
// {
// var commandA = new CommandA();
//
// Subject.Push(commandA);
//
// _executorA.Verify(c => c.Execute(commandA), Times.Once());
// _executorB.Verify(c => c.Execute(It.IsAny<CommandB>()), Times.Never());
// }
//
// [Test]
// public void broken_executor_should_throw_the_exception()
// {
// var commandA = new CommandA();
//
// _executorA.Setup(c => c.Execute(It.IsAny<CommandA>()))
// .Throws(new NotImplementedException());
//
// Assert.Throws<NotImplementedException>(() => Subject.Push(commandA));
// }
//
//
// [Test]
// public void broken_executor_should_publish_executed_event()
// {
// var commandA = new CommandA();
//
// _executorA.Setup(c => c.Execute(It.IsAny<CommandA>()))
// .Throws(new NotImplementedException());
//
// Assert.Throws<NotImplementedException>(() => Subject.Push(commandA));
//
// VerifyEventPublished<CommandExecutedEvent>();
// }
//
// [Test]
// public void should_publish_executed_event_on_success()
// {
// var commandA = new CommandA();
// Subject.Push(commandA);
//
// VerifyEventPublished<CommandExecutedEvent>();
// }
// }
//
// public class CommandA : Command
// {
// public CommandA(int id = 0)
// {
// }
// }
//
// public class CommandB : Command
// {
//
// public CommandB()
// {
// }
// }
//
//}

View File

@@ -1,19 +0,0 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.Update.Commands;
namespace NzbDrone.Core.Test.Messaging.Commands
{
[TestFixture]
public class CommandFixture
{
[Test]
public void default_values()
{
var command = new ApplicationUpdateCommand();
command.Id.Should().NotBe(0);
command.Name.Should().Be("ApplicationUpdate");
}
}
}

View File

@@ -0,0 +1,55 @@
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.Download;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.Messaging.Commands
{
[TestFixture]
public class CommandQueueManagerFixture : CoreTest<CommandQueueManager>
{
[SetUp]
public void Setup()
{
var id = 0;
var commands = new List<CommandModel>();
Mocker.GetMock<ICommandRepository>()
.Setup(s => s.Insert(It.IsAny<CommandModel>()))
.Returns<CommandModel>(c =>
{
c.Id = id + 1;
commands.Add(c);
id++;
return c;
});
Mocker.GetMock<ICommandRepository>()
.Setup(s => s.Get(It.IsAny<int>()))
.Returns<int>(c =>
{
return commands.SingleOrDefault(e => e.Id == c);
});
}
[Test]
public void should_not_remove_commands_for_five_minutes_after_they_end()
{
var command = Subject.Push<CheckForFinishedDownloadCommand>(new CheckForFinishedDownloadCommand());
Subject.Start(command);
Subject.Complete(command, "All done");
Subject.CleanCommands();
Subject.Get(command.Id).Should().NotBeNull();
Mocker.GetMock<ICommandRepository>()
.Verify(v => v.Get(It.IsAny<int>()), Times.Never());
}
}
}

View File

@@ -6,7 +6,7 @@ using NzbDrone.Core.MetadataSource;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
namespace NzbDrone.Core.Test.MetadataSourceTests
namespace NzbDrone.Core.Test.MetadataSource
{
[TestFixture]
public class SearchSeriesComparerFixture : CoreTest

View File

@@ -1,21 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.MetadataSource;
using NzbDrone.Core.MetadataSource.SkyHook;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using NzbDrone.Test.Common;
using NzbDrone.Test.Common.Categories;
namespace NzbDrone.Core.Test.MetadataSourceTests
namespace NzbDrone.Core.Test.MetadataSource.SkyHook
{
[TestFixture]
[IntegrationTest]
public class TvdbDataProxyFixture : CoreTest<TvDbProxy>
public class SkyHookProxySearchFixture : CoreTest<SkyHookProxy>
{
[SetUp]
public void Setup()

View File

@@ -0,0 +1,102 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Moq;
using NUnit.Framework;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.Notifications;
using NzbDrone.Core.Notifications.Synology;
using NzbDrone.Core.Test.Framework;
using NzbDrone.Core.Tv;
using NzbDrone.Test.Common;
namespace NzbDrone.Core.Test.NotificationTests
{
[TestFixture]
public class SynologyIndexerFixture : CoreTest<SynologyIndexer>
{
private Series _series;
private DownloadMessage _upgrade;
[SetUp]
public void SetUp()
{
_series = new Series()
{
Path = @"C:\Test\".AsOsAgnostic()
};
_upgrade = new DownloadMessage()
{
Series = _series,
EpisodeFile = new EpisodeFile
{
RelativePath = "file1.S01E01E02.mkv"
},
OldFiles = new List<EpisodeFile>
{
new EpisodeFile
{
RelativePath = "file1.S01E01.mkv"
},
new EpisodeFile
{
RelativePath = "file1.S01E02.mkv"
}
}
};
Subject.Definition = new NotificationDefinition
{
Settings = new SynologyIndexerSettings
{
UpdateLibrary = true
}
};
}
[Test]
public void should_not_update_library_if_disabled()
{
(Subject.Definition.Settings as SynologyIndexerSettings).UpdateLibrary = false;
Subject.AfterRename(_series);
Mocker.GetMock<ISynologyIndexerProxy>()
.Verify(v => v.UpdateFolder(_series.Path), Times.Never());
}
[Test]
public void should_remove_old_episodes_on_upgrade()
{
Subject.OnDownload(_upgrade);
Mocker.GetMock<ISynologyIndexerProxy>()
.Verify(v => v.DeleteFile(@"C:\Test\file1.S01E01.mkv".AsOsAgnostic()), Times.Once());
Mocker.GetMock<ISynologyIndexerProxy>()
.Verify(v => v.DeleteFile(@"C:\Test\file1.S01E02.mkv".AsOsAgnostic()), Times.Once());
}
[Test]
public void should_add_new_episode_on_upgrade()
{
Subject.OnDownload(_upgrade);
Mocker.GetMock<ISynologyIndexerProxy>()
.Verify(v => v.AddFile(@"C:\Test\file1.S01E01E02.mkv".AsOsAgnostic()), Times.Once());
}
[Test]
public void should_update_entire_series_folder_on_rename()
{
Subject.AfterRename(_series);
Mocker.GetMock<ISynologyIndexerProxy>()
.Verify(v => v.UpdateFolder(@"C:\Test\".AsOsAgnostic()), Times.Once());
}
}
}

View File

@@ -147,6 +147,7 @@
<Compile Include="DecisionEngineTests\RssSync\DelaySpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\RssSync\ProperSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\Search\SeriesSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\SameEpisodesSpecificationFixture.cs" />
<Compile Include="DecisionEngineTests\UpgradeDiskSpecificationFixture.cs" />
<Compile Include="Download\CompletedDownloadServiceFixture.cs" />
<Compile Include="Download\DownloadApprovedReportsTests\DownloadApprovedFixture.cs" />
@@ -238,9 +239,11 @@
<Compile Include="MediaFiles\EpisodeImport\Specifications\UpgradeSpecificationFixture.cs" />
<Compile Include="MediaFiles\ImportApprovedEpisodesFixture.cs" />
<Compile Include="MediaFiles\MediaFileRepositoryFixture.cs" />
<Compile Include="MetadataSourceTests\TvdbDataProxyFixture.cs" />
<Compile Include="MetadataSourceTests\SearchSeriesComparerFixture.cs" />
<Compile Include="Messaging\Commands\CommandQueueManagerFixture.cs" />
<Compile Include="MetadataSource\SkyHook\SkyHookProxySearchFixture.cs" />
<Compile Include="MetadataSource\SearchSeriesComparerFixture.cs" />
<Compile Include="MetadataSource\SkyHook\SkyHookProxyFixture.cs" />
<Compile Include="NotificationTests\SynologyIndexerFixture.cs" />
<Compile Include="OrganizerTests\FileNameBuilderTests\CleanTitleFixture.cs" />
<Compile Include="OrganizerTests\FileNameBuilderTests\EpisodeTitleCollapseFixture.cs" />
<Compile Include="OrganizerTests\FileNameBuilderTests\MultiEpisodeFixture.cs" />
@@ -257,7 +260,6 @@
<Compile Include="MediaFiles\UpgradeMediaFileServiceFixture.cs" />
<Compile Include="Messaging\Commands\CommandEqualityComparerFixture.cs" />
<Compile Include="Messaging\Commands\CommandExecutorFixture.cs" />
<Compile Include="Messaging\Commands\CommandFixture.cs" />
<Compile Include="Messaging\Events\EventAggregatorFixture.cs" />
<Compile Include="Metadata\Consumers\Roksbox\FindMetadataFileFixture.cs" />
<Compile Include="Metadata\Consumers\Wdtv\FindMetadataFileFixture.cs" />
@@ -448,6 +450,9 @@
<Content Include="Files\RSS\SizeParsing\omgwtfnzbs.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Files\RSS\torznab_tpb.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="Files\RSS\wombles.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<SubType>Designer</SubType>
@@ -501,7 +506,7 @@
<PostBuildEvent>
</PostBuildEvent>
</PropertyGroup>
<Import Project="$(SolutionDir)\.nuget\nuget.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">

View File

@@ -77,6 +77,10 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase("[Jumonji-Giri]_[F-B]_Kagihime_Monogatari_Eikyuu_Alice_Rondo_Ep08_(8246e542).mkv", "Kagihime Monogatari Eikyuu Alice Rondo", 8, 0, 0)]
[TestCase("Knights of Sidonia - 01 [1080p 10b DTSHD-MA eng sub].mkv", "Knights of Sidonia", 1, 0, 0)]
[TestCase("Series Title (2010) {01} Episode Title (1).hdtv-720p", "Series Title (2010)", 1, 0, 0)]
[TestCase("[HorribleSubs] Shirobako - 20 [720p].mkv", "Shirobako", 20, 0, 0)]
[TestCase("[Hatsuyuki] Dragon Ball Kai (2014) - 017 (115) [1280x720][B2CFBC0F]", "Dragon Ball Kai 2014", 17, 0, 0)]
[TestCase("[Hatsuyuki] Dragon Ball Kai (2014) - 018 (116) [1280x720][C4A3B16E]", "Dragon Ball Kai 2014", 18, 0, 0)]
[TestCase("Dragon Ball Kai (2014) - 39 (137) [v2][720p.HDTV][Unison Fansub]", "Dragon Ball Kai 2014", 39, 0, 0)]
//[TestCase("", "", 0, 0, 0)]
public void should_parse_absolute_numbers(string postTitle, string title, int absoluteEpisodeNumber, int seasonNumber, int episodeNumber)
{

View File

@@ -186,28 +186,6 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
.Verify(v => v.FindEpisode(It.IsAny<Int32>(), It.IsAny<Int32>(), It.IsAny<Int32>()), Times.Once());
}
[Test]
public void should_use_scene_numbering_when_season_0_for_anime()
{
GivenAbsoluteNumberingSeries();
Mocker.GetMock<ISceneMappingService>()
.Setup(s => s.GetSeasonNumber(_parsedEpisodeInfo.SeriesTitle))
.Returns(0);
Mocker.GetMock<IEpisodeService>()
.Setup(s => s.FindEpisodesBySceneNumbering(It.IsAny<Int32>(), 0, It.IsAny<Int32>()))
.Returns(new List<Episode>());
Subject.GetEpisodes(_parsedEpisodeInfo, _series, true, null);
Mocker.GetMock<IEpisodeService>()
.Verify(v => v.FindEpisodesBySceneNumbering(It.IsAny<Int32>(), 0, It.IsAny<Int32>()), Times.Once());
Mocker.GetMock<IEpisodeService>()
.Verify(v => v.FindEpisode(It.IsAny<Int32>(), 0, It.IsAny<Int32>()), Times.Once());
}
[Test]
public void should_look_for_episode_in_season_zero_if_absolute_special()
{
@@ -223,5 +201,77 @@ namespace NzbDrone.Core.Test.ParserTests.ParsingServiceTests
Mocker.GetMock<IEpisodeService>()
.Verify(v => v.FindEpisode(It.IsAny<Int32>(), 0, It.IsAny<Int32>()), Times.Once());
}
[TestCase(0)]
[TestCase(1)]
[TestCase(2)]
public void should_use_scene_numbering_when_scene_season_number_has_value(int seasonNumber)
{
GivenAbsoluteNumberingSeries();
Mocker.GetMock<ISceneMappingService>()
.Setup(s => s.GetSeasonNumber(_parsedEpisodeInfo.SeriesTitle))
.Returns(seasonNumber);
Mocker.GetMock<IEpisodeService>()
.Setup(s => s.FindEpisodesBySceneNumbering(It.IsAny<Int32>(), seasonNumber, It.IsAny<Int32>()))
.Returns(new List<Episode>());
Subject.GetEpisodes(_parsedEpisodeInfo, _series, true, null);
Mocker.GetMock<IEpisodeService>()
.Verify(v => v.FindEpisodesBySceneNumbering(It.IsAny<Int32>(), seasonNumber, It.IsAny<Int32>()), Times.Once());
Mocker.GetMock<IEpisodeService>()
.Verify(v => v.FindEpisode(It.IsAny<Int32>(), seasonNumber, It.IsAny<Int32>()), Times.Once());
}
[TestCase(0)]
[TestCase(1)]
[TestCase(2)]
public void should_find_episode_by_season_and_scene_absolute_episode_number(int seasonNumber)
{
GivenAbsoluteNumberingSeries();
Mocker.GetMock<ISceneMappingService>()
.Setup(s => s.GetSeasonNumber(_parsedEpisodeInfo.SeriesTitle))
.Returns(seasonNumber);
Mocker.GetMock<IEpisodeService>()
.Setup(s => s.FindEpisodesBySceneNumbering(It.IsAny<Int32>(), seasonNumber, It.IsAny<Int32>()))
.Returns(new List<Episode> { _episodes.First() });
Subject.GetEpisodes(_parsedEpisodeInfo, _series, true, null);
Mocker.GetMock<IEpisodeService>()
.Verify(v => v.FindEpisodesBySceneNumbering(It.IsAny<Int32>(), seasonNumber, It.IsAny<Int32>()), Times.Once());
Mocker.GetMock<IEpisodeService>()
.Verify(v => v.FindEpisode(It.IsAny<Int32>(), seasonNumber, It.IsAny<Int32>()), Times.Never());
}
[TestCase(0)]
[TestCase(1)]
[TestCase(2)]
public void should_find_episode_by_season_and_absolute_episode_number_when_scene_absolute_episode_number_returns_multiple_results(int seasonNumber)
{
GivenAbsoluteNumberingSeries();
Mocker.GetMock<ISceneMappingService>()
.Setup(s => s.GetSeasonNumber(_parsedEpisodeInfo.SeriesTitle))
.Returns(seasonNumber);
Mocker.GetMock<IEpisodeService>()
.Setup(s => s.FindEpisodesBySceneNumbering(It.IsAny<Int32>(), seasonNumber, It.IsAny<Int32>()))
.Returns(Builder<Episode>.CreateListOfSize(5).Build().ToList());
Subject.GetEpisodes(_parsedEpisodeInfo, _series, true, null);
Mocker.GetMock<IEpisodeService>()
.Verify(v => v.FindEpisodesBySceneNumbering(It.IsAny<Int32>(), seasonNumber, It.IsAny<Int32>()), Times.Once());
Mocker.GetMock<IEpisodeService>()
.Verify(v => v.FindEpisode(It.IsAny<Int32>(), seasonNumber, It.IsAny<Int32>()), Times.Once());
}
}
}

View File

@@ -30,6 +30,7 @@ namespace NzbDrone.Core.Test.ParserTests
[TestCase(@"C:\Test\Series\Season 01\1 Pilot (1080p HD).mkv", 1, 1)]
[TestCase(@"C:\Test\Series\Season 1\02 Honor Thy Father (1080p HD).m4v", 1, 2)]
[TestCase(@"C:\Test\Series\Season 1\2 Honor Thy Father (1080p HD).m4v", 1, 2)]
// [TestCase(@"C:\CSI.NY.S02E04.720p.WEB-DL.DD5.1.H.264\73696S02-04.mkv", 2, 4)] //Gets treated as S01E04 (because it gets parsed as anime)
public void should_parse_from_path(string path, int season, int episode)
{
var result = Parser.Parser.ParsePath(path.AsOsAgnostic());

View File

@@ -27,6 +27,11 @@ namespace NzbDrone.Core.Test.ProviderTests.DiskScanProviderTests
@"C:\Test\movie"
};
GivenFiles();
}
private void GivenFiles()
{
Mocker.GetMock<IDiskProvider>()
.Setup(s => s.GetFiles(It.IsAny<String>(), SearchOption.AllDirectories))
.Returns(_files);
@@ -69,7 +74,7 @@ namespace NzbDrone.Core.Test.ProviderTests.DiskScanProviderTests
public void should_return_video_files_only()
{
var path = @"C:\Test\";
var test = Subject.GetVideoFiles(path);
Subject.GetVideoFiles(path).Should().HaveCount(4);
}
}

View File

@@ -33,7 +33,7 @@ namespace NzbDrone.Core.Test.TvTests.EpisodeRepositoryTests
[Test]
public void should_get_episodes()
{
var episodes = Subject.EpisodesBetweenDates(DateTime.Today.AddDays(-1), DateTime.Today.AddDays(3));
var episodes = Subject.EpisodesBetweenDates(DateTime.Today.AddDays(-1), DateTime.Today.AddDays(3), false);
episodes.Should().HaveCount(1);
}
}

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