1
0
mirror of https://github.com/Sonarr/Sonarr.git synced 2026-03-05 13:20:20 -05:00

Compare commits

...

192 Commits

Author SHA1 Message Date
Taloth Saldono
713e109bc9 Fixed api blacklist, history, wanted/missing and wanted/cutoff requiring sortKey, now defaulting to an appropriate value (airDateUtc/date desc for most).
fixes #1452
2016-09-10 23:05:34 +02:00
Taloth Saldono
4bf3ef45b0 Fixed: Auto-Updater rollback logic tries to restore unchanged files. 2016-09-10 12:56:27 +02:00
Taloth Saldono
09530b238f Fixed: Removed requirement to disable sabnzbd pre-check for sab version 1.1.0 onward. 2016-09-07 19:58:18 +02:00
Taloth Saldono
5414dadffc Fixed stupid error. 2016-09-03 12:57:30 +02:00
Mark McDowall
5482fa3ae0 New: Support for TLS 1.1 and 1.2 connections when only .net 4.5 is installed. 2016-09-03 12:11:06 +02:00
Taloth Saldono
6e8480d7cb Added additional categories to NZBFinder preset. 2016-09-03 11:57:10 +02:00
Ashley Broughton
7c7cfc0b7d Changed exit statement to "Press enter to exit..." to match use of ReadLine() (#1425) 2016-09-03 11:53:56 +02:00
Taloth Saldono
4e051bfde2 Merge branch 'pr/n1446_Awarua-' into develop 2016-09-03 11:48:39 +02:00
Taloth Saldono
356e14ac5b Cleanup 2016-09-03 11:48:23 +02:00
Taloth Saldono
97d0ddb6e9 Fixed: Sabnzbd 1.1.0 rc4 queue api changed time format. 2016-09-03 11:42:40 +02:00
Björn Dahlgren
17f6841426 Fixed: Updated HDBits api to api changes.
Closes #1448
2016-09-03 11:18:19 +02:00
Dion Woolley
a6a7732cd5 Fixed issue #1445 where sabnzbd has changed the time format reported for downloads 2016-09-03 13:13:55 +12:00
Mark McDowall
aa37b65842 Fixed tests for EpisodeFileMovingService 2016-09-02 12:26:04 -07:00
Mark McDowall
6ac9e5ec18 Don't blow if driveFormat is null when looking up DriveType
Closes #1444
2016-08-30 22:23:49 -07:00
Mark McDowall
2d54ca5d47 New: Ability to include unmonitored episodes in the iCAL feed
Closes #1442
2016-08-29 21:28:45 -07:00
Mark McDowall
422371d118 Fixed: Episode import when the series folder had a trailing slash and folder was not on disk 2016-08-29 21:22:25 -07:00
Mark McDowall
77574ec555 Fixed: Parsing absolute episode numbers preceeded by Episode
Closes #1441
2016-08-29 11:59:50 -07:00
Mark McDowall
3b385e8738 Fixed: Parsing of a multi-episode anime formats 2016-08-25 21:46:22 -07:00
Taloth Saldono
7f6101a6bc Better Captcha message. 2016-08-20 19:37:47 +02:00
Mark McDowall
28f6777f9a Fixed media info test 2016-08-16 21:12:18 -07:00
Chris Allen
e7275af073 VideoBitDepth and AudioChannels in Filename examples 2016-08-16 16:48:28 -07:00
Mark McDowall
5fcd65ef57 Fixed: MediaInfo.AudioChannels for some eac3 and mp3 files 2016-08-16 16:46:27 -07:00
Taloth Saldono
222ed1eb4b Changed startup loglevel to avoid incorrect sonarr.debug/trace log entry. 2016-08-16 22:10:03 +02:00
Taloth Saldono
4486317888 Fixed: Manual Import failing to parse series title from certain nested folders. 2016-08-14 21:05:35 +02:00
Taloth Saldono
206ff12b71 Added .editorconfig 2016-08-13 22:22:24 +02:00
Taloth Saldono
73fb216e6f New: Added CAPTCHA support to Rarbg. 2016-08-13 22:10:42 +02:00
Mark McDowall
e9eab0ae48 Audio codec E-AC-3 will be EAC3 in filenames 2016-08-13 11:40:12 -07:00
Christopher Heath
626d94d435 New: Join notifications
Closes #1197
2016-08-13 11:37:55 -07:00
Mark McDowall
e4adc1d3a1 Fixed: Include afpfs mount points in free space checks
Fixes #1399
2016-08-13 11:27:58 -07:00
Mark McDowall
c3f9a0336c Move platform files to proper folders 2016-08-11 20:30:46 -07:00
Mark McDowall
04ca1e4569 Always include decimal place for AudioChannels 2016-08-11 18:06:03 -07:00
Mark McDowall
459715b9b4 Fixed UpdateMediaInfoService tests 2016-08-11 16:20:40 -07:00
Mark McDowall
db4b0de5e2 New: MediaInfo VideoBitDepth and AudioChannels 2016-08-11 15:56:35 -07:00
Taloth Saldono
914f799f9d New: Added support to save .magnet to blackhole directory.
fixes #1406
2016-08-05 23:10:15 +02:00
Taloth Saldono
e2272dcca3 Reject m2ts (bluray) raw releases from BTN as well. 2016-08-05 22:18:33 +02:00
Taloth Saldono
2b1c97ffa4 Fixed: AnimoTosho RSS feed size parsing.
Also added handling for multiple enclosure elements.

ref #1384
2016-08-04 19:36:13 +02:00
Taloth Saldono
b80d6c74ad Api errors now log statuscode too. 2016-07-31 23:40:07 +02:00
Taloth Saldono
6f2dd5d2fa Improved Quality lookup. 2016-07-31 14:43:08 +02:00
Mark McDowall
1218dd255f Fixed: Allow series to be added unmonitored through the API
Closes #1404
2016-07-27 12:08:49 -07:00
Mark McDowall
5bf9b069fc Fixed: Slack Icon is optional 2016-07-27 11:31:51 -07:00
Mark McDowall
2ab9bb4fce Fixed: Incorrect matching of date in title
Closes #1401
2016-07-26 22:31:03 -07:00
Taloth Saldono
87d00abdf1 Adjusted Gzip stream to reduce response times. 2016-07-24 01:00:48 +02:00
Taloth Saldono
19aded7a15 Order the pipeline registration process. 2016-07-24 01:00:44 +02:00
Taloth Saldono
a2536deef0 Fixed: Significantly improved api performance. 2016-07-24 01:00:41 +02:00
Taloth Saldono
ff6737314f Added additional index to episodes table to speed up certain queries. 2016-07-24 01:00:39 +02:00
Taloth Saldono
4fc150f77b Reset srcset on failed load. 2016-07-24 01:00:37 +02:00
Taloth Saldono
90b5947a19 Fixed Deluge and BTN cleanse password logic. 2016-07-24 01:00:36 +02:00
Taloth Saldono
c7d445d1c1 New: Added raw DVD check for BTN to prevent those pesky VIDEO_TS downloads.
fixes #r1377
2016-07-24 01:00:34 +02:00
Taloth Saldono
c4a3bc3d2f Fixed cookie tests with expired dates. 2016-07-22 00:34:18 +02:00
Taloth Saldono
8966e5a403 Removed fanzub tests and disabled kickass tests. 2016-07-22 00:03:35 +02:00
Taloth Saldono
bc94a7f921 Fixed: Forcibly disable kat when using the wrong domain. 2016-07-21 23:29:13 +02:00
Taloth Saldono
f3cbc2bdd2 Fixed: Calendar arrows on Edge browser. 2016-07-21 19:29:09 +02:00
Taloth Saldono
f681d43601 Fixed: XEM series with only alternate season titles. 2016-07-21 19:28:42 +02:00
Mark McDowall
b232cc3081 Fixed: Delete all history for series when series is removed from Sonarr 2016-07-20 15:53:49 -07:00
Mark McDowall
857d661ff1 Fixed: Recreate log database if migration fails
Fixes: #1050
2016-07-19 18:57:36 -07:00
Mark McDowall
8255fb0b28 GHI and PR Templates 2016-07-07 12:44:32 -07:00
Mark McDowall
ab63c3e83d Fixed: Parsing series title during manual import 2016-07-03 23:32:25 -07:00
Mark McDowall
c1f59a55c6 Fixed: Kodi metadata AC3 audio codec
Closes #1344
2016-07-02 00:22:58 -07:00
Mark McDowall
c8474701a0 Fixed: Parsing Plex Media Server version 1.0 2016-07-02 00:19:31 -07:00
Mark McDowall
c206b83318 Fixed spelling mistake 2016-07-01 23:37:27 -07:00
Mark McDowall
a8b9a47f5f Emby metadata name changes 2016-07-01 23:29:32 -07:00
vintage81
3e9a159466 Fixed: Adding label to torrents in qBittorrent v3.3.5
Fixes #1347
2016-06-29 18:48:18 -07:00
Mark McDowall
ba817557ba Fixed: More results in season searches when KAT 2016-06-29 18:13:31 -07:00
Fossil
30ed3a4a80 Updated NZBFinder URL 2016-06-25 23:39:21 -07:00
Taloth Saldono
006dc9202b Fixed: Tweaked sample detection for short episodes. 2016-06-25 22:50:16 +02:00
Mark McDowall
c2a2746ccf Fixed: Roksbox metadata generation when series certification is not available 2016-06-20 01:07:35 -07:00
Taloth Saldono
2fa0729158 Fixed series filter 'missing' since percentOfEpisodes isn't always set. 2016-06-19 00:28:01 +02:00
Taloth Saldono
b3eee50892 Fixed relative path issue in Metadata handling. 2016-06-18 23:15:02 +02:00
Mark McDowall
f1c007c5fe Include full grab/import message for Slack notifications 2016-06-18 10:57:18 -07:00
Mark McDowall
7c8a8f8e55 AsOsAgnostic 2016-06-16 00:45:33 -07:00
aaraujo666
a3ade09964 New: Missing episodes series filter 2016-06-16 00:00:36 -07:00
Mark McDowall
8e429239a8 Fixed: Metadata files incorrectly stored in database 2016-06-15 23:53:23 -07:00
Taloth Saldono
4783803b6b New: Part One/Two/Three/.. parser support for mini series. 2016-06-15 21:25:08 +02:00
Taloth Saldono
b320a23bf8 Added better log message when indexer reached daily grab limit. 2016-06-14 23:15:41 +02:00
Taloth Saldono
32a347bdd2 Updated WEB regex. 2016-06-14 23:13:50 +02:00
Björn Dahlgren
a0b0f6162f New: Kodi metadata Stream Details
Closes #1286
2016-06-14 00:39:22 -07:00
Martin Hartvig
6c287f118f New: Slack notifications
Closes #1002
2016-06-13 23:35:25 -07:00
Mark McDowall
8213f020ff Removed 6box 2016-06-13 22:08:22 -07:00
Mark McDowall
d984dd41d6 Fixed: Parsing WEB releases that use spaces instead of periods
Closes #1339
2016-06-13 22:08:22 -07:00
Peter Tutervai
fea5db3e4b Re-enabled calling synoindex after renaming
Fixed: On Rename connections to synoindex
2016-06-13 22:07:47 -07:00
Taloth Saldono
c38973cce4 Fixed tests. 2016-06-11 00:53:55 +02:00
Taloth Saldono
958153be55 Fixed: Reduced spurious cpu usage on mono while idle. 2016-06-11 00:25:59 +02:00
Taloth Saldono
d4bab775df Updated NLog to 4.3.4. 2016-06-11 00:24:07 +02:00
Mark McDowall
ecf67e609e Fix regex for expanded series + episode number (S01 - E01) 2016-06-06 10:15:21 -07:00
Mark McDowall
449b15331a Fixed Hadouken tests 2016-06-03 20:46:32 -07:00
Mark McDowall
f0437d1f22 Fixed: Ignore more folder names when browsing file system 2016-06-03 19:12:33 -07:00
Mark McDowall
902d6929c0 New: Hadouken torrent client support 2016-06-03 19:09:44 -07:00
phrusher
d81e03fcc0 Updated to support Hadouken v5.1 and above
Fixes indentation issues

Removes AuthenticationType

Corrects FieldDefinitions
2016-06-02 19:24:13 -07:00
Viktor Elofsson
abf8c684e7 Basic Hadouken v5 implementation. 2016-06-02 19:24:13 -07:00
Mark McDowall
7476d692aa New: EpisodeTitles for Custom Scripts
Closes #1263
2016-06-02 19:16:35 -07:00
Mark McDowall
c25bea6470 Fixed: Parsing separated season and episode numbers (S01 - E01)
Closes #1320
2016-06-02 19:07:15 -07:00
Mark McDowall
b9d67ae421 Better logger name for QualityParser 2016-06-02 10:04:41 -07:00
Mark McDowall
ff3fc8de2e Replace octal characters from mounts in /proc/mounts
Fixed: Replace octal characters in mount points
Closes #1295
2016-06-01 21:24:41 -07:00
Mark McDowall
e4e3770e54 Log message makes sense now 2016-05-24 18:40:07 -07:00
Taloth Saldono
498a86f850 Fixed Search tests. 2016-05-22 16:45:41 +02:00
Taloth Saldono
f0ae908892 Merge remote-tracking branch 'upstream/search-delays' into develop 2016-05-22 15:57:49 +02:00
Taloth Saldono
54c17de849 New: Http/Socks proxy support.
fixes #732
2016-05-22 15:57:38 +02:00
Mark McDowall
12a1865d4d Service now depends on HTTP Service (http) 2016-05-21 23:31:39 -07:00
Taloth Saldono
9ea753011b Added round-robin over httpbin.org hosts for httpclient tests. 2016-05-21 23:02:02 +02:00
Taloth Saldono
4c39594a57 Final cleanup on proxy support. 2016-05-21 22:59:26 +02:00
Mark McDowall
67ff871cf6 FolderWritableValidator
Fixed: Information in UI when attempting to add a root folder that is not writable
Fixes #1280
2016-05-21 01:39:39 -07:00
Mark McDowall
ea0982ecae Fixed: Error messages when config file is empty or contains invalid characters
Closes #1104
2016-05-21 00:22:12 -07:00
Mark McDowall
4f5d79b189 Fixed: Perform health check after download clients or indexers are removed
Closes #1304
2016-05-20 23:29:29 -07:00
Mark McDowall
aec3ed16d0 Use wildcards when searching for single episodes on BTN
Fixed: Double episode searching on BTN
Closes #1305
2016-05-20 23:02:22 -07:00
Mark McDowall
2e90ea9c19 Fixed: Prefix Twitter notifications (Grabbed/Imported) 2016-05-19 18:54:49 -07:00
Igal Tabachnik
a6b1a1fc0d New: Vuze torrent client support 2016-05-16 21:00:27 -07:00
Mark McDowall
fd42ddec1b Fixed: Respect delays when searching after a failed DownloadRelease
Closes #1292
2016-05-12 21:02:57 -07:00
Taloth Saldono
229986033c Added logging of Sonarr API calls. 2016-05-11 21:13:31 +02:00
Taloth Saldono
c249ad5dbe Using a tiered fallback is safer in case there is another data-loss and ids get reset. 2016-05-11 19:06:35 +02:00
Mark McDowall
e2d6d374ab Update HttpAccept.Rss to include application/xml 2016-05-10 15:18:27 -07:00
Taloth Saldono
d3adb7ac40 Fixed: HDBits release age incorrect.
fixes #1272
2016-05-10 22:19:51 +02:00
Taloth Saldono
0f1afd416b Fixed: Adjusted BTN Recent Feed (RssSync) to better use their api db indexes. 2016-05-10 21:43:34 +02:00
Mark McDowall
2f3bc61af7 Nice try uTorrent, you're not Deluge
Fixed: uTorrent error message identity crisis
2016-05-02 10:38:59 -07:00
Lloyd Sparkes
2b11ad4585 Proxy BypassList tests
Lightly refactored and updated documentation to make it easier for users
2016-05-02 11:24:51 +01:00
Lloyd Sparkes
c82b90aca8 Fix tests failing due to lack of constructor less classes, after refactoring 2016-05-01 12:47:19 +01:00
Lloyd Sparkes
5fae8e7762 Updating SocksWebProxy to fix issues with POST Requests 2016-05-01 11:51:30 +01:00
Mark McDowall
319b4f13b7 Fixed: Refreshing series that have duplicate season information 2016-04-30 11:02:40 -07:00
Mark McDowall
54fda3d648 Fixed: Updating Emby Library
Closes #1267
2016-04-28 23:42:53 -07:00
Mark McDowall
2f6fded7c3 Fixed: An issue preventing access to settings due to extraneous data in the database 2016-04-27 16:18:11 -07:00
Mark McDowall
7934003b5e Fixed: Rare error when removing pending items that have been rejected 2016-04-27 16:16:43 -07:00
Taloth Saldono
b479064abd Moved Proxy types around and refactored/renamed a few things. 2016-04-25 21:53:26 +02:00
Taloth Saldono
9e7927acec Create HttpProxySettingsProvider and fixed related issues. 2016-04-25 20:22:52 +02:00
Lloyd Sparkes
f807e44a39 Force to IPv4 2016-04-24 17:01:01 +01:00
Lloyd Sparkes
d68abc746c Bug Fixes 2016-04-24 16:56:31 +01:00
Mark McDowall
8773d38ddd Fixed: Plex Meda Server authentication 2016-04-23 13:28:07 -07:00
Lloyd Sparkes
6fdbb2b659 Fix merging issues & API changes - that conflict resolution did not detect 2016-04-23 11:06:10 +01:00
Lloyd Sparkes
bfe134ee54 Proxy Support for Sonarr #732
Switch to use Port #0 which just uses the next free port
Non Functional - Code Cleanup
Tabs -> Spaces,
Opps Deleted too much
Refactoring & Code Cleanup & Move Config to DB
remove unneeded line
Clean Up Spaces
Code Review Points on the UI
FIx extra space
Clean Up unrequired changes
Add a HealthCheck.
Extra Check in ProxyCheck. Correctly deal with Socks Authentication
Remove SubModule
Add in Nuget + Tweak ProxyCheck
Code Review Points
Missed Review Point
Add Subnet Filtering, Add ProxyBypass for local addresses. UI updated for property changes.
Fix typo, and copy&paste error
Tweak URL to hit
2016-04-23 10:58:02 +01:00
Taloth Saldono
f16f097b3e Fixed: Sabnzbd 1.0.1 added two new status values.
fixes #1259
2016-04-23 11:40:49 +02:00
Mark McDowall
7284ef50eb Fixed: Manual Import not scrolling after using file browser
Closes #745
2016-04-17 16:07:08 -07:00
Mark McDowall
e9248e284e Return decisions when catching exceptions during decision making
Fixed: Manual Import not showing files that failed to process
Closes #1131
2016-04-17 14:17:46 -07:00
Sam Holmes
aff6af1806 Update package.json license expression (#1242) 2016-04-13 08:08:20 -07:00
Taloth Saldono
c0c35a0eba Updated NzbGet tests. 2016-04-09 20:45:11 +02:00
Taloth Saldono
072ca459bd Fixed: NzbGet DUPE/COPY status should be considered failure.
fixes #919
closes #693
closes #505
2016-04-09 20:28:37 +02:00
Taloth Saldono
0865306064 Fixed: Adding Nzb with {{password}} in name to NzbGet failed. 2016-04-09 20:13:58 +02:00
Taloth Saldono
ac14444d34 Removed redundant logging. 2016-04-09 18:05:33 +02:00
Mark McDowall
8a6d1ef373 Release scoring 2016-04-08 13:25:50 -07:00
Mark McDowall
dc694b0f34 Don't throw after catching the exception during TearDown 2016-04-08 10:16:29 -07:00
Mark McDowall
76f8cc81da Fixed: Don't force testing when updating connections, indexers or download clients 2016-04-07 17:40:52 -07:00
Mark McDowall
14f737bd60 Fixed: Set permissions on series metadata images when they are created
Closes #871
2016-04-07 17:37:16 -07:00
Mark McDowall
5942ddf9f1 Implement mono logic to not set owner/group with chown
New: Allow Owner or Group to be left blank to not set it when changing owner (mono only)
Closes #1220
2016-04-07 15:52:12 -07:00
Mark McDowall
ab7b427241 Fixed: Default display time for Kodi notifications 2016-04-07 15:52:12 -07:00
Taloth Saldono
15120270b4 Disabled unreliable lookup test. 2016-04-07 00:07:49 +02:00
Taloth Saldono
cc0406653a Readded logging Download Client responses. 2016-04-06 22:53:14 +02:00
Taloth Saldono
9f34127565 Better error handling in the Deluge ConnectDaemon code. 2016-04-06 22:07:25 +02:00
Taloth Saldono
71ecc96c70 Refactored IntegrationTests to work with Nunit3 VS adapter. 2016-04-06 22:06:40 +02:00
Taloth Saldono
2fa3873503 Give a couple of timing-based tests a bit more breathing room. 2016-04-06 21:35:00 +02:00
Taloth Saldono
96b7bd3b2b Merge branch 'http-uri-combine-path' into develop 2016-04-06 21:34:20 +02:00
Taloth Saldono
e1ea17cabf Fixed: uTorrent api proxy would fail on specific Win10 configurations. (The Phoenix Rises)
Moved token queryparam to start since uTorrent requires it.
Fixed handling response missing an expected Set-Cookie header.
Force Cache-Control: no-cache for uTorrent.
Added Connection: KeepAlive to fix inexplicable uTorrent api failure.
2016-04-05 23:49:28 +02:00
Taloth Saldono
3a162be265 CombinePath now simple, uri resolve done via operator and CombineRelativePath. 2016-04-05 23:42:10 +02:00
Mark McDowall
502298aab9 Cleanup HttpUri.PathCombine 2016-04-03 18:47:43 -07:00
Mark McDowall
edea488dbe Upgrade to NUnit3 2016-04-01 19:19:32 -07:00
vawen
2fabe2d198 New: Prevent grabbing season packs if full season hasn't aired yet
Closes #743
2016-03-29 19:44:33 -07:00
Mark McDowall
f2c8156c00 ParsingService.GetEpisodes will use TVDB season number when available 2016-03-28 19:32:58 -07:00
Mark McDowall
942be364dc Treat XEM aliases as SceneSeasonNumber
Fixed: Aliases used incorrectly when TVDB season number matched the seaon number of the alias
Closes #1140
2016-03-28 19:32:25 -07:00
Keivan Beigi
44e09e2220 build.sh uses msbuild 14 2016-03-28 11:45:06 -07:00
Keivan Beigi
0fcd20ec4a use npm-cache if installed 2016-03-28 11:37:29 -07:00
Taloth Saldono
4c5707bba8 Fixed: Newznab/Torznab used wrong query if tvrageid was unknown in combination with a specific indexer capability profile. 2016-03-28 20:15:31 +02:00
Taloth Saldono
947f494e72 Fixed: Release Group detection didn't handle RLSGRP_English properly.
fixes #1198
2016-03-26 21:42:51 +01:00
Taloth Saldono
3f74a87b45 Fixed: Removed TrollHD from the RawHD detection regex since they now also release other sources.
fixes #1193
2016-03-26 20:45:52 +01:00
Taloth Saldono
da0bdc5750 New: Added RERIP as REPACK (Proper). 2016-03-26 20:41:27 +01:00
Taloth Saldono
3ea59cd91b Don't set ACL if already set. 2016-03-26 19:31:12 +01:00
Taloth Saldono
9b42dc7082 Reconfigure Logging early in the process to set the correct log level. 2016-03-26 19:31:11 +01:00
Taloth Saldono
8b1c022244 Updated NLog to 4.3.0-rc1. 2016-03-26 19:31:09 +01:00
Mark McDowall
e5cb8bb0bd Fixed: Some releases with date and season/episode numbers with multiple episodes on a single day
Closes #1192
2016-03-25 19:13:24 -07:00
Mark McDowall
d37343bb7d Fixed: Prevent root folders from being added under the startup folder 2016-03-25 19:03:28 -07:00
Nathan
9c91f11cdc New: Safari Pinned tab icon
Closes #1122
2016-03-24 15:47:08 -07:00
Mark McDowall
444fcf5ae5 Fixed: Use new rTorrent commands when resolving magnets
Closes #1199
2016-03-24 14:35:36 -07:00
Mark McDowall
31f8e0a47a New: Windows Phone theme 2016-03-24 12:25:51 -07:00
Mark McDowall
1e0fcc877b New: Mobile Chrome theme (Android 5.0+) 2016-03-24 12:25:51 -07:00
Björn Dahlgren
1293bab868 Run gulp using npm Simplifies usage of gulp and makes sure everyone is using same version 2016-03-24 10:43:54 -07:00
GΛVĪN
9de92d18e1 Updated OS X startup script to work with macports mono 2016-03-23 22:56:45 -07:00
Mark McDowall
5a877cbd62 Fixed: RSS Sync failing due to one broken indexer 2016-03-23 19:04:35 -07:00
Taloth Saldono
99aa25bf83 New: Light green background color in Season Pass for seasons with all episodes downloaded. 2016-03-22 23:09:38 +01:00
Iain Nicol
5f66c15b91 Fixed: Allow underscore when validating hostnames
This is particularly useful on FreeNAS.  It uses underscores in the hostnames when creating plugin jails.
2016-03-22 20:06:41 +00:00
Mark McDowall
af220c7f7b ItemViewContainer didn't exist sometimes for root folders
Fixed: Error displaying existing root folders in some cases
Closes #1170
2016-03-21 15:02:09 -07:00
Mark McDowall
59e71a4cd9 Include series type for CustomScript 2016-03-19 19:35:02 -07:00
Mark McDowall
6b1a4c4198 Always validate settings when testing thingies
Fixed: Validation skipped when saving connections
2016-03-18 15:42:19 -07:00
Mark McDowall
1072c5247c On grab for custom scripts
New: On Grab handling for Custom Scripts
2016-03-17 18:40:58 -07:00
Taloth Saldono
6508e920fe New: Added (fairly strict) regex for the new scene WEB quality = WEB-DL. 2016-03-17 21:03:01 +01:00
Taloth Saldono
1154e0eeb3 Add WebException handlers to prevent them reaching the UI. 2016-03-17 20:53:11 +01:00
Taloth Saldono
2d96914bfa Send Http auth without waiting for challenge. 2016-03-17 20:15:53 +01:00
Taloth Saldono
70494c3674 Adding magnet to qbit should use FormData not QueryParam. 2016-03-17 19:49:05 +01:00
Taloth Saldono
d68ad98176 Fixed: UsenetBlackhole not importing since latest develop. 2016-03-17 08:01:07 +01:00
Taloth Saldono
eb70a6419c Fixed: Not uploading nzbs to Nzbget on linux since previous develop. 2016-03-17 07:53:23 +01:00
Taloth Saldono
22aa759abc Fixed: Rarbg indexer broken on develop. 2016-03-17 07:32:45 +01:00
Taloth Saldono
19b8fb6d8b Merge branch 'blackhole-delay' into develop 2016-03-15 21:47:18 +01:00
Taloth Saldono
d4bc835b1c New: Delaying Blackhole imports while they're still being updated. 2016-03-15 21:46:40 +01:00
Taloth Saldono
b99d82cccc Ensure auto-generated mocks are also registered in the test container. 2016-03-15 21:46:39 +01:00
Taloth Saldono
25d481d5d9 Migrated all Download client proxies from RestSharp to HttpClient. 2016-03-11 20:36:03 +01:00
Taloth Saldono
23871503a2 Replaced Uri with HttpUri. 2016-03-11 20:36:01 +01:00
Taloth Saldono
7c54fa70d7 Added support for FormData (AddFormParameter and AddFormUpload), which automatically gets converted to multipart/form-data or application/x-www-form-urlencoded. 2016-03-11 20:35:59 +01:00
Taloth Saldono
2ffbbb0e71 Refactored HttpRequest and HttpRequestBuilder, moving most of the logic to the HttpRequestBuilder.
Added ContentSummary to be able to describe the ContentData in a human readable form. (Useful for JsonRpc and FormData).
2016-03-11 20:35:58 +01:00
529 changed files with 12746 additions and 4389 deletions

25
.editorconfig Normal file
View File

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

5
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

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

14
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

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

1
.gitignore vendored
View File

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

View File

@@ -18,7 +18,9 @@ Setup guides, FAQ, the more information we have on the wiki the better.
1. Fork Sonarr
2. Clone (develop branch) *you may need pull in submodules separately if you client doesn't clone them automatically (CurlSharp)*
3. Run `npm install`
4. Run `gulp watch` - Used to compile the UI components and copy them (leave this window open)
4. Run `npm start` - Used to compile the UI components and copy them.
Leave this window open.
If you have gulp globally installed you can use `gulp watch` instead
5. Compile in Visual Studio
### Contributing Code ###

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -52,7 +52,7 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.2.3\lib\net40\NLog.dll</HintPath>
<HintPath>..\packages\NLog.4.3.4\lib\net40\NLog.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />

View File

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

View File

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

View File

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

View File

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

View File

@@ -46,9 +46,9 @@
<HintPath>..\packages\FluentAssertions.4.2.1\lib\net40\FluentAssertions.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="nunit.framework, Version=2.6.3.13283, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\NUnit.2.6.3\lib\nunit.framework.dll</HintPath>
<Reference Include="nunit.framework, Version=3.2.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>..\packages\NUnit.3.2.0\lib\net40\nunit.framework.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
@@ -63,13 +63,9 @@
<Reference Include="Moq">
<HintPath>..\packages\Moq.4.0.10827\lib\NET40\Moq.dll</HintPath>
</Reference>
<Reference Include="Omu.ValueInjecter">
<HintPath>..\packages\ValueInjecter.2.3.3\lib\net35\Omu.ValueInjecter.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="ClientSchemaTests\SchemaBuilderFixture.cs" />
<Compile Include="MappingTests\ResourceMappingFixture.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
@@ -111,4 +107,4 @@
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>

View File

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

View File

@@ -27,6 +27,8 @@ namespace NzbDrone.Api.Authentication
_configFileProvider = configFileProvider;
}
public int Order { get { return 10; } }
public void Register(IPipelines pipelines)
{
if (_configFileProvider.AuthenticationMethod == AuthenticationType.Forms)

View File

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

View File

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

View File

@@ -26,12 +26,14 @@ namespace NzbDrone.Api.Calendar
var futureDays = 28;
var start = DateTime.Today.AddDays(-pastDays);
var end = DateTime.Today.AddDays(futureDays);
var unmonitored = false;
// TODO: Remove start/end parameters in v3, they don't work well for iCal
var queryStart = Request.Query.Start;
var queryEnd = Request.Query.End;
var queryPastDays = Request.Query.PastDays;
var queryFutureDays = Request.Query.FutureDays;
var queryUnmonitored = Request.Query.Unmonitored;
if (queryStart.HasValue) start = DateTime.Parse(queryStart.Value);
if (queryEnd.HasValue) end = DateTime.Parse(queryEnd.Value);
@@ -48,7 +50,12 @@ namespace NzbDrone.Api.Calendar
end = DateTime.Today.AddDays(futureDays);
}
var episodes = _episodeService.EpisodesBetweenDates(start, end, false);
if (queryUnmonitored.HasValue)
{
unmonitored = bool.Parse(queryUnmonitored.Value);
}
var episodes = _episodeService.EpisodesBetweenDates(start, end, unmonitored);
var icalCalendar = new iCalendar();
foreach (var episode in episodes.OrderBy(v => v.AirDateUtc.Value))

View File

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

View File

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

View File

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

View File

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

View File

@@ -21,5 +21,10 @@ namespace NzbDrone.Api.Config
.SetValidator(pathExistsValidator)
.When(c => !string.IsNullOrWhiteSpace(c.DownloadedEpisodesFolder));
}
protected override DownloadClientConfigResource ToResource(IConfigService model)
{
return DownloadClientConfigResourceMapper.ToResource(model);
}
}
}

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,5 @@
using FluentValidation;
using System;
using FluentValidation;
using NzbDrone.Api.Validation;
using NzbDrone.Core.Configuration;
@@ -19,5 +20,10 @@ namespace NzbDrone.Api.Config
SharedValidator.RuleFor(c => c.RssSyncInterval)
.IsValidRssSyncInterval();
}
protected override IndexerConfigResource ToResource(IConfigService model)
{
return IndexerConfigResourceMapper.ToResource(model);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,6 @@ using NLog;
using NzbDrone.Api.REST;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Api.Mapping;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Tv;
@@ -14,7 +13,7 @@ using NzbDrone.SignalR;
namespace NzbDrone.Api.EpisodeFiles
{
public class EpisodeModule : NzbDroneRestModuleWithSignalR<EpisodeFileResource, EpisodeFile>,
public class EpisodeFileModule : NzbDroneRestModuleWithSignalR<EpisodeFileResource, EpisodeFile>,
IHandle<EpisodeFileAddedEvent>
{
private readonly IMediaFileService _mediaFileService;
@@ -23,7 +22,7 @@ namespace NzbDrone.Api.EpisodeFiles
private readonly IQualityUpgradableSpecification _qualityUpgradableSpecification;
private readonly Logger _logger;
public EpisodeModule(IBroadcastSignalRMessage signalRBroadcaster,
public EpisodeFileModule(IBroadcastSignalRMessage signalRBroadcaster,
IMediaFileService mediaFileService,
IRecycleBinProvider recycleBinProvider,
ISeriesService seriesService,
@@ -47,7 +46,7 @@ namespace NzbDrone.Api.EpisodeFiles
var episodeFile = _mediaFileService.Get(id);
var series = _seriesService.GetSeries(episodeFile.SeriesId);
return MapToResource(series, episodeFile);
return episodeFile.ToResource(series, _qualityUpgradableSpecification);
}
private List<EpisodeFileResource> GetEpisodeFiles()
@@ -61,8 +60,7 @@ namespace NzbDrone.Api.EpisodeFiles
var series = _seriesService.GetSeries(seriesId);
return _mediaFileService.GetFilesBySeries(seriesId)
.Select(f => MapToResource(series, f)).ToList();
return _mediaFileService.GetFilesBySeries(seriesId).ConvertAll(f => f.ToResource(series, _qualityUpgradableSpecification));
}
private void SetQuality(EpisodeFileResource episodeFileResource)
@@ -83,16 +81,6 @@ namespace NzbDrone.Api.EpisodeFiles
_mediaFileService.Delete(episodeFile, DeleteMediaFileReason.Manual);
}
private EpisodeFileResource MapToResource(Core.Tv.Series series, EpisodeFile episodeFile)
{
var resource = episodeFile.InjectTo<EpisodeFileResource>();
resource.Path = Path.Combine(series.Path, episodeFile.RelativePath);
resource.QualityCutoffNotMet = _qualityUpgradableSpecification.CutoffNotMet(series.Profile.Value, episodeFile.Quality);
return resource;
}
public void Handle(EpisodeFileAddedEvent message)
{
BroadcastResourceChange(ModelAction.Updated, message.EpisodeFile.Id);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,8 @@
using System;
using Nancy;
using NzbDrone.Api.Episodes;
using NzbDrone.Api.Extensions;
using NzbDrone.Api.Series;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Download;
@@ -26,15 +28,16 @@ namespace NzbDrone.Api.History
Post["/failed"] = x => MarkAsFailed();
}
protected override HistoryResource ToResource<TModel>(TModel model)
protected HistoryResource MapToResource(Core.History.History model)
{
var resource = base.ToResource(model);
var resource = model.ToResource();
var history = model as Core.History.History;
resource.Series = model.Series.ToResource();
resource.Episode = model.Episode.ToResource();
if (history != null && history.Series != null)
if (model.Series != null)
{
resource.QualityCutoffNotMet = _qualityUpgradableSpecification.CutoffNotMet(history.Series.Profile.Value, history.Quality);
resource.QualityCutoffNotMet = _qualityUpgradableSpecification.CutoffNotMet(model.Series.Profile.Value, model.Quality);
}
return resource;
@@ -44,13 +47,7 @@ namespace NzbDrone.Api.History
{
var episodeId = Request.Query.EpisodeId;
var pagingSpec = new PagingSpec<Core.History.History>
{
Page = pagingResource.Page,
PageSize = pagingResource.PageSize,
SortKey = pagingResource.SortKey,
SortDirection = pagingResource.SortDirection
};
var pagingSpec = pagingResource.MapToPagingSpec<HistoryResource, Core.History.History>("date", SortDirection.Descending);
if (pagingResource.FilterKey == "eventType")
{
@@ -64,7 +61,7 @@ namespace NzbDrone.Api.History
pagingSpec.FilterExpression = h => h.EpisodeId == i;
}
return ApplyToPage(_historyService.Paged, pagingSpec);
return ApplyToPage(_historyService.Paged, pagingSpec, MapToResource);
}
private Response MarkAsFailed()
@@ -74,4 +71,4 @@ namespace NzbDrone.Api.History
return new object().AsResponse();
}
}
}
}

View File

@@ -17,8 +17,6 @@ namespace NzbDrone.Api.History
public QualityModel Quality { get; set; }
public bool QualityCutoffNotMet { get; set; }
public DateTime Date { get; set; }
public string Indexer { get; set; }
public string ReleaseGroup { get; set; }
public string DownloadId { get; set; }
public HistoryEventType EventType { get; set; }
@@ -28,4 +26,31 @@ namespace NzbDrone.Api.History
public EpisodeResource Episode { get; set; }
public SeriesResource Series { get; set; }
}
public static class HistoryResourceMapper
{
public static HistoryResource ToResource(this Core.History.History model)
{
if (model == null) return null;
return new HistoryResource
{
Id = model.Id,
EpisodeId = model.EpisodeId,
SeriesId = model.SeriesId,
SourceTitle = model.SourceTitle,
Quality = model.Quality,
//QualityCutoffNotMet
Date = model.Date,
DownloadId = model.DownloadId,
EventType = model.EventType,
Data = model.Data
//Episode
//Series
};
}
}
}

View File

@@ -1,4 +1,5 @@
using NzbDrone.Core.Indexers;
using System;
using NzbDrone.Core.Indexers;
namespace NzbDrone.Api.Indexers
{
@@ -9,6 +10,25 @@ namespace NzbDrone.Api.Indexers
{
}
protected override void MapToResource(IndexerResource resource, IndexerDefinition definition)
{
base.MapToResource(resource, definition);
resource.EnableRss = definition.EnableRss;
resource.EnableSearch = definition.EnableSearch;
resource.SupportsRss = definition.SupportsRss;
resource.SupportsSearch = definition.SupportsSearch;
resource.Protocol = definition.Protocol;
}
protected override void MapToModel(IndexerDefinition definition, IndexerResource resource)
{
base.MapToModel(definition, resource);
definition.EnableRss = resource.EnableRss;
definition.EnableSearch = resource.EnableSearch;
}
protected override void Validate(IndexerDefinition definition, bool includeWarnings)
{
if (!definition.Enable) return;

View File

@@ -89,7 +89,7 @@ namespace NzbDrone.Api.Indexers
{
try
{
var decisions = _nzbSearchService.EpisodeSearch(episodeId);
var decisions = _nzbSearchService.EpisodeSearch(episodeId, true);
var prioritizedDecisions = _prioritizeDownloadDecision.PrioritizeDecisions(decisions);
return MapDecisions(prioritizedDecisions);

View File

@@ -2,7 +2,6 @@
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Parser.Model;
using Omu.ValueInjecter;
using System.Linq;
namespace NzbDrone.Api.Indexers
@@ -25,42 +24,20 @@ namespace NzbDrone.Api.Indexers
protected virtual ReleaseResource MapDecision(DownloadDecision decision, int initialWeight)
{
var release = new ReleaseResource();
var release = decision.ToResource();
release.InjectFrom(decision.RemoteEpisode.Release);
release.InjectFrom(decision.RemoteEpisode.ParsedEpisodeInfo);
release.InjectFrom(decision);
release.Rejections = decision.Rejections.Select(r => r.Reason).ToList();
release.DownloadAllowed = decision.RemoteEpisode.DownloadAllowed;
release.ReleaseWeight = initialWeight;
if (decision.RemoteEpisode.Series != null)
{
release.QualityWeight = decision.RemoteEpisode
.Series
.Profile
.Value
.Items
.FindIndex(v => v.Quality == release.Quality.Quality) * 100;
release.QualityWeight = decision.RemoteEpisode.Series
.Profile.Value
.Items.FindIndex(v => v.Quality == release.Quality.Quality) * 100;
}
release.QualityWeight += release.Quality.Revision.Real * 10;
release.QualityWeight += release.Quality.Revision.Version;
var torrentRelease = decision.RemoteEpisode.Release as TorrentInfo;
if (torrentRelease != null)
{
release.Protocol = DownloadProtocol.Torrent;
release.Seeders = torrentRelease.Seeders;
//TODO: move this up the chains
release.Leechers = torrentRelease.Peers - torrentRelease.Seeders;
}
else
{
release.Protocol = DownloadProtocol.Usenet;
}
return release;
}
}

View File

@@ -6,7 +6,6 @@ using NzbDrone.Core.Download;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Api.Mapping;
using NzbDrone.Api.Extensions;
using NLog;
using NzbDrone.Core.Indexers;
@@ -39,9 +38,7 @@ namespace NzbDrone.Api.Indexers
{
_logger.Info("Release pushed: {0} - {1}", release.Title, release.DownloadUrl);
var info = release.Protocol == DownloadProtocol.Usenet ?
release.InjectTo<ReleaseInfo>() :
release.InjectTo<TorrentInfo>();
var info = release.ToModel();
info.Guid = "PUSH-" + info.DownloadUrl;

View File

@@ -5,6 +5,9 @@ using NzbDrone.Api.REST;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Core.DecisionEngine;
using System.Linq;
namespace NzbDrone.Api.Indexers
{
@@ -20,11 +23,9 @@ namespace NzbDrone.Api.Indexers
public int IndexerId { get; set; }
public string Indexer { get; set; }
public string ReleaseGroup { get; set; }
public string SubGroup { get; set; }
public string ReleaseHash { get; set; }
public string Title { get; set; }
public bool FullSeason { get; set; }
public bool SceneSource { get; set; }
public int SeasonNumber { get; set; }
public Language Language { get; set; }
public string AirDate { get; set; }
@@ -45,6 +46,8 @@ namespace NzbDrone.Api.Indexers
public int ReleaseWeight { get; set; }
public string MagnetUrl { get; set; }
public string InfoHash { get; set; }
public int? Seeders { get; set; }
public int? Leechers { get; set; }
public DownloadProtocol Protocol { get; set; }
@@ -74,4 +77,98 @@ namespace NzbDrone.Api.Indexers
public bool IsPossibleSpecialEpisode { get; set; }
public bool Special { get; set; }
}
public static class ReleaseResourceMapper
{
public static ReleaseResource ToResource(this DownloadDecision model)
{
var releaseInfo = model.RemoteEpisode.Release;
var parsedEpisodeInfo = model.RemoteEpisode.ParsedEpisodeInfo;
var remoteEpisode = model.RemoteEpisode;
var torrentInfo = (model.RemoteEpisode.Release as TorrentInfo) ?? new TorrentInfo();
// TODO: Clean this mess up. don't mix data from multiple classes, use sub-resources instead? (Got a huge Deja Vu, didn't we talk about this already once?)
return new ReleaseResource
{
Guid = releaseInfo.Guid,
Quality = parsedEpisodeInfo.Quality,
//QualityWeight
Age = releaseInfo.Age,
AgeHours = releaseInfo.AgeHours,
AgeMinutes = releaseInfo.AgeMinutes,
Size = releaseInfo.Size,
IndexerId = releaseInfo.IndexerId,
Indexer = releaseInfo.Indexer,
ReleaseGroup = parsedEpisodeInfo.ReleaseGroup,
ReleaseHash = parsedEpisodeInfo.ReleaseHash,
Title = releaseInfo.Title,
FullSeason = parsedEpisodeInfo.FullSeason,
SeasonNumber = parsedEpisodeInfo.SeasonNumber,
Language = parsedEpisodeInfo.Language,
AirDate = parsedEpisodeInfo.AirDate,
SeriesTitle = parsedEpisodeInfo.SeriesTitle,
EpisodeNumbers = parsedEpisodeInfo.EpisodeNumbers,
AbsoluteEpisodeNumbers = parsedEpisodeInfo.AbsoluteEpisodeNumbers,
Approved = model.Approved,
TemporarilyRejected = model.TemporarilyRejected,
Rejected = model.Rejected,
TvdbId = releaseInfo.TvdbId,
TvRageId = releaseInfo.TvRageId,
Rejections = model.Rejections.Select(r => r.Reason).ToList(),
PublishDate = releaseInfo.PublishDate,
CommentUrl = releaseInfo.CommentUrl,
DownloadUrl = releaseInfo.DownloadUrl,
InfoUrl = releaseInfo.InfoUrl,
DownloadAllowed = remoteEpisode.DownloadAllowed,
//ReleaseWeight
MagnetUrl = torrentInfo.MagnetUrl,
InfoHash = torrentInfo.InfoHash,
Seeders = torrentInfo.Seeders,
Leechers = (torrentInfo.Peers.HasValue && torrentInfo.Seeders.HasValue) ? (torrentInfo.Peers.Value - torrentInfo.Seeders.Value) : (int?)null,
Protocol = releaseInfo.DownloadProtocol,
IsDaily = parsedEpisodeInfo.IsDaily,
IsAbsoluteNumbering = parsedEpisodeInfo.IsAbsoluteNumbering,
IsPossibleSpecialEpisode = parsedEpisodeInfo.IsPossibleSpecialEpisode,
Special = parsedEpisodeInfo.Special,
};
}
public static ReleaseInfo ToModel(this ReleaseResource resource)
{
ReleaseInfo model;
if (resource.Protocol == DownloadProtocol.Torrent)
{
model = new TorrentInfo
{
MagnetUrl = resource.MagnetUrl,
InfoHash = resource.InfoHash,
Seeders = resource.Seeders,
Peers = (resource.Seeders.HasValue && resource.Leechers.HasValue) ? (resource.Seeders + resource.Leechers) : null
};
}
else
{
model = new ReleaseInfo();
}
model.Guid = resource.Guid;
model.Title = resource.Title;
model.Size = resource.Size;
model.DownloadUrl = resource.DownloadUrl;
model.InfoUrl = resource.InfoUrl;
model.CommentUrl = resource.CommentUrl;
model.IndexerId = resource.IndexerId;
model.Indexer = resource.Indexer;
model.DownloadProtocol = resource.DownloadProtocol;
model.TvdbId = resource.TvdbId;
model.TvRageId = resource.TvRageId;
model.PublishDate = resource.PublishDate;
return model;
}
}
}

View File

@@ -1,6 +1,5 @@
using NzbDrone.Core.Datastore;
using NzbDrone.Core.Instrumentation;
using NzbDrone.Api.Mapping;
namespace NzbDrone.Api.Logs
{
@@ -16,7 +15,7 @@ namespace NzbDrone.Api.Logs
private PagingResource<LogResource> GetLogs(PagingResource<LogResource> pagingResource)
{
var pageSpec = pagingResource.InjectTo<PagingSpec<Log>>();
var pageSpec = pagingResource.MapToPagingSpec<LogResource, Log>();
if (pageSpec.SortKey == "time")
{
@@ -48,7 +47,7 @@ namespace NzbDrone.Api.Logs
}
}
return ApplyToPage(_logService.Paged, pageSpec);
return ApplyToPage(_logService.Paged, pageSpec, LogResourceMapper.ToResource);
}
}
}

View File

@@ -11,6 +11,25 @@ namespace NzbDrone.Api.Logs
public string Level { get; set; }
public string Logger { get; set; }
public string Message { get; set; }
public string Method { get; set; }
}
public static class LogResourceMapper
{
public static LogResource ToResource(this Core.Instrumentation.Log model)
{
if (model == null) return null;
return new LogResource
{
Id = model.Id,
Time = model.Time,
Exception = model.Exception,
ExceptionType = model.ExceptionType,
Level = model.Level,
Logger = model.Logger,
Message = model.Message
};
}
}
}

View File

@@ -25,7 +25,7 @@ namespace NzbDrone.Api.ManualImport
var downloadIdQuery = Request.Query.downloadId;
var downloadId = (string)downloadIdQuery.Value;
return ToListResource(_manualImportService.GetMediaFiles(folder, downloadId)).Select(AddQualityWeight).ToList();
return _manualImportService.GetMediaFiles(folder, downloadId).ToResource().Select(AddQualityWeight).ToList();
}
private ManualImportResource AddQualityWeight(ManualImportResource item)

View File

@@ -1,7 +1,9 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Api.Episodes;
using NzbDrone.Api.REST;
using NzbDrone.Api.Series;
using NzbDrone.Common.Crypto;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Qualities;
@@ -20,13 +22,35 @@ namespace NzbDrone.Api.ManualImport
public int QualityWeight { get; set; }
public string DownloadId { get; set; }
public IEnumerable<Rejection> Rejections { get; set; }
}
public int Id
public static class ManualImportResourceMapper
{
public static ManualImportResource ToResource(this Core.MediaFiles.EpisodeImport.Manual.ManualImportItem model)
{
get
if (model == null) return null;
return new ManualImportResource
{
return Path.GetHashCode();
}
Id = HashConverter.GetHashInt31(model.Path),
Path = model.Path,
RelativePath = model.RelativePath,
Name = model.Name,
Size = model.Size,
Series = model.Series.ToResource(),
SeasonNumber = model.SeasonNumber,
Episodes = model.Episodes.ToResource(),
Quality = model.Quality,
//QualityWeight
DownloadId = model.DownloadId,
Rejections = model.Rejections
};
}
public static List<ManualImportResource> ToResource(this IEnumerable<Core.MediaFiles.EpisodeImport.Manual.ManualImportItem> models)
{
return models.Select(ToResource).ToList();
}
}
}

View File

@@ -1,125 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Marr.Data;
using Omu.ValueInjecter;
namespace NzbDrone.Api.Mapping
{
public class CloneInjection : ConventionInjection
{
protected override bool Match(ConventionInfo conventionInfo)
{
return conventionInfo.SourceProp.Name == conventionInfo.TargetProp.Name &&
conventionInfo.SourceProp.Value != null;
}
protected override object SetValue(ConventionInfo conventionInfo)
{
if (conventionInfo.SourceProp.Type == conventionInfo.TargetProp.Type)
return conventionInfo.SourceProp.Value;
if (conventionInfo.SourceProp.Type.IsArray)
{
var array = (Array)conventionInfo.SourceProp.Value;
var clone = (Array)array.Clone();
for (var index = 0; index < array.Length; index++)
{
var item = array.GetValue(index);
if (!item.GetType().IsValueType && !(item is string))
{
clone.SetValue(Activator.CreateInstance(item.GetType()).InjectFrom<CloneInjection>(item), index);
}
}
return clone;
}
if (conventionInfo.SourceProp.Type.IsGenericType)
{
var genericInterfaces = conventionInfo.SourceProp.Type.GetGenericTypeDefinition().GetInterfaces();
if (genericInterfaces.Any(d => d == typeof(IEnumerable)))
{
return MapLists(conventionInfo);
}
if (genericInterfaces.Any(i => i == typeof(ILazyLoaded)))
{
return MapLazy(conventionInfo);
}
//unhandled generic type, you could also return null or throw
return conventionInfo.SourceProp.Value;
}
//for simple object types create a new instace and apply the clone injection on it
return Activator.CreateInstance(conventionInfo.TargetProp.Type)
.InjectFrom<CloneInjection>(conventionInfo.SourceProp.Value);
}
private static object MapLazy(ConventionInfo conventionInfo)
{
var sourceArgument = conventionInfo.SourceProp.Type.GetGenericArguments()[0];
dynamic lazy = conventionInfo.SourceProp.Value;
if (lazy.IsLoaded && lazy.Value != null)
{
if (conventionInfo.TargetProp.Type.IsAssignableFrom(sourceArgument))
{
return lazy.Value;
}
var genericArgument = conventionInfo.TargetProp.Type;
if (genericArgument.IsValueType || genericArgument == typeof(string))
{
return lazy.Value;
}
if (genericArgument.IsGenericType)
{
if (conventionInfo.SourceProp.Type.GetGenericTypeDefinition().GetInterfaces().Any(d => d == typeof(IEnumerable)))
{
return MapLists(genericArgument, lazy.Value);
}
}
return Activator.CreateInstance(genericArgument).InjectFrom((object)lazy.Value);
}
return null;
}
private static object MapLists(ConventionInfo conventionInfo)
{
var genericArgument = conventionInfo.TargetProp.Type.GetGenericArguments()[0];
return MapLists(genericArgument, conventionInfo.SourceProp.Value);
}
private static object MapLists(Type targetType, object sourceValue)
{
if (targetType.IsValueType || targetType == typeof(string))
{
return sourceValue;
}
var listType = typeof(List<>).MakeGenericType(targetType);
var addMethod = listType.GetMethod("Add");
var result = Activator.CreateInstance(listType);
foreach (var sourceItem in (IEnumerable)sourceValue)
{
var e = Activator.CreateInstance(targetType).InjectFrom<CloneInjection>(sourceItem);
addMethod.Invoke(result, new[] { e });
}
return result;
}
}
}

View File

@@ -1,54 +0,0 @@
using System;
using System.Linq;
using System.Reflection;
using NzbDrone.Api.REST;
using NzbDrone.Common.Reflection;
namespace NzbDrone.Api.Mapping
{
public static class MappingValidation
{
public static void ValidateMapping(Type modelType, Type resourceType)
{
var errors = modelType.GetSimpleProperties().Where(c=>!c.GetGetMethod().IsStatic).Select(p => GetError(resourceType, p)).Where(c => c != null).ToList();
if (errors.Any())
{
throw new ResourceMappingException(errors);
}
PrintExtraProperties(modelType, resourceType);
}
private static void PrintExtraProperties(Type modelType, Type resourceType)
{
var resourceBaseProperties = typeof(RestResource).GetProperties().Select(c => c.Name);
var resourceProperties = resourceType.GetProperties().Select(c => c.Name).Except(resourceBaseProperties);
var modelProperties = modelType.GetProperties().Select(c => c.Name);
var extra = resourceProperties.Except(modelProperties);
foreach (var extraProp in extra)
{
Console.WriteLine("Extra: [{0}]", extraProp);
}
}
private static string GetError(Type resourceType, PropertyInfo modelProperty)
{
var resourceProperty = resourceType.GetProperties().FirstOrDefault(c => c.Name == modelProperty.Name);
if (resourceProperty == null)
{
return string.Format("public {0} {1} {{ get; set; }}", modelProperty.PropertyType.Name, modelProperty.Name);
}
if (resourceProperty.PropertyType != modelProperty.PropertyType && !typeof(RestResource).IsAssignableFrom(resourceProperty.PropertyType))
{
return string.Format("Expected {0}.{1} to have type of {2} but found {3}", resourceType.Name, resourceProperty.Name, modelProperty.PropertyType, resourceProperty.PropertyType);
}
return null;
}
}
}

View File

@@ -1,15 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace NzbDrone.Api.Mapping
{
public class ResourceMappingException : ApplicationException
{
public ResourceMappingException(IEnumerable<string> error)
: base(Environment.NewLine + string.Join(Environment.NewLine, error.OrderBy(c => c)))
{
}
}
}

View File

@@ -1,42 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Omu.ValueInjecter;
namespace NzbDrone.Api.Mapping
{
public static class ValueInjectorExtensions
{
public static TTarget InjectTo<TTarget>(this object source) where TTarget : new()
{
if (source == null) return default(TTarget);
var targetType = typeof(TTarget);
if (targetType.IsGenericType &&
targetType.GetGenericTypeDefinition() != null &&
targetType.GetGenericTypeDefinition().GetInterfaces().Contains(typeof(IEnumerable)) &&
source.GetType().IsGenericType &&
source.GetType().GetGenericTypeDefinition() != null &&
source.GetType().GetGenericTypeDefinition().GetInterfaces().Contains(typeof(IEnumerable)))
{
var result = new TTarget();
var listSubType = targetType.GetGenericArguments()[0];
var listType = typeof(List<>).MakeGenericType(listSubType);
var addMethod = listType.GetMethod("Add");
foreach (var sourceItem in (IEnumerable)source)
{
var e = Activator.CreateInstance(listSubType).InjectFrom<CloneInjection>(sourceItem);
addMethod.Invoke(result, new[] { e });
}
return result;
}
return (TTarget)new TTarget().InjectFrom<CloneInjection>(source);
}
}
}

View File

@@ -1,4 +1,5 @@
using NzbDrone.Core.Metadata;
using System;
using NzbDrone.Core.Metadata;
namespace NzbDrone.Api.Metadata
{
@@ -9,6 +10,20 @@ namespace NzbDrone.Api.Metadata
{
}
protected override void MapToResource(MetadataResource resource, MetadataDefinition definition)
{
base.MapToResource(resource, definition);
resource.Enable = definition.Enable;
}
protected override void MapToModel(MetadataDefinition definition, MetadataResource resource)
{
base.MapToModel(definition, resource);
definition.Enable = resource.Enable;
}
protected override void Validate(MetadataDefinition definition, bool includeWarnings)
{
if (!definition.Enable) return;

View File

@@ -1,8 +1,9 @@
using System;
using NLog;
using System.Linq;
using Nancy;
using Nancy.Bootstrapper;
using Nancy.Diagnostics;
using NLog;
using NzbDrone.Api.ErrorManagement;
using NzbDrone.Api.Extensions.Pipelines;
using NzbDrone.Common.EnvironmentInfo;
@@ -37,13 +38,11 @@ namespace NzbDrone.Api
container.Resolve<DatabaseTarget>().Register();
container.Resolve<IEventAggregator>().PublishEvent(new ApplicationStartedEvent());
ApplicationPipelines.OnError.AddItemToEndOfPipeline((Func<NancyContext, Exception, Response>) container.Resolve<NzbDroneErrorPipeline>().HandleException);
}
private void RegisterPipelines(IPipelines pipelines)
{
var pipelineRegistrars = _tinyIoCContainer.ResolveAll<IRegisterNancyPipeline>();
var pipelineRegistrars = _tinyIoCContainer.ResolveAll<IRegisterNancyPipeline>().OrderBy(v => v.Order).ToList();
foreach (var registerNancyPipeline in pipelineRegistrars)
{

View File

@@ -1,4 +1,5 @@
using NzbDrone.Core.Notifications;
using System;
using NzbDrone.Core.Notifications;
namespace NzbDrone.Api.Notifications
{
@@ -9,6 +10,36 @@ namespace NzbDrone.Api.Notifications
{
}
protected override void MapToResource(NotificationResource resource, NotificationDefinition definition)
{
base.MapToResource(resource, definition);
resource.OnGrab = definition.OnGrab;
resource.OnDownload = definition.OnDownload;
resource.OnUpgrade = definition.OnUpgrade;
resource.OnRename = definition.OnRename;
resource.SupportsOnGrab = definition.SupportsOnGrab;
resource.SupportsOnDownload = definition.SupportsOnDownload;
resource.SupportsOnUpgrade = definition.SupportsOnUpgrade;
resource.SupportsOnRename = definition.SupportsOnRename;
resource.Tags = definition.Tags;
}
protected override void MapToModel(NotificationDefinition definition, NotificationResource resource)
{
base.MapToModel(definition, resource);
definition.OnGrab = resource.OnGrab;
definition.OnDownload = resource.OnDownload;
definition.OnUpgrade = resource.OnUpgrade;
definition.OnRename = resource.OnRename;
definition.SupportsOnGrab = resource.SupportsOnGrab;
definition.SupportsOnDownload = resource.SupportsOnDownload;
definition.SupportsOnUpgrade = resource.SupportsOnUpgrade;
definition.SupportsOnRename = resource.SupportsOnRename;
definition.Tags = resource.Tags;
}
protected override void Validate(NotificationDefinition definition, bool includeWarnings)
{
if (!definition.OnGrab && !definition.OnDownload) return;

View File

@@ -4,7 +4,6 @@ namespace NzbDrone.Api.Notifications
{
public class NotificationResource : ProviderResource
{
public string Link { get; set; }
public bool OnGrab { get; set; }
public bool OnDownload { get; set; }
public bool OnUpgrade { get; set; }
@@ -13,7 +12,6 @@ namespace NzbDrone.Api.Notifications
public bool SupportsOnDownload { get; set; }
public bool SupportsOnUpgrade { get; set; }
public bool SupportsOnRename { get; set; }
public string TestCommand { get; set; }
public HashSet<int> Tags { get; set; }
}
}

View File

@@ -60,7 +60,7 @@
<HintPath>..\packages\Newtonsoft.Json.6.0.6\lib\net40\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.2.3\lib\net40\NLog.dll</HintPath>
<HintPath>..\packages\NLog.4.3.4\lib\net40\NLog.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
@@ -70,9 +70,6 @@
<Reference Include="DDay.iCal">
<HintPath>..\packages\DDay.iCal.1.0.2.575\lib\DDay.iCal.dll</HintPath>
</Reference>
<Reference Include="Omu.ValueInjecter">
<HintPath>..\packages\ValueInjecter.2.3.3\lib\net35\Omu.ValueInjecter.dll</HintPath>
</Reference>
<Reference Include="System.Data.SQLite, Version=1.0.84.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\Libraries\Sqlite\System.Data.SQLite.dll</HintPath>
@@ -100,6 +97,7 @@
<Compile Include="Commands\CommandResource.cs" />
<Compile Include="Extensions\AccessControlHeaders.cs" />
<Compile Include="Extensions\Pipelines\CorsPipeline.cs" />
<Compile Include="Extensions\Pipelines\RequestLoggingPipeline.cs" />
<Compile Include="Frontend\Mappers\LoginHtmlMapper.cs" />
<Compile Include="Frontend\Mappers\RobotsTxtMapper.cs" />
<Compile Include="Indexers\ReleaseModuleBase.cs" />
@@ -110,7 +108,6 @@
<Compile Include="ManualImport\ManualImportResource.cs" />
<Compile Include="Profiles\Delay\DelayProfileModule.cs" />
<Compile Include="Profiles\Delay\DelayProfileResource.cs" />
<Compile Include="Profiles\Delay\DelayProfileValidator.cs" />
<Compile Include="Queue\QueueActionModule.cs" />
<Compile Include="RemotePathMappings\RemotePathMappingModule.cs" />
<Compile Include="RemotePathMappings\RemotePathMappingResource.cs" />
@@ -145,7 +142,6 @@
<Compile Include="ErrorManagement\ErrorModel.cs" />
<Compile Include="ErrorManagement\NzbDroneErrorPipeline.cs" />
<Compile Include="Exceptions\InvalidApiKeyException.cs" />
<Compile Include="Extensions\LazyExtensions.cs" />
<Compile Include="Extensions\NancyJsonSerializer.cs" />
<Compile Include="Extensions\Pipelines\CacheHeaderPipeline.cs" />
<Compile Include="Extensions\Pipelines\GZipPipeline.cs" />
@@ -180,10 +176,6 @@
<Compile Include="Logs\LogModule.cs" />
<Compile Include="Logs\LogResource.cs" />
<Compile Include="Logs\UpdateLogFileModule.cs" />
<Compile Include="Mapping\CloneInjection.cs" />
<Compile Include="Mapping\MappingValidation.cs" />
<Compile Include="Mapping\ResourceMappingException.cs" />
<Compile Include="Mapping\ValueInjectorExtensions.cs" />
<Compile Include="MediaCovers\MediaCoverModule.cs" />
<Compile Include="Metadata\MetadataModule.cs" />
<Compile Include="Metadata\MetadataResource.cs" />

View File

@@ -4,7 +4,6 @@ using System.Collections.Generic;
using NzbDrone.Api.REST;
using NzbDrone.Api.Validation;
using NzbDrone.Core.Datastore;
using NzbDrone.Api.Mapping;
namespace NzbDrone.Api
{
@@ -25,42 +24,19 @@ namespace NzbDrone.Api
PutValidator.RuleFor(r => r.Id).ValidId();
}
protected int GetNewId<TModel>(Func<TModel, TModel> function, TResource resource) where TModel : ModelBase, new()
{
var model = resource.InjectTo<TModel>();
function(model);
return model.Id;
}
protected List<TResource> ToListResource<TModel>(Func<IEnumerable<TModel>> function) where TModel : class
{
var modelList = function();
return ToListResource(modelList);
}
protected virtual List<TResource> ToListResource<TModel>(IEnumerable<TModel> modelList) where TModel : class
{
return modelList.Select(ToResource).ToList();
}
protected virtual TResource ToResource<TModel>(TModel model) where TModel : class
{
return model.InjectTo<TResource>();
}
protected PagingResource<TResource> ApplyToPage<TModel>(Func<PagingSpec<TModel>, PagingSpec<TModel>> function, PagingSpec<TModel> pagingSpec) where TModel : ModelBase, new()
protected PagingResource<TResource> ApplyToPage<TModel>(Func<PagingSpec<TModel>, PagingSpec<TModel>> function, PagingSpec<TModel> pagingSpec, Converter<TModel, TResource> mapper)
{
pagingSpec = function(pagingSpec);
return new PagingResource<TResource>
{
Page = pagingSpec.Page,
PageSize = pagingSpec.PageSize,
SortDirection = pagingSpec.SortDirection,
SortKey = pagingSpec.SortKey,
TotalRecords = pagingSpec.TotalRecords,
Records = ToListResource(pagingSpec.Records)
};
{
Page = pagingSpec.Page,
PageSize = pagingSpec.PageSize,
SortDirection = pagingSpec.SortDirection,
SortKey = pagingSpec.SortKey,
TotalRecords = pagingSpec.TotalRecords,
Records = pagingSpec.Records.ConvertAll(mapper)
};
}
}
}

View File

@@ -8,7 +8,7 @@ namespace NzbDrone.Api
{
public abstract class NzbDroneRestModuleWithSignalR<TResource, TModel> : NzbDroneRestModule<TResource>, IHandle<ModelEvent<TModel>>
where TResource : RestResource, new()
where TModel : ModelBase
where TModel : ModelBase, new()
{
private readonly IBroadcastSignalRMessage _signalRBroadcaster;

View File

@@ -3,7 +3,7 @@ using NzbDrone.Core.Datastore;
namespace NzbDrone.Api
{
public class PagingResource<TModel>
public class PagingResource<TResource>
{
public int Page { get; set; }
public int PageSize { get; set; }
@@ -12,6 +12,31 @@ namespace NzbDrone.Api
public string FilterKey { get; set; }
public string FilterValue { get; set; }
public int TotalRecords { get; set; }
public List<TModel> Records { get; set; }
public List<TResource> Records { get; set; }
}
public static class PagingResourceMapper
{
public static PagingSpec<TModel> MapToPagingSpec<TResource, TModel>(this PagingResource<TResource> pagingResource, string defaultSortKey = "Id", SortDirection defaultSortDirection = SortDirection.Ascending)
{
var pagingSpec = new PagingSpec<TModel>
{
Page = pagingResource.Page,
PageSize = pagingResource.PageSize,
SortKey = pagingResource.SortKey,
SortDirection = pagingResource.SortDirection,
};
if (pagingResource.SortKey == null)
{
pagingSpec.SortKey = defaultSortKey;
if(pagingResource.SortDirection == SortDirection.Default)
{
pagingSpec.SortDirection = defaultSortDirection;
}
}
return pagingSpec;
}
}
}

View File

@@ -1,5 +1,6 @@
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
using NzbDrone.Api.Episodes;
using NzbDrone.Api.Series;
using NzbDrone.Core.Parser;
namespace NzbDrone.Api.Parse
{
@@ -16,7 +17,7 @@ namespace NzbDrone.Api.Parse
private ParseResource Parse()
{
var title = Request.Query.Title.Value;
var title = Request.Query.Title.Value as string;
var parsedEpisodeInfo = Parser.ParseTitle(title);
if (parsedEpisodeInfo == null)
@@ -26,24 +27,24 @@ namespace NzbDrone.Api.Parse
var remoteEpisode = _parsingService.Map(parsedEpisodeInfo, 0, 0);
if (remoteEpisode == null)
if (remoteEpisode != null)
{
remoteEpisode = new RemoteEpisode
{
ParsedEpisodeInfo = parsedEpisodeInfo
};
return new ParseResource
{
Title = title,
ParsedEpisodeInfo = parsedEpisodeInfo
};
{
Title = title,
ParsedEpisodeInfo = remoteEpisode.ParsedEpisodeInfo,
Series = remoteEpisode.Series.ToResource(),
Episodes = remoteEpisode.Episodes.ToResource()
};
}
else
{
return new ParseResource
{
Title = title,
ParsedEpisodeInfo = parsedEpisodeInfo
};
}
var resource = ToResource(remoteEpisode);
resource.Title = title;
return resource;
}
}
}

View File

@@ -1,6 +1,6 @@
using System.Collections.Generic;
using FluentValidation;
using NzbDrone.Api.Mapping;
using FluentValidation.Results;
using NzbDrone.Api.REST;
using NzbDrone.Api.Validation;
using NzbDrone.Core.Profiles.Delay;
@@ -26,12 +26,21 @@ namespace NzbDrone.Api.Profiles.Delay
SharedValidator.RuleFor(d => d.Tags).SetValidator(tagInUseValidator);
SharedValidator.RuleFor(d => d.UsenetDelay).GreaterThanOrEqualTo(0);
SharedValidator.RuleFor(d => d.TorrentDelay).GreaterThanOrEqualTo(0);
SharedValidator.RuleFor(d => d.Id).SetValidator(new DelayProfileValidator());
SharedValidator.Custom(delayProfile =>
{
if (!delayProfile.EnableUsenet && !delayProfile.EnableTorrent)
{
return new ValidationFailure("", "Either Usenet or Torrent should be enabled");
}
return null;
});
}
private int Create(DelayProfileResource resource)
{
var model = resource.InjectTo<DelayProfile>();
var model = resource.ToModel();
model = _delayProfileService.Add(model);
return model.Id;
@@ -49,17 +58,18 @@ namespace NzbDrone.Api.Profiles.Delay
private void Update(DelayProfileResource resource)
{
GetNewId<DelayProfile>(_delayProfileService.Update, resource);
var model = resource.ToModel();
_delayProfileService.Update(model);
}
private DelayProfileResource GetById(int id)
{
return _delayProfileService.Get(id).InjectTo<DelayProfileResource>();
return _delayProfileService.Get(id).ToResource();
}
private List<DelayProfileResource> GetAll()
{
return _delayProfileService.All().InjectTo<List<DelayProfileResource>>();
return _delayProfileService.All().ToResource();
}
}
}

View File

@@ -1,6 +1,8 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Api.REST;
using NzbDrone.Core.Indexers;
using NzbDrone.Core.Profiles.Delay;
namespace NzbDrone.Api.Profiles.Delay
{
@@ -14,4 +16,48 @@ namespace NzbDrone.Api.Profiles.Delay
public int Order { get; set; }
public HashSet<int> Tags { get; set; }
}
public static class DelayProfileResourceMapper
{
public static DelayProfileResource ToResource(this DelayProfile model)
{
if (model == null) return null;
return new DelayProfileResource
{
Id = model.Id,
EnableUsenet = model.EnableUsenet,
EnableTorrent = model.EnableTorrent,
PreferredProtocol = model.PreferredProtocol,
UsenetDelay = model.UsenetDelay,
TorrentDelay = model.TorrentDelay,
Order = model.Order,
Tags = new HashSet<int>(model.Tags)
};
}
public static DelayProfile ToModel(this DelayProfileResource resource)
{
if (resource == null) return null;
return new DelayProfile
{
Id = resource.Id,
EnableUsenet = resource.EnableUsenet,
EnableTorrent = resource.EnableTorrent,
PreferredProtocol = resource.PreferredProtocol,
UsenetDelay = resource.UsenetDelay,
TorrentDelay = resource.TorrentDelay,
Order = resource.Order,
Tags = new HashSet<int>(resource.Tags)
};
}
public static List<DelayProfileResource> ToResource(this IEnumerable<DelayProfile> models)
{
return models.Select(ToResource).ToList();
}
}
}

View File

@@ -1,27 +0,0 @@
using FluentValidation.Validators;
using NzbDrone.Core.Profiles.Delay;
using Omu.ValueInjecter;
namespace NzbDrone.Api.Profiles.Delay
{
public class DelayProfileValidator : PropertyValidator
{
public DelayProfileValidator()
: base("Usenet or Torrent must be enabled")
{
}
protected override bool IsValid(PropertyValidatorContext context)
{
var delayProfile = new DelayProfile();
delayProfile.InjectFrom(context.ParentContext.InstanceToValidate);
if (!delayProfile.EnableUsenet && !delayProfile.EnableTorrent)
{
return false;
}
return true;
}
}
}

View File

@@ -7,7 +7,7 @@ namespace NzbDrone.Api.Profiles.Languages
public class LanguageResource : RestResource
{
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Include)]
public int Id { get; set; }
public new int Id { get; set; }
public string Name { get; set; }
public string NameLower { get { return Name.ToLowerInvariant(); } }
}

View File

@@ -1,6 +1,5 @@
using System.Collections.Generic;
using FluentValidation;
using NzbDrone.Api.Mapping;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities;
using NzbDrone.Core.Validation;
@@ -28,9 +27,9 @@ namespace NzbDrone.Api.Profiles
private int Create(ProfileResource resource)
{
var model = resource.InjectTo<Profile>();
model = _profileService.Add(model);
return model.Id;
var model = resource.ToModel();
return _profileService.Add(model).Id;
}
private void DeleteProfile(int id)
@@ -40,26 +39,19 @@ namespace NzbDrone.Api.Profiles
private void Update(ProfileResource resource)
{
var model = _profileService.Get(resource.Id);
model.Name = resource.Name;
model.Cutoff = (Quality)resource.Cutoff.Id;
model.Items = resource.Items.InjectTo<List<ProfileQualityItem>>();
model.Language = resource.Language;
var model = resource.ToModel();
_profileService.Update(model);
}
private ProfileResource GetById(int id)
{
return _profileService.Get(id).InjectTo<ProfileResource>();
return _profileService.Get(id).ToResource();
}
private List<ProfileResource> GetAll()
{
var profiles = _profileService.All().InjectTo<List<ProfileResource>>();
return profiles;
return _profileService.All().ToResource();
}
}
}

View File

@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Api.REST;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Api.Profiles
@@ -19,4 +21,64 @@ namespace NzbDrone.Api.Profiles
public Quality Quality { get; set; }
public bool Allowed { get; set; }
}
public static class ProfileResourceMapper
{
public static ProfileResource ToResource(this Profile model)
{
if (model == null) return null;
return new ProfileResource
{
Id = model.Id,
Name = model.Name,
Cutoff = model.Cutoff,
Items = model.Items.ConvertAll(ToResource),
Language = model.Language
};
}
public static ProfileQualityItemResource ToResource(this ProfileQualityItem model)
{
if (model == null) return null;
return new ProfileQualityItemResource
{
Quality = model.Quality,
Allowed = model.Allowed
};
}
public static Profile ToModel(this ProfileResource resource)
{
if (resource == null) return null;
return new Profile
{
Id = resource.Id,
Name = resource.Name,
Cutoff = (Quality)resource.Cutoff.Id,
Items = resource.Items.ConvertAll(ToModel),
Language = resource.Language
};
}
public static ProfileQualityItem ToModel(this ProfileQualityItemResource resource)
{
if (resource == null) return null;
return new ProfileQualityItem
{
Quality = (Quality)resource.Quality.Id,
Allowed = resource.Allowed
};
}
public static List<ProfileResource> ToResource(this IEnumerable<Profile> models)
{
return models.Select(ToResource).ToList();
}
}
}

View File

@@ -1,6 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Api.Mapping;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Profiles;
using NzbDrone.Core.Qualities;
@@ -31,7 +30,7 @@ namespace NzbDrone.Api.Profiles
profile.Items = items;
profile.Language = Language.English;
return new List<ProfileResource> { profile.InjectTo<ProfileResource>() };
return new List<ProfileResource> { profile.ToResource() };
}
}
}

View File

@@ -6,11 +6,9 @@ using FluentValidation.Results;
using Nancy;
using NzbDrone.Api.ClientSchema;
using NzbDrone.Api.Extensions;
using NzbDrone.Api.Mapping;
using NzbDrone.Common.Reflection;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
using Omu.ValueInjecter;
using Newtonsoft.Json;
namespace NzbDrone.Api
@@ -29,7 +27,7 @@ namespace NzbDrone.Api
Get["schema"] = x => GetTemplates();
Post["test"] = x => Test(ReadResourceFromRequest(true));
Post["connectData/{stage}"] = x => ConnectData(x.stage, ReadResourceFromRequest(true));
Post["action/{action}"] = x => RequestAction(x.action, ReadResourceFromRequest(true));
GetResourceAll = GetAll;
GetResourceById = GetProviderById;
@@ -48,9 +46,10 @@ namespace NzbDrone.Api
private TProviderResource GetProviderById(int id)
{
var definition = _providerFactory.Get(id);
var resource = definition.InjectTo<TProviderResource>();
_providerFactory.SetProviderCharacteristics(definition);
resource.InjectFrom(_providerFactory.GetProviderCharacteristics(_providerFactory.GetInstance(definition), definition));
var resource = new TProviderResource();
MapToResource(resource, definition);
return resource;
}
@@ -63,10 +62,10 @@ namespace NzbDrone.Api
foreach (var definition in providerDefinitions)
{
_providerFactory.SetProviderCharacteristics(definition);
var providerResource = new TProviderResource();
providerResource.InjectFrom(definition);
providerResource.InjectFrom(_providerFactory.GetProviderCharacteristics(_providerFactory.GetInstance(definition), definition));
providerResource.Fields = SchemaBuilder.ToSchema(definition.Settings);
MapToResource(providerResource, definition);
result.Add(providerResource);
}
@@ -92,11 +91,6 @@ namespace NzbDrone.Api
{
var providerDefinition = GetDefinition(providerResource, false);
if (providerDefinition.Enable)
{
Test(providerDefinition, false);
}
_providerFactory.Update(providerDefinition);
}
@@ -104,15 +98,8 @@ namespace NzbDrone.Api
{
var definition = new TProviderDefinition();
definition.InjectFrom(providerResource);
MapToModel(definition, providerResource);
var preset = _providerFactory.GetPresetDefinitions(definition)
.Where(v => v.Name == definition.Name)
.Select(v => v.Settings)
.FirstOrDefault();
var configContract = ReflectionExtensions.CoreAssembly.FindTypeByName(definition.ConfigContract);
definition.Settings = (IProviderConfig)SchemaBuilder.ReadFormSchema(providerResource.Fields, configContract, preset);
if (validate)
{
Validate(definition, includeWarnings);
@@ -121,6 +108,37 @@ namespace NzbDrone.Api
return definition;
}
protected virtual void MapToResource(TProviderResource resource, TProviderDefinition definition)
{
resource.Id = definition.Id;
resource.Name = definition.Name;
resource.ImplementationName = definition.ImplementationName;
resource.Implementation = definition.Implementation;
resource.ConfigContract = definition.ConfigContract;
resource.Message = definition.Message;
resource.Fields = SchemaBuilder.ToSchema(definition.Settings);
resource.InfoLink = string.Format("https://github.com/Sonarr/Sonarr/wiki/Supported-{0}#{1}",
typeof(TProviderResource).Name.Replace("Resource", "s"),
definition.Implementation.ToLower());
}
protected virtual void MapToModel(TProviderDefinition definition, TProviderResource resource)
{
definition.Id = resource.Id;
definition.Name = resource.Name;
definition.ImplementationName = resource.ImplementationName;
definition.Implementation = resource.Implementation;
definition.ConfigContract = resource.ConfigContract;
definition.Message = resource.Message;
var configContract = ReflectionExtensions.CoreAssembly.FindTypeByName(definition.ConfigContract);
definition.Settings = (IProviderConfig)SchemaBuilder.ReadFromSchema(resource.Fields, configContract);
}
private void DeleteProvider(int id)
{
_providerFactory.Delete(id);
@@ -135,19 +153,14 @@ namespace NzbDrone.Api
foreach (var providerDefinition in defaultDefinitions)
{
var providerResource = new TProviderResource();
providerResource.InjectFrom(providerDefinition);
providerResource.Fields = SchemaBuilder.ToSchema(providerDefinition.Settings);
providerResource.InfoLink = string.Format("https://github.com/NzbDrone/NzbDrone/wiki/Supported-{0}#{1}",
typeof(TProviderResource).Name.Replace("Resource", "s"),
providerDefinition.Implementation.ToLower());
MapToResource(providerResource, providerDefinition);
var presetDefinitions = _providerFactory.GetPresetDefinitions(providerDefinition);
providerResource.Presets = presetDefinitions.Select(v =>
{
var presetResource = new TProviderResource();
presetResource.InjectFrom(v);
presetResource.Fields = SchemaBuilder.ToSchema(v.Settings);
MapToResource(presetResource, v);
return presetResource as ProviderResource;
}).ToList();
@@ -160,21 +173,23 @@ namespace NzbDrone.Api
private Response Test(TProviderResource providerResource)
{
var providerDefinition = GetDefinition(providerResource, true);
// Don't validate when getting the definition so we can validate afterwards (avoids validation being skipped because the provider is disabled)
var providerDefinition = GetDefinition(providerResource, true, false);
Validate(providerDefinition, true);
Test(providerDefinition, true);
return "{}";
}
private Response ConnectData(string stage, TProviderResource providerResource)
private Response RequestAction(string action, TProviderResource providerResource)
{
TProviderDefinition providerDefinition = GetDefinition(providerResource, true, false);
var providerDefinition = GetDefinition(providerResource, true, false);
if (!providerDefinition.Enable) return "{}";
var query = ((IDictionary<string, object>)Request.Query.ToDictionary()).ToDictionary(k => k.Key, k => k.Value.ToString());
object data = _providerFactory.ConnectData(providerDefinition, stage, (IDictionary<string, object>) Request.Query.ToDictionary());
var data = _providerFactory.RequestAction(providerDefinition, action, query);
Response resp = JsonConvert.SerializeObject(data);
resp.ContentType = "application/json";
return resp;

View File

@@ -1,6 +1,5 @@
using System.Collections.Generic;
using NzbDrone.Core.Qualities;
using NzbDrone.Api.Mapping;
namespace NzbDrone.Api.Qualities
{
@@ -21,18 +20,18 @@ namespace NzbDrone.Api.Qualities
private void Update(QualityDefinitionResource resource)
{
var model = resource.InjectTo<QualityDefinition>();
var model = resource.ToModel();
_qualityDefinitionService.Update(model);
}
private QualityDefinitionResource GetById(int id)
{
return _qualityDefinitionService.GetById(id).InjectTo<QualityDefinitionResource>();
return _qualityDefinitionService.GetById(id).ToResource();
}
private List<QualityDefinitionResource> GetAll()
{
return ToListResource(_qualityDefinitionService.All);
return _qualityDefinitionService.All().ToResource();
}
}
}

View File

@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Api.REST;
using NzbDrone.Core.Qualities;
@@ -15,4 +17,50 @@ namespace NzbDrone.Api.Qualities
public double? MinSize { get; set; }
public double? MaxSize { get; set; }
}
public static class QualityDefinitionResourceMapper
{
public static QualityDefinitionResource ToResource(this QualityDefinition model)
{
if (model == null) return null;
return new QualityDefinitionResource
{
Id = model.Id,
Quality = model.Quality,
Title = model.Title,
Weight = model.Weight,
MinSize = model.MinSize,
MaxSize = model.MaxSize
};
}
public static QualityDefinition ToModel(this QualityDefinitionResource resource)
{
if (resource == null) return null;
return new QualityDefinition
{
Id = resource.Id,
Quality = resource.Quality,
Title = resource.Title,
Weight = resource.Weight,
MinSize = resource.MinSize,
MaxSize = resource.MaxSize
};
}
public static List<QualityDefinitionResource> ToResource(this IEnumerable<QualityDefinition> models)
{
return models.Select(ToResource).ToList();
}
}
}

View File

@@ -24,7 +24,7 @@ namespace NzbDrone.Api.Queue
private List<QueueResource> GetQueue()
{
return ToListResource(GetQueueItems);
return GetQueueItems().ToResource();
}
private IEnumerable<Core.Queue.Queue> GetQueueItems()

View File

@@ -6,6 +6,7 @@ using NzbDrone.Api.Series;
using NzbDrone.Api.Episodes;
using NzbDrone.Core.Download.TrackedDownloads;
using NzbDrone.Core.Indexers;
using System.Linq;
namespace NzbDrone.Api.Queue
{
@@ -25,4 +26,36 @@ namespace NzbDrone.Api.Queue
public string DownloadId { get; set; }
public DownloadProtocol Protocol { get; set; }
}
public static class QueueResourceMapper
{
public static QueueResource ToResource(this Core.Queue.Queue model)
{
if (model == null) return null;
return new QueueResource
{
Id = model.Id,
Series = model.Series.ToResource(),
Episode = model.Episode.ToResource(),
Quality = model.Quality,
Size = model.Size,
Title = model.Title,
Sizeleft = model.Sizeleft,
Timeleft = model.Timeleft,
EstimatedCompletionTime = model.EstimatedCompletionTime,
Status = model.Status,
TrackedDownloadStatus = model.TrackedDownloadStatus,
StatusMessages = model.StatusMessages,
DownloadId = model.DownloadId,
Protocol = model.Protocol
};
}
public static List<QueueResource> ToResource(this IEnumerable<Core.Queue.Queue> models)
{
return models.Select(ToResource).ToList();
}
}
}

View File

@@ -1,9 +1,7 @@
using System.Collections.Generic;
using FluentValidation;
using NzbDrone.Api.Mapping;
using NzbDrone.Core.RemotePathMappings;
using NzbDrone.Core.Validation.Paths;
using Omu.ValueInjecter;
namespace NzbDrone.Api.RemotePathMappings
{
@@ -39,17 +37,19 @@ namespace NzbDrone.Api.RemotePathMappings
private RemotePathMappingResource GetMappingById(int id)
{
return _remotePathMappingService.Get(id).InjectTo<RemotePathMappingResource>();
return _remotePathMappingService.Get(id).ToResource();
}
private int CreateMapping(RemotePathMappingResource rootFolderResource)
private int CreateMapping(RemotePathMappingResource resource)
{
return GetNewId<RemotePathMapping>(_remotePathMappingService.Add, rootFolderResource);
var model = resource.ToModel();
return _remotePathMappingService.Add(model).Id;
}
private List<RemotePathMappingResource> GetMappings()
{
return ToListResource(_remotePathMappingService.All);
return _remotePathMappingService.All().ToResource();
}
private void DeleteMapping(int id)
@@ -59,9 +59,7 @@ namespace NzbDrone.Api.RemotePathMappings
private void UpdateMapping(RemotePathMappingResource resource)
{
var mapping = _remotePathMappingService.Get(resource.Id);
mapping.InjectFrom(resource);
var mapping = resource.ToModel();
_remotePathMappingService.Update(mapping);
}

View File

@@ -1,5 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Api.REST;
using NzbDrone.Core.RemotePathMappings;
namespace NzbDrone.Api.RemotePathMappings
{
@@ -9,4 +12,40 @@ namespace NzbDrone.Api.RemotePathMappings
public string RemotePath { get; set; }
public string LocalPath { get; set; }
}
public static class RemotePathMappingResourceMapper
{
public static RemotePathMappingResource ToResource(this RemotePathMapping model)
{
if (model == null) return null;
return new RemotePathMappingResource
{
Id = model.Id,
Host = model.Host,
RemotePath = model.RemotePath,
LocalPath = model.LocalPath
};
}
public static RemotePathMapping ToModel(this RemotePathMappingResource resource)
{
if (resource == null) return null;
return new RemotePathMapping
{
Id = resource.Id,
Host = resource.Host,
RemotePath = resource.RemotePath,
LocalPath = resource.LocalPath
};
}
public static List<RemotePathMappingResource> ToResource(this IEnumerable<RemotePathMapping> models)
{
return models.Select(ToResource).ToList();
}
}
}

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using FluentValidation.Results;
using NzbDrone.Api.Mapping;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Restrictions;
@@ -16,11 +15,11 @@ namespace NzbDrone.Api.Restrictions
{
_restrictionService = restrictionService;
GetResourceById = Get;
GetResourceAll = GetAll;
CreateResource = Create;
UpdateResource = Update;
DeleteResource = Delete;
GetResourceById = GetRestriction;
GetResourceAll = GetAllRestrictions;
CreateResource = CreateRestriction;
UpdateResource = UpdateRestriction;
DeleteResource = DeleteRestriction;
SharedValidator.Custom(restriction =>
{
@@ -33,27 +32,27 @@ namespace NzbDrone.Api.Restrictions
});
}
private RestrictionResource Get(int id)
private RestrictionResource GetRestriction(int id)
{
return _restrictionService.Get(id).InjectTo<RestrictionResource>();
return _restrictionService.Get(id).ToResource();
}
private List<RestrictionResource> GetAll()
private List<RestrictionResource> GetAllRestrictions()
{
return ToListResource(_restrictionService.All);
return _restrictionService.All().ToResource();
}
private int Create(RestrictionResource resource)
private int CreateRestriction(RestrictionResource resource)
{
return _restrictionService.Add(resource.InjectTo<Restriction>()).Id;
return _restrictionService.Add(resource.ToModel()).Id;
}
private void Update(RestrictionResource resource)
private void UpdateRestriction(RestrictionResource resource)
{
_restrictionService.Update(resource.InjectTo<Restriction>());
_restrictionService.Update(resource.ToModel());
}
private void Delete(int id)
private void DeleteRestriction(int id)
{
_restrictionService.Delete(id);
}

View File

@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Api.REST;
using NzbDrone.Core.Restrictions;
namespace NzbDrone.Api.Restrictions
{
@@ -16,4 +18,42 @@ namespace NzbDrone.Api.Restrictions
Tags = new HashSet<int>();
}
}
public static class RestrictionResourceMapper
{
public static RestrictionResource ToResource(this Restriction model)
{
if (model == null) return null;
return new RestrictionResource
{
Id = model.Id,
Required = model.Required,
Preferred = model.Preferred,
Ignored = model.Ignored,
Tags = new HashSet<int>(model.Tags)
};
}
public static Restriction ToModel(this RestrictionResource resource)
{
if (resource == null) return null;
return new Restriction
{
Id = resource.Id,
Required = resource.Required,
Preferred = resource.Preferred,
Ignored = resource.Ignored,
Tags = new HashSet<int>(resource.Tags)
};
}
public static List<RestrictionResource> ToResource(this IEnumerable<Restriction> models)
{
return models.Select(ToResource).ToList();
}
}
}

View File

@@ -1,7 +1,6 @@
using System.Collections.Generic;
using FluentValidation;
using NzbDrone.Core.RootFolders;
using NzbDrone.Api.Mapping;
using NzbDrone.Core.Validation.Paths;
using NzbDrone.SignalR;
@@ -16,7 +15,9 @@ namespace NzbDrone.Api.RootFolders
RootFolderValidator rootFolderValidator,
PathExistsValidator pathExistsValidator,
DroneFactoryValidator droneFactoryValidator,
MappedNetworkDriveValidator mappedNetworkDriveValidator)
MappedNetworkDriveValidator mappedNetworkDriveValidator,
StartupFolderValidator startupFolderValidator,
FolderWritableValidator folderWritableValidator)
: base(signalRBroadcaster)
{
_rootFolderService = rootFolderService;
@@ -32,22 +33,26 @@ namespace NzbDrone.Api.RootFolders
.SetValidator(rootFolderValidator)
.SetValidator(droneFactoryValidator)
.SetValidator(mappedNetworkDriveValidator)
.SetValidator(pathExistsValidator);
.SetValidator(startupFolderValidator)
.SetValidator(pathExistsValidator)
.SetValidator(folderWritableValidator);
}
private RootFolderResource GetRootFolder(int id)
{
return _rootFolderService.Get(id).InjectTo<RootFolderResource>();
return _rootFolderService.Get(id).ToResource();
}
private int CreateRootFolder(RootFolderResource rootFolderResource)
{
return GetNewId<RootFolder>(_rootFolderService.Add, rootFolderResource);
var model = rootFolderResource.ToModel();
return _rootFolderService.Add(model).Id;
}
private List<RootFolderResource> GetRootFolders()
{
return ToListResource(_rootFolderService.AllWithUnmappedFolders);
return _rootFolderService.AllWithUnmappedFolders().ToResource();
}
private void DeleteFolder(int id)

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Api.REST;
using NzbDrone.Core.RootFolders;
@@ -12,4 +13,40 @@ namespace NzbDrone.Api.RootFolders
public List<UnmappedFolder> UnmappedFolders { get; set; }
}
public static class RootFolderResourceMapper
{
public static RootFolderResource ToResource(this RootFolder model)
{
if (model == null) return null;
return new RootFolderResource
{
Id = model.Id,
Path = model.Path,
FreeSpace = model.FreeSpace,
UnmappedFolders = model.UnmappedFolders
};
}
public static RootFolder ToModel(this RootFolderResource resource)
{
if (resource == null) return null;
return new RootFolder
{
Id = resource.Id,
Path = resource.Path,
//FreeSpace
//UnmappedFolders
};
}
public static List<RootFolderResource> ToResource(this IEnumerable<RootFolder> models)
{
return models.Select(ToResource).ToList();
}
}
}

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