Compare commits

...

395 commits

Author SHA1 Message Date
Taloth Saldono
9b9597093c Fixed: Regression causing Manual Import to ignore user provided information. 2018-06-21 07:23:20 +02:00
Taloth Saldono
c92fb4d9c0 Fixed: Mini-series with multiple episode E01-E02.
fixes #2614
2018-06-17 15:57:33 +02:00
Taloth Saldono
88c659059b Added {MediaInfo AudioLanguages} {.. SubtitleLanguages}
fixes #2593
2018-06-16 22:19:33 +02:00
Taloth Saldono
d1ced600a1 Handle empty Episode list during decision maker. 2018-06-16 21:44:37 +02:00
Taloth Saldono
a5977f2752 Fixed: Manual Import not using Release Group during renamer. [develop only]
fixes #2612
2018-06-16 21:44:37 +02:00
Mark McDowall
8668e8b036 Use FolderEpisodeInfo instead of parent of FileEpisodeInfo
Fixes #2338
Fixed: Detecting some incorrect file name paring during import
2018-06-09 21:42:01 -07:00
Mark McDowall
783c27a584 Fixed: Initially pausing torrents in QBittorrent
Fixes #2599
2018-06-08 16:38:20 -07:00
Taloth Saldono
fde0409650 Fixed WithData sample length not using parameter. 2018-06-08 22:08:09 +02:00
Taloth Saldono
2ed5abf4d3 Also add as data to exception so sentry gets it. 2018-06-08 19:58:58 +02:00
Taloth Saldono
cb372f284d Log indexer response to Trace if an exception occurs. 2018-06-08 19:29:08 +02:00
Taloth Saldono
5d674a07f7 Fixed SeedConfigProvider failing on ReleasePush. 2018-05-26 00:16:34 +02:00
Mark McDowall
59e69c1839 Update bug issue template 2018-05-21 12:43:07 -07:00
Mark McDowall
89a3eec6ae Fixed: Parsing of anime releases with year and EP before episode number
Fixes #2578
2018-05-21 00:31:03 -07:00
Mark McDowall
6517f1af14 New: Treat 1440p releases as 1080p instead of 480p
Closes #2558
2018-05-20 23:33:45 -07:00
Taloth Saldono
b95c4d37d7 Fixed validation error for Seed Ratio on btn. 2018-05-20 12:34:24 +02:00
Taloth Saldono
7388e8c2c2 Fixed broken test for nested settings. 2018-05-20 10:19:52 +02:00
Taloth Saldono
b837ab41eb Fixed broken tests. 2018-05-19 22:15:19 +02:00
Taloth Saldono
de6615f586 Added a few more units. 2018-05-19 21:07:01 +02:00
Kevin Richter
0947dfc423 Stop deluge torrent when they reach stop ratio 2018-05-19 21:07:01 +02:00
Kevin Richter
002ed9e4c7 Fix parsing of entered time for seed time 2018-05-19 21:07:01 +02:00
Taloth Saldono
1a6a3038d6 Added warnings for minimum criteria for BTN. 2018-05-19 21:07:01 +02:00
Taloth Saldono
d86beb06f7 Added fancy unit indicator to fields. 2018-05-19 21:07:01 +02:00
Taloth Saldono
6df61e305d Added Seed Time and Season-Pack seed time. 2018-05-19 21:07:00 +02:00
Taloth Saldono
47018b02a8 Added nested settings for seed criteria. 2018-05-19 21:07:00 +02:00
Taloth Saldono
b6ef4d50dc Fixed more C#7. 2018-05-19 21:07:00 +02:00
Kevin Richter
2d86e44c63 New: Added advanced setting per indexer to override seed ratio limit for supported clients. 2018-05-19 21:07:00 +02:00
Taloth Saldono
69f8fc4d5e Added support for nested settings models so settings can be grouped together and reused for multiple providers. 2018-05-19 21:06:59 +02:00
Taloth Saldono
b339fcbd82 Fixed mono debug check. 2018-05-19 21:06:08 +02:00
Mark McDowall
7d1b09ac1b Update issue templates 2018-05-18 14:40:18 -07:00
Mark McDowall
55d01f620a Fixed: Setting inital state of torrents sent to QBittorrent
Fixes #2565
2018-05-16 14:00:21 -07:00
Mark McDowall
39d0d08ced Fixed: Removed old warning that Torrent Blackhole does not support magnet links 2018-05-16 13:59:33 -07:00
Mark McDowall
94f8cc9b62 Improve parsing of standard titles with junk in []
Fixed: Improve parsing of standard series releases/files with square brackets at the end
2018-05-11 19:08:47 -07:00
Mark McDowall
17b998f01f Improve parsing of non-standard date releases
Fixed: Parsing of some releases with season and episode numbers along with date
2018-05-11 18:34:34 -07:00
Mark McDowall
18415ddb30 New: Remove additional URL prefixes from release names
Closes #2542
2018-05-09 13:53:37 -07:00
Mark McDowall
2b3d0235cf Don't read media info when disabled in settings
Fixed: Don't read media info for existing files if "Analyse video files" disabled
Fixes #2549
2018-05-09 13:53:27 -07:00
Mark McDowall
c687f45ff6 Fixed: Don't try to find episodes if parsing failed 2018-05-09 13:35:56 -07:00
Mark McDowall
13f540f1f5 Fixed: Rescan series if refresh fails
Closes #2540
2018-05-02 23:34:05 -07:00
Mark McDowall
a7aff3bd9a Upgrade NLog to 4.5.3
Closes #2535
2018-05-01 23:20:54 -07:00
Taloth Saldono
03997e2a0d Switched to BigInteger for qbit eta as workaround for api bug, tyvm. 2018-04-30 22:18:42 +02:00
Mark McDowall
60c73df685 Fixed: Custom script unable to execute when release processed via /release/push API 2018-04-29 22:04:18 -07:00
Mark McDowall
b5ce3b2773 Fixed: Improved parsing of iTunes named files 2018-04-29 21:56:33 -07:00
Qstick
6c09b7dc28 Fixed: Throw SonarrStartupException if can't access AppFolder Location 2018-04-22 13:25:50 -07:00
Mark McDowall
bf1fbd7fc4 Fixed: Remove leading space from file names
Fixes #2365
2018-04-22 12:40:11 -07:00
Mark McDowall
401530db65 Fixed RemoveGrabbed tests 2018-04-22 09:40:44 -07:00
Mark McDowall
4fb7cb3a8b Fixed RemoveRejected tests 2018-04-22 09:27:07 -07:00
Mark McDowall
aba8abb176 Fixed: Suppress warnings for daily style extra files
Fixes #2503
2018-04-21 15:25:21 -07:00
Taloth Saldono
2b3956cb60 Honor x264 tag in filename if mediainfo does not conclusively indicate h264.
Closes #2500
2018-04-18 22:31:56 +02:00
Taloth Saldono
1b0647423d Updated a few harder to detect MediaInfo VideoCodec formats. 2018-04-18 22:07:38 +02:00
Taloth Saldono
5b627a8bc2 Fixed missing existingfile flag. 2018-04-15 16:52:30 +02:00
Taloth Saldono
62f7e90748 Fixed db migration issue. 2018-04-14 22:24:48 +02:00
Taloth Saldono
fd1064cb69 Fixed errors in MatchesFolderSpecification and tests. 2018-04-14 22:21:48 +02:00
Taloth Saldono
c677736a8f Fixed: Hide fallback pending releases if temporarily delayed.
Also batch changing of Pending Release Reason.
2018-04-14 22:07:08 +02:00
Taloth Saldono
67038ddd5e Cache EventAggregator Subscribers. 2018-04-14 22:07:08 +02:00
Taloth Saldono
ff8eb0b67f Added additional indexes to speed up DecisionMaker performance. 2018-04-14 22:07:08 +02:00
Taloth Saldono
70afacee3f Refactored PendingRelease logic for performance. 2018-04-14 22:07:08 +02:00
Taloth Saldono
5b0e959d3f Debounce Command Notifications. 2018-04-14 22:07:08 +02:00
Taloth Saldono
b7c5639a9d Speed up sqlite3 initialization by disabling unused features. 2018-04-14 22:07:08 +02:00
Taloth Saldono
740af2751a New: Season Search for Daily series type.
closes #755
2018-04-14 22:07:08 +02:00
Mark McDowall
81e385bebf New: Use media info during import to extract resolution for quality
Closes #448
Closes #1105
2018-04-14 22:01:24 +02:00
nivong
650d18797a Added missing bracket in issue template 2018-04-13 08:48:17 -07:00
Mark McDowall
49e91b8660
Update issue template regarding title prefixes 2018-04-11 20:45:33 -07:00
Taloth Saldono
4fb3d99153 Fixed: Updated AnimeTosho url.
closes #2501
2018-04-11 20:53:32 +02:00
Paul Kozlovitch
a29f133e5d Fixed: Pasting title into add new series search input will trigger search 2018-04-03 09:31:49 -07:00
dnnr
f277b60cc9 Fix grammar in EditProfileViewTemplate 2018-04-02 15:08:32 +02:00
Taloth Saldono
6d8eebcd30 Revised deletion of cookies. 2018-04-01 20:29:46 +02:00
Taloth Saldono
91c97ed232 Merge branch 'nfo-detector' into develop 2018-03-24 11:54:53 +01:00
margaale
13a259b473 check if mono is running with --debug arg 2018-03-24 11:49:27 +01:00
Taloth Saldono
d3f8e0ef4c Fixed: Revised handling of cookies in case of redirects. 2018-03-24 11:43:57 +01:00
Taloth Saldono
700751715b Add form param before submitting request. 2018-03-24 11:43:57 +01:00
Taloth Saldono
58e939beda Fixed: Preserve existing watched status in Kodi nfo files on metadata refreshes (not file upgrades). 2018-03-18 20:10:50 +01:00
Taloth Saldono
b19b6c93d3 New: Detect Kodi .nfo vs Scene .nfo and handle as appropriate. Rename scene .nfo to .nfo-orig only when needed. 2018-03-18 20:10:50 +01:00
Taloth Saldono
17e4a8ab66 Moved tests. 2018-03-18 20:10:50 +01:00
Taloth Saldono
3573e631c8 Fixed: Recycle Metadata files on episode removal. 2018-03-18 20:10:50 +01:00
Taloth Saldono
52588509ed Fixed failing test and some flaky tests. 2018-03-16 22:00:59 +01:00
Thirrian
e607a67f00 Fixed: Sorting for series "A.P. Bio"
closes #2450
2018-03-12 17:03:59 +01:00
Kevin Richter
96d7382a1c Fixed: Parsing # in front of absolute numbers 2018-03-10 21:41:28 +01:00
Taloth Saldono
e15530cee1 Fixed: TheXEM mapping with one scene release to multiple tvdb episodes. 2018-03-09 23:10:30 +01:00
Taloth Saldono
940f59468a New: Required/Ignored restrictions now support /pattern/ regular expressions. 2018-03-09 23:10:29 +01:00
Marcelo Castagna
ff885ab3bd Fixed: Added errorcode 160 - Permission denied on FileStation for easier diagnostics 2018-03-09 22:53:15 +01:00
Steven
17cfaf170e Add missing error check when adding a magnet link to deluge (#2295)
* Add missing error check when adding a magnet link to deluge

* Fix typo.
2018-03-09 22:51:52 +01:00
Thirrian
f1b2186313 Fix typo 2018-03-09 22:49:54 +01:00
Mark McDowall
ac379e3b84 Fixed: Don't add category when removing torrent from qBittorrent
Fixes #2438
2018-03-02 06:32:24 -08:00
Taloth Saldono
616454edb4 Fixed: Preserve existing watched status in Kodi nfo files on metadata refreshes (not file upgrades). 2018-03-01 20:36:07 +01:00
Taloth Saldono
d284969379 New: Detect Kodi .nfo vs Scene .nfo and handle as appropriate. Rename scene .nfo to .nfo-orig only when needed. 2018-03-01 20:35:12 +01:00
Taloth Saldono
f0eba619f3 Moved tests. 2018-03-01 18:30:09 +01:00
Taloth Saldono
448cfff769 Fixed: Recycle Metadata files on episode removal. 2018-03-01 18:30:08 +01:00
Taloth Saldono
99ee59e9cc Fixed: Updated NLog to 4.5 RC6 to handle mono 5.10 2018-02-27 22:41:46 +01:00
Taloth Saldono
c9ff55f601 Added console logging in case NLog fails to initialize. 2018-02-27 22:41:42 +01:00
Mark McDowall
ecb0438b16 New: Add MediaInfo to Episode Files returned from the API 2018-02-21 20:40:26 -08:00
Mark McDowall
d0acbe992e Log response content from Kodi when checking for errors 2018-02-21 20:39:53 -08:00
Mark McDowall
fb44e67ea8 Fixed: Parsing of WEB-DLMux files
Closes #2422
2018-02-21 20:37:25 -08:00
Mark McDowall
3f86c10c44 Fix broken profile test 2018-02-12 17:37:42 -08:00
Mark McDowall
9e7f0c859f New: Opt-in to delete empty series/season folders 2018-02-11 09:01:41 -08:00
Mark McDowall
a9c54a1f01 Remove empty series folders when create empty folders is false
New: Remove empty series folders after disk scan/deleting last episode file and create emoty series folders is disabled

Closes #2395
2018-02-10 18:16:59 -08:00
Mark McDowall
1c36bc5fee Return total space when adding new root folder 2018-02-10 10:15:52 -08:00
Mark McDowall
b529270f71 Improve error message when deleting a profile that is in use 2018-02-10 10:14:28 -08:00
Mark McDowall
bcf45bb68a Fixed: Send category to qBittorrent when adding torrent/magnet 2018-02-10 10:13:35 -08:00
Mark McDowall
9744873e7a Fixed: Removexpost suffix from release groups 2018-02-04 12:22:26 -08:00
Mark McDowall
9a61bb13e6 Fixed: Disable delete button on used quality profiles 2018-02-02 08:54:29 -08:00
Mark McDowall
2f69a158b1 API: Include total space with root folders
Closes #2390
2018-02-01 17:58:55 -08:00
Mark McDowall
4975793b45 Don't reject paths under /srv
Closes #2388
2018-01-31 19:56:04 -08:00
Taloth Saldono
d93645fb46 Fixed: Show error if System->Logs fails to load due to ad blocker. 2018-01-27 23:27:52 +01:00
overkill32
d617b6c6e3 Fixed typo in log file 2018-01-27 13:10:41 -08:00
Mark McDowall
cc01608f0a Fixed: Show error message when manual import fails to load
Fixes #2384
2018-01-26 22:26:52 -08:00
Mark McDowall
8a3db99811 Fixed: Remove Pre and postbot suffixes from release groups 2018-01-23 19:27:19 -08:00
Mark McDowall
883f7b88cf Removed DailyEpisodeMatchSpecification in favour of SingleEpisodeSearchMatchSpecification
Fixed: Parsing releases with standard and daily formats
2018-01-23 18:53:02 -08:00
Taloth Saldono
03da779ddb Fixed Season Special import. 2018-01-23 16:32:26 +01:00
Taloth Saldono
7c95fc23d0 Fixed Season Special import. 2018-01-22 21:35:20 +01:00
Mark McDowall
aa2174b43c Ensure request exists before trying to get query parameters
Fixes #2379
2018-01-21 12:59:00 -08:00
Mark McDowall
a023732c1c
New: Delay import of episodes without titles temporarily
Closes #2098
2018-01-20 23:56:35 -08:00
Mark McDowall
90f9dce44a Ensure request exists before trying to get query parameters 2018-01-16 12:13:12 -08:00
Mark McDowall
ea54b10bf7 First try 2018-01-14 18:12:23 -08:00
Mark McDowall
223ccb5a73 Fixed broken DeleteEpisodeFileFixture tests 2018-01-14 16:23:21 -08:00
Mark McDowall
968d8159a6 Added tests for DB Converters
Closes #482
2018-01-14 12:42:18 -08:00
Mark McDowall
245bf6c7d6 Optionally include season images when fetching series from API
Closes #464
2018-01-13 15:01:33 -08:00
Mark McDowall
ee673cd152 Log indexer when processing results
Closes #295
2018-01-13 14:10:19 -08:00
Mark McDowall
a1b6095f6e Add Paused above Use SSL for NZBGet 2018-01-13 13:55:28 -08:00
Mark McDowall
5663eb527b Add paused options for Deluge and Transmission
New: Option to add paused for Deluge
New: Option to add paused for Transmission

Closes #795
2018-01-13 11:58:24 -08:00
Mark McDowall
8e8da76467 New: Device names for Join notifications
Closes #2364
2018-01-12 19:14:45 -08:00
Taloth Saldono
8a6acd999a Reordered UI a bit and code cleanup. 2018-01-12 22:00:02 +01:00
Mark McDowall
f32e8beab8 Fixed: Parsing of prefixed range multi-episode filenames
Fixes #2359
2018-01-12 12:40:36 -08:00
Marcelo Castagna
d25e5fe329 New: Setting for absolute maximum size for a release
Closes #2367 #1996
2018-01-12 20:50:21 +01:00
Taloth
d0e8aef949
New: Consider all scene SxxE00 releases Specials as well.
* New: Consider all scene SxxE00 releases Specials as well.

* Cleanup special episode titles and handle E00 specials with existing scenemappings.

* Moved handling of scene mapped E00 to central function.
2018-01-04 21:49:16 +01:00
Kevin Richter
ad10349878 Ignore macOS DS_Store files (#2356) 2017-12-31 13:13:02 +01:00
Mark McDowall
560fe31280 csproj fix 2017-12-29 23:37:20 -08:00
Mark McDowall
c67a4a61a4 New: Run missing root folder health check when an import is successful
Closes #2309
2017-12-29 22:30:16 -08:00
Mark McDowall
6e85b8782e Fixed: Set air date to 1970-01-01 if episode aired before (mono)
Fixes #2345
2017-12-29 10:56:11 -08:00
Mark McDowall
f719c5ccf1 Fixed: Improve logging for invalid NZB messages
Closes #2349
2017-12-28 22:05:20 -08:00
Mark McDowall
e8c5e417b6 Improve handling of multiple seasons in one file
Fixed: Invalid scene numbering leading to manual import failing to load
Fixes #2255
2017-12-28 21:48:05 -08:00
Fish2
3492d6bbaa Cleanup moment.js deprecated zone and add functions 2017-12-26 10:10:13 -08:00
Taloth Saldono
15bd181f16 Fixed failing tests in DownloadStation. 2017-12-19 22:49:27 +01:00
Taloth Saldono
96108cb758 Fixed up comments. 2017-12-19 18:38:29 +01:00
Taloth Saldono
459d6ea906 Fixed: Mono internals does not properly copy/move symlinks, but instead copies the contents. 2017-12-19 18:34:53 +01:00
margaale
747f3e171c Fixed: Handling of unknown status types in DownloadStation. 2017-12-19 18:32:48 +01:00
Mark McDowall
b371296e78 Validate before deleting series folders
Closes #264
2017-12-16 12:16:33 -08:00
Taloth Saldono
0553a39a02 Rare timing issue on first-use causing duplicate naming config. 2017-12-13 20:25:29 +01:00
Taloth Saldono
4ca5e978ac Fixed MediaCover endpoint. 2017-12-13 20:18:29 +01:00
Mark McDowall
025440ee86 Don't handle content requests in IndexHtmlMapper 2017-12-13 19:16:50 +01:00
Taloth Saldono
40016f2928 Merge branch 'security-patch-2017-1' into develop 2017-12-07 19:44:58 +01:00
Taloth Saldono
581320e3a4 Fixed: Security Vulnerabilities allowing authentication to be bypassed (discovered by Kyle Neideck) 2017-12-07 19:42:18 +01:00
Taloth Saldono
5fae2ac66f Improved handling of the Preflight OPTIONS request. 2017-12-07 19:35:53 +01:00
Taloth Saldono
3ba61cd5aa Fixed: Limit Cross-Origin access to api and specific shared resources. 2017-12-07 19:35:46 +01:00
Taloth Saldono
0008f28236 Fixed: Case sensitivity in handling of static resource names. 2017-12-07 19:35:39 +01:00
Mark McDowall
f9ec4c6b0d
Updated tests to allow for older versions of mediainfo 2017-12-06 13:22:46 -08:00
Mark McDowall
2b20b91ec5
Updated tests to reflect changes in mediainfo 2017-12-05 14:30:58 -08:00
Mark McDowall
e070eeda4a
New: Upgrade MediaInfo to 17.10 (Windows/macOS) 2017-12-04 18:52:17 -08:00
Mark McDowall
195a761c29
Fixed: Sorting by episodes on series overview and poster views
Fixes #2167
2017-12-04 18:51:47 -08:00
Mark McDowall
4399d272dc
Fixed: Import failures when audio channels are in an unexpected format
Fixes #2318
2017-12-03 19:58:31 -08:00
Mark McDowall
9d7547e941
Add debug logging when formatting audio channels 2017-12-02 16:16:53 -08:00
Mark McDowall
0a690eb4d8
New: Include APFS disks in disk space
Closes #2261
2017-12-01 18:39:44 -08:00
Mark McDowall
32309260b9
Fixed: Sorting Manual Import by relative path
Fixes #2313
2017-11-30 23:18:23 -08:00
Mark McDowall
e11e8ad272
New: Channel setting for Slack notifications to override default channel
Closes #2311
2017-11-30 22:48:00 -08:00
Mark McDowall
b45b2017a8
Log warnings when deleting an episode file and the root folder is missing/empty
Closes #2305
2017-11-24 18:51:39 -08:00
Mark McDowall
ae2a97763d
New: Validate NZBs before sending to download client
Closes #263
2017-11-21 19:00:25 -08:00
Frank Scholl
442d49046d
New: Add authentication options to Webhook
Closes #2257
2017-11-21 18:55:46 -08:00
Mark McDowall
3d8cd9616e
Fixed: Parsing of resolution in TVRips
Fixes #2281
2017-11-21 18:33:25 -08:00
Mark McDowall
9c68c89f24
Fix namespace take 2 2017-11-19 16:09:01 -08:00
Mark McDowall
59ad5f35df
Fixed: Parsing when using episode number as folder name in naming config 2017-11-16 17:57:11 -08:00
Mark McDowall
263f3c46b1
Fix namespace 2017-11-16 17:55:55 -08:00
Taloth Saldono
2c13fcf579 Trim quotes from dsm version parts. 2017-11-11 23:20:04 +01:00
Taloth Saldono
7a1c1064ae Fallback to parsing Series from sub path during Manual Import. 2017-11-09 22:10:24 +01:00
Taloth Saldono
bb52f3d41c Fixed: Incorrect parsing of filenames with [SDTV] suffix trigging Anime pattern. 2017-11-09 21:54:17 +01:00
Taloth Saldono
0688340722 Fixed: Regression preventing new downloads from bypassing the Download Client Back-off logic.
fixes #2277
2017-11-08 21:29:57 +01:00
Taloth Saldono
cf7b8455d4 Fixed another flaky test. 2017-10-27 22:32:18 +02:00
Taloth Saldono
df3c6ed572 Fixed: Telegram notification with underscore and other special characters.
fixses #2249
2017-10-27 22:29:33 +02:00
Taloth Saldono
e6c01be2cb Reverted the bindingredirect as well. 2017-10-26 20:55:34 +02:00
Taloth Saldono
2196db9a58 Downgraded Moq again since it causes failing tests on older mono versions due to bad Array covariance handling. 2017-10-26 20:29:09 +02:00
Taloth Saldono
f10174da37 Fixed flaky test. 2017-10-26 19:42:05 +02:00
Taloth Saldono
b48eaa7fd3 Fixed unrelated testcase. 2017-10-25 23:35:58 +02:00
Taloth Saldono
c34eec160f Cache BestForTags briefly for better performance when processing releases. 2017-10-25 23:35:58 +02:00
Taloth Saldono
393996fe88 Fixed: Progressively degrading performance issue in Pending/Delayed releases system.
fixes #2205
2017-10-25 23:35:57 +02:00
Taloth Saldono
1ef08424ff New: Query 'Season 1.1' groups on BTN during manual single episode searches.
No longer does a season query on auto single episode searches since those would be rejected anyway.
2017-10-25 20:12:28 +02:00
Taloth Saldono
20af2c8c0f Fixed: Releases no longer available on the indexer should be removed from the pending queue.
fixes #679
2017-10-25 19:59:05 +02:00
Taloth Saldono
b7e74bd5be Fixed Moq version conflict between packages.config and csproj in Api.Test by upgrading them all. 2017-10-25 19:55:49 +02:00
Taloth Saldono
34ded19be4 Fixed not setting MediaInfo SchemaRevision first time causing it to be fetched again during the series rescan. 2017-10-25 19:36:30 +02:00
Taloth Saldono
f53cad822a Fixed: Raise ApplicationStartupEvent after Owin is running. 2017-10-25 19:30:44 +02:00
Taloth Saldono
090dd45541 Improved test timing for CommandExecutor. 2017-10-25 19:30:44 +02:00
Taloth Saldono
e01e822394 Shutdown logging before Environment.Exit. 2017-10-25 19:30:44 +02:00
Taloth Saldono
ea5769fdd6 Updated NLog from 4.4.3 to 4.4.12 2017-10-25 19:30:44 +02:00
Mark McDowall
1d2b35fdde
Fix failing test 2017-10-22 23:18:37 -07:00
Mark McDowall
1d3c536c93 Updated ISSUE_TEMPLATE.md and added SUPPORT.md 2017-10-22 12:38:07 -07:00
James White
e6b8b17b21 Fixed: Default ports for uTorrent and qBittorrent 2017-10-22 12:31:42 -07:00
Mark McDowall
2a0df7e83e
history/since API endpoint
Closes #2208
2017-10-22 12:05:21 -07:00
Mark McDowall
12065948ca
Fixed: Not deleting episode files during upgrade when root folder is missing 2017-10-22 10:12:40 -07:00
Mark McDowall
a9e1aee295
Moved NotParentException to Disk namespace 2017-10-22 10:04:06 -07:00
Ronnie
73cb789f59
Fixed: Better import error messages 2017-10-22 10:04:05 -07:00
Mark McDowall
7345811115
Fixed: Execute On Grab script if release group is unknown 2017-10-20 20:59:43 -07:00
James White
22378b28f5 New: Add default calendar name for iCal 2017-10-13 14:06:54 -07:00
Wyall
961f0d0ad3 New: HDBits HEVC support 2017-10-11 17:43:01 -07:00
Mark McDowall
65187e7d01
Fixed: All Day iCal events in local time 2017-10-10 20:55:43 -07:00
Miha Frangez
00c3074216 Fixed: Ambiguous date options 2017-10-07 12:31:38 -07:00
Mark McDowall
e042388a97
Fixed subpack parsing tests 2017-09-22 18:18:00 -07:00
Mark McDowall
2b8ab92ef7
Fixed: Parse and reject season extras
Fixes #2176
2017-09-21 18:28:44 -07:00
Mark McDowall
7b7f48a0e3
Fixed: Media Info audio channel parsing of dual mono audio
Fixes #2182
2017-09-21 17:44:09 -07:00
Mark McDowall
714ce2640b
New: Log rejections/acceptance before importing files 2017-09-20 19:13:14 -07:00
Mark McDowall
7c5daa6000
Fixed: Long Deluge ETAs from breaking getting queue items 2017-09-20 19:12:26 -07:00
Mark McDowall
ea7d7d0a14
Set test log output via environment variable 2017-09-17 16:58:14 -07:00
Mark McDowall
8bc55c5305
Fixed: Don't ignore filenames that start with periods 2017-09-16 13:05:51 -07:00
Mark McDowall
50b01d8d00
Fixed: Case insensitive paths for static resources under Windows
Fixes #2157
2017-09-02 18:25:02 -07:00
Mark McDowall
9c1d275403
Split wanted/missing and wanted/cutoffunmet imtegration tests 2017-09-02 14:11:23 -07:00
Mark McDowall
5372ed5d40
Fixed: Don't attempt to fetch a release if the download client is disabled 2017-09-01 22:33:10 -07:00
Mark McDowall
6626397350
Move DB migration to start 2017-09-01 21:55:47 -07:00
Mark McDowall
ef8b882258
New: Initial state for torrents added to qBittorrent
Closes #2147
2017-08-31 23:33:36 -07:00
Mark McDowall
728f553802
Log when running tray app 2017-08-31 22:43:01 -07:00
Mark McDowall
bc32ad064e
Move DB migration after application router 2017-08-31 22:42:43 -07:00
Mark McDowall
56825da6b6
Don't get registered URLs until they need to be configured 2017-08-31 22:05:49 -07:00
Mark McDowall
17ff52ada1
Update help output for NzbDrone.Console 2017-08-31 22:04:55 -07:00
Mark McDowall
bbd38dec29
Fixed: Force priority items in paused SAB queue won't show as paused 2017-08-27 10:56:21 -07:00
Mark McDowall
77cdb542b6
Provider Status housekeeping
Fixed: Clean up indexer status if stored times are in the future
Fixed: Clean up download client status if stored times are in the future
Closes #1396
2017-08-27 00:09:15 -07:00
Mark McDowall
52ce2c0007
Fixed: Reject partial season packs
Fixes #2135
2017-08-26 22:55:06 -07:00
Mark McDowall
58d158a5dc
New: Parsing additional labels as WEB-DL releases 2017-08-25 23:57:59 -07:00
Mark McDowall
de5d120aac
Fixed: Formatting of audio channels from media info for some files 2017-08-25 23:47:52 -07:00
Mark McDowall
19a4d3536b
New: Initial state for torrents added to UTorrent
Closes #409
2017-08-22 20:48:53 -07:00
Mark McDowall
bea4954de9
Windows installer has option for startup folder and opening after install
Close #450
2017-08-22 19:59:47 -07:00
Mark McDowall
810701ad52
Parse path in ParseModule
Closes #358
2017-08-21 21:24:15 -07:00
Mark McDowall
f3bf50e8d7
Backup API improvements 2017-08-20 23:36:39 -07:00
Mark McDowall
fa34af8f15
uTorrent start/stop on add
New: Start torrents added to uTorrent by default
New: Option to add torrents to uTorrent in a stopped state
Closes #2141
2017-08-20 21:29:02 -07:00
Mark McDowall
9a82f45020
Added Lithuanian and Czech languages
New: Added Lithuanian language
New: Added Czech language

Closes #1857
Closes #2113
2017-08-19 21:57:42 -07:00
ECB\rotem.shoshan
7d21585f50
Added Hebrew lanugage
New: Added Hebrew language
Closes #2115
2017-08-19 21:57:42 -07:00
Mark McDowall
51d08fd8e8
Fix my typo 2017-08-19 12:46:28 -07:00
Mark McDowall
078b53dc13
Opt out of updating episodes matching season monitored state when updating series
Fixed: Season pass resetting changes when a season changed it's monitored state
Closes #2139
2017-08-19 00:26:42 -07:00
Mark McDowall
b8b0f05920
Fixed: Parsing 4k UHD as 2160p
Closes #2127
2017-08-17 21:53:22 -07:00
Mark McDowall
b8001943b4
Fixed: Size parsing of empty description from torrent RSS feeds 2017-08-14 18:01:56 -07:00
Taloth Saldono
c70740e3b6 Remove extension from ReleaseTitle.
Fixes #2118
2017-08-13 12:27:12 +02:00
Taloth Saldono
35f911286f Added jackett apikey to log cleanser. 2017-08-11 15:51:28 +02:00
James White
650f01176c Fix inconsistent naming of qBittorrent in various places 2017-08-10 21:56:04 +02:00
Mark McDowall
2246dfab05 Fixed: Logging error when accessing mount point
Fixes #1993
2017-08-10 21:46:08 +02:00
Taloth Saldono
6bfb790eb8 Fixed regression in suppressWarning. 2017-08-10 19:01:22 +02:00
Taloth Saldono
0e85e39815 More System->Disk Space cleanup. 2017-08-10 19:00:57 +02:00
Taloth Saldono
9471343533 Fixed test case for unavailable download client. 2017-08-09 23:34:52 +02:00
Taloth Saldono
2d1d1c8a99 Fixed: Changes in http redirect logic causing failed grabs and >25% cpu usage. 2017-08-09 23:12:07 +02:00
Taloth Saldono
7e5e136930 Fixed typo. 2017-08-09 23:02:25 +02:00
Taloth Saldono
c659ba1c10 Fixed: Hide some more irrelevant paths from System->Disk Space such as /boot. 2017-08-09 21:38:18 +02:00
Taloth Saldono
caf7a8c69e Fixed: Use pending download if no download client is configured instead of logging a warning. 2017-08-09 21:37:34 +02:00
Taloth Saldono
40e5626ddb Added a few more codecs, not even halfway. 2017-08-03 19:26:14 +02:00
Taloth Saldono
1b9ccc319f Fixed: TLS issue for OSX. 2017-08-03 16:53:15 +02:00
Taloth Saldono
89e804814b Revert "Change default tls provider so users won't have to set TLS_PROVIDER explicitly."
This reverts commit 8e63f7d436.

Only certain platforms need it and causes issues with OSX and cases where the user relies on btls.
2017-08-03 16:53:11 +02:00
Taloth Saldono
ce6a5713d1 Lets not take any risks here. 2017-08-03 00:20:48 +02:00
Taloth Saldono
ba01b636b9 Fixed: Recent changes to log messages prevented curl fallback from being triggered for tls1.2.
fixes #2089
2017-08-02 23:19:26 +02:00
Taloth Saldono
970006a4fe Added a bit more logging for Tracked Downloads. 2017-08-02 01:01:58 +02:00
Taloth Saldono
dbcfacbebe Updated Sentry DSN. 2017-08-01 20:09:44 +02:00
Taloth Saldono
fdaa8fb0f5 Disable test on mono managed http. 2017-08-01 19:51:29 +02:00
Taloth Saldono
f305331565 Fixed: http->https redirects do not use the tls1.2 curl fallback.
fixes #2082
2017-08-01 19:31:10 +02:00
Taloth Saldono
0ec64e7043 Fixed: DownloadedEpisodeScan API should delete source folder if ImportMode is Move. 2017-08-01 13:34:36 +02:00
Taloth Saldono
ac837c8f74 Added a bunch of extra MediaInfo formats. 2017-07-31 15:21:47 +02:00
Taloth Saldono
54d5ad4b71 Fixed unittests after MediaInfo change. 2017-07-30 23:04:10 +02:00
Taloth Saldono
27dca830cc Updated MediaInfo schema and revised logic that Formats it. Also added logic to log events to Sentry. 2017-07-30 22:47:48 +02:00
Taloth Saldono
94f2473fbb Fixed: Backup fails after recent develop release on certain platforms. (Trouble updating? see github issue #2080)
fixes #2080
2017-07-29 22:33:54 +02:00
Taloth Saldono
432666b2da Default to filename if there is no SceneName. And added Opus and MPEG-4 Visual. 2017-07-29 19:50:57 +02:00
Taloth Saldono
f91e1a3576 Tweaked error message when TransferFile destination already exists. 2017-07-29 15:34:23 +02:00
Taloth Saldono
e16e4091f1 VideoCodec formatter HEVC. 2017-07-29 15:09:29 +02:00
Taloth Saldono
b02d0a33b1 Better error message for DNS exceptions on mono. 2017-07-29 13:06:21 +02:00
Taloth Saldono
1cb25525ab Added additional codecs and more complete error message. 2017-07-29 12:33:21 +02:00
Taloth Saldono
c7d30ae703 Include releaseTitle in InvalidSceneMappingException. 2017-07-29 11:35:39 +02:00
Taloth Saldono
8e63f7d436 Change default tls provider so users won't have to set TLS_PROVIDER explicitly. 2017-07-29 11:35:10 +02:00
Taloth Saldono
36e4075629 Fixed: Improved database backup journal handling. 2017-07-29 00:38:58 +02:00
Taloth Saldono
84d2d6a1d5 Fixed: Slower daemon startup loop if Sonarr runs into non-recoverable errors such as unwritable pid/appfolder/config file. 2017-07-28 23:40:12 +02:00
Taloth Saldono
f7f155be1f Minor fixes from Sentry. 2017-07-28 20:01:45 +02:00
Taloth Saldono
1b6d6c26d5 Fixed HttpUri parsing of domain names with underscores. 2017-07-28 20:01:29 +02:00
Taloth Saldono
f8be5c9cb9 Disabled TLS health check warning. 2017-07-27 22:07:05 +02:00
Taloth Saldono
24eb196336 Added License and Copyright file for completeness. 2017-07-27 22:04:57 +02:00
Taloth Saldono
54cc19c688 Fixed error in regex. 2017-07-27 19:50:00 +02:00
Taloth Saldono
6fc4ea614d Fixed: Ignore '.unwanted' directory when importing.
fixes #2072
2017-07-27 15:49:45 +02:00
Taloth Saldono
90a42bcbad Abort indexer Test if connection failed.
fixes #2055
2017-07-27 11:25:55 +02:00
Taloth Saldono
792773323c Check for whitespace in IsValidUrl.
closes #2020
2017-07-26 22:57:38 +02:00
Mark McDowall
4b9f2e0ff7
Guard against null reference exception when parsing newznab capabilities
Closes #2054
2017-07-26 07:28:34 -07:00
Mark McDowall
5efff4d481
Cleanup exception messages
Closes #2054
2017-07-26 07:28:34 -07:00
Mark McDowall
d257b926dc
API Key for signalR connections 2017-07-26 07:28:33 -07:00
Indrek Ardel
5c4dc9ccb1 Fixed: Duplicate scene titles causes unnecessary indexer queries.
fixes #2068
2017-07-26 16:14:10 +02:00
Taloth Saldono
126b849c27 Fixed: Changed qbitTorrent 3.3.14 api.
fixes #1956
2017-07-22 17:01:34 +02:00
Mark McDowall
9312f0a462 New: Store episode renames in History
Closes #1541
2017-07-19 20:23:06 -07:00
Taloth Saldono
f293b9a44e Fixed: Added FLAC to MediaInfo renamer audio codecs. 2017-07-19 10:34:48 +02:00
Mark McDowall
fbcc0b76bf
Fixed: Try to set last write time on files moved to recycle bin
Closes #2057
2017-07-18 23:45:03 -07:00
Mark McDowall
6d0a3b1ca4
Fixed: Remove TVRage URL Link
Closes #2048
2017-07-18 23:44:41 -07:00
Taloth Saldono
2e2f79503c Sonarr stuck if Deluge didn't return an infohash. Also updated some logging. 2017-07-18 10:33:34 +02:00
Taloth Saldono
a4f51aeef9 Added XviD to MediaInfoFormatter. 2017-07-17 11:07:01 +02:00
Taloth Saldono
c68ef626d2 Fixed: Calculates wrong age for releases pushed via ReleasePush api.
closes Zymest/autodl-curl-sonarr#3
2017-07-13 17:41:37 +02:00
Mark McDowall
d4e771117d
Fixed: Reject full bluray disc releases
Closes #1995
2017-07-09 22:17:04 -07:00
Mark McDowall
6bbdefb11e
Fixed: Roksbox metadata images being generated when settings are off
Fixes #2031
2017-07-09 16:01:03 -07:00
Mark McDowall
197febe9db
Fixed: Sending Slack notifications without an icon 2017-07-05 09:16:50 -07:00
Mark McDowall
e10717f6bb
Slack improvements
New: Slack icon can be an emoji or a URL
Fixed: Icon for MatterMost Slack notifications
2017-07-04 18:59:07 -07:00
Taloth Saldono
b03f434329 Disable Nyaa forcibly. 2017-07-02 21:27:59 +02:00
Chris Dyson
68290b0385 New: Added 'Series Title, The' renaming option
Closes #371
2017-07-02 21:21:46 +02:00
Taloth Saldono
773274f3cc Update Nyaa Pantsu apiPath to the actual /feed/torznab url. 2017-07-01 12:43:30 +02:00
Taloth Saldono
a33d8968ed New: Added Nyaa Pantsu as Torznab preset for Anime. 2017-07-01 00:08:01 +02:00
Taloth Saldono
e41baccd02 New: Added AnimeTosho as Newznab and Torznab presets. 2017-06-30 23:40:16 +02:00
Taloth Saldono
fef3423019 Fixed error in NzbGet KeepHistory check and updated tests. 2017-06-30 22:01:31 +02:00
Taloth Saldono
11926d8b2d Fixed: Support for Mono 5.x with the newer BoringTLS provider. 2017-06-30 21:33:41 +02:00
Mark McDowall
4189bc6f76
Webhook improvements
New: Include Path/Relative Path for on download Webhooks
New: IsUpgrade flag for on download Webhooks
2017-06-28 16:11:05 -07:00
Mark McDowall
601b54c244
Reorder HttpMethods to match RestSharp 2017-06-28 16:11:05 -07:00
Taloth Saldono
905ab18336 Fixed: Subtitle extensions should be case-insensitive. 2017-06-27 16:22:07 +02:00
Taloth Saldono
f46a576df5 Check if NzbGet KeepHistory value is set too high instead of only checking for 0. 2017-06-27 16:22:07 +02:00
Mark McDowall
60231fbcae
Fixed: Show rounded age in minimum age rejection message
Fixes #2003
2017-06-26 22:41:22 -07:00
Mark McDowall
63860e783e
Fixed: Background logo when URL base is used
Fixes #2002
2017-06-26 22:29:52 -07:00
Mark McDowall
cf8b9df5ad
Fixed: Ignore case when importing extra files 2017-06-26 22:25:10 -07:00
Taloth Saldono
ff6841e410 Fixed: Added permanent Health Check warning to ensure Drone Factory is no longer used. 2017-06-21 19:35:32 +02:00
Taloth Saldono
ab49afe116 Don't log error on the shutdown the command execution pipeline.
closes #1961
2017-06-20 17:35:37 +02:00
Taloth Saldono
afcf136c4f Fixed regression in pending icon. 2017-06-20 17:23:01 +02:00
Taloth Saldono
d1b85444d0 Fixed DetectSampleFixture. 2017-06-19 23:48:47 +02:00
Taloth Saldono
63e0eba02f Fixed: releases with unknown seeders show on the UI as - instead of 0 to be easier to distinguish. 2017-06-19 23:30:43 +02:00
Taloth Saldono
475c99d492 Tweaked Newznab/Torznab handling of attr without value. 2017-06-19 23:30:43 +02:00
Taloth Saldono
23552c3267 Tweaked SingleInstancePolicy not to cancel startup if AppData is overridden is set. 2017-06-19 23:30:43 +02:00
Mark McDowall
a49e37239e
Log responses from qbit 2017-06-18 22:21:33 -07:00
Mark McDowall
65b936ed94
Fixed: Time left cell for pending items in queue 2017-06-18 22:15:44 -07:00
Mark McDowall
359ef04861
Fixed: Grab/Delete buttons for pending releases in queue 2017-06-18 22:10:52 -07:00
Mark McDowall
8cf028e071 Fixed: Improve sample rejection message when MediaInfo is not available
Closes #1967
2017-06-18 21:26:18 -07:00
Mark McDowall
eea3419849 New: Download client and ID for custom scripts 2017-06-18 21:17:33 -07:00
Taloth Saldono
62bc63312d Fixed: Changed Authentication cookie to prevent conflicts with other apps. (invalidates existing logins)
Closes #1962
2017-06-18 00:18:23 +02:00
Taloth Saldono
6a6d415625 Fixed: Pending releases from blocked indexers should not be grabbed.
ref #1961
2017-06-18 00:07:16 +02:00
Taloth Saldono
1fbe82ae47 Prevent back-off escalation during grace period. 2017-06-17 23:42:04 +02:00
Taloth Saldono
87f3cc9014 Fixed: Regression prevented indexers from being re-enabled after a successful Test.
ref #1961
2017-06-17 23:41:38 +02:00
Taloth Saldono
ab07a40931 New: Added Omgwtfnzbs UHD category.
closes #1977
2017-06-17 20:55:18 +02:00
Taloth Saldono
10f292b225 Removed superfluous try catches so that DownloadClient backoff logic gets triggered. 2017-06-17 20:53:26 +02:00
Mark McDowall
de5ce23989 Fixed: Redirect calls missing URL Base 2017-06-10 16:47:12 -07:00
Mark McDowall
d0e226e269
Fixed: Logging full error message to database 2017-06-10 16:25:14 -07:00
Mark McDowall
d7cb5090fc
Fixed: Twitter oAuth callback URL 2017-06-05 20:22:37 -07:00
Mark McDowall
416e9abca5
Fixed: Error message when adding a Plex server without a TV library 2017-06-04 21:10:56 -07:00
Mark McDowall
6dcb7768a9
Fixed broken test and add a couple more for ProcessDownloadDecisions 2017-06-04 00:52:34 -07:00
Mark McDowall
baf83b4c71
Fixed: Error when processing manual import decisions
Fixes #1590
2017-06-03 21:10:56 -07:00
Mark McDowall
285288db1a
Additional logging when an import decision cannot be made 2017-06-03 21:10:56 -07:00
Jeremy Ryan
ec3dd982f6 Update EpisodeFileEditorLayoutTemplate.hbs 2017-06-03 21:10:20 -07:00
Drew Freyling
410aa467b8 include css files in minification 2017-06-03 21:08:35 -07:00
Mark McDowall
0c89a4ae8f Store releases when download client is unavailable
New: Retry releases when download client was unavailable
Closes #949
2017-06-03 20:41:32 -07:00
Taloth Saldono
a1edbafa8a Removed ugly UUID= VolumeLabel from mounts. 2017-05-27 22:26:41 +02:00
Taloth Saldono
ef5a400c68 Added missing ACC audio format. 2017-05-27 22:19:55 +02:00
Taloth Saldono
8cc02a9d9c Fixed: Minimum seeding check causing exception when release was pushed via api instead of by indexer. 2017-05-27 22:02:30 +02:00
Taloth Saldono
e83e852e0d Added ability for HealthChecks to run on specific events. 2017-05-27 20:45:01 +02:00
Taloth Saldono
4e10d30cf6 Added Status refreshes to Download Monitoring Service and allow DownloadService to report success (but not failure). 2017-05-27 20:44:58 +02:00
Taloth Saldono
f335cc1af8 Fixed: Prevent Download Client from being queried every minute if it failed repeatedly similar to Indexer temporarily disabled logic. 2017-05-27 20:44:55 +02:00
Taloth Saldono
f4bea5512c Refactored IndexerStatusService into Thingy Provider architecture. 2017-05-27 14:59:37 +02:00
Taloth Saldono
9f8091e4d7 Fixed: Added wildcard to BTN season searches to pick up 'Season x - Episodes 1-10' formats.
Closes #1946
2017-05-26 15:50:34 +02:00
Mark McDowall
5aa02eb15c
Cleanup/fix EpisodeMonitoredService
Fixed: Unmonitor episodes when the season is unmonitored when adding the series
Fixes #1852
2017-05-23 22:04:56 -07:00
Mark McDowall
6bbe4ce066
Fixed: Ensure an API Key is set when starting Sonarr
Closes #1514
2017-05-22 21:54:18 -07:00
Mark McDowall
563b5ef017
Fixed: Don't use invalid scene mappings. Fixes #1627 2017-05-22 21:39:13 -07:00
Mark McDowall
b9df5634bf
New: Link to more information on RSS sync interval
Closes #1918
2017-05-22 20:55:30 -07:00
Mark McDowall
755575d107
Fixed: Follow 301 redirects when fetching torrents
Closes #1929
2017-05-22 18:09:59 -07:00
Taloth Saldono
8eaab46488 Fixed up some errors and do the guid cache fix on the module instead of backend coz that would cause other issues. 2017-05-21 22:01:03 +02:00
Taloth Saldono
4fbc481780 Fixed: Processing of mixed newznab/torznab api such as the experimental animetosho api.
Ref #1384
2017-05-21 21:10:54 +02:00
Mark McDowall
a41b5723d4 New: Ability to set minimum seeders on a per indexer basis 2017-05-19 12:08:30 -07:00
Mark McDowall
c2b66cf524 Fixed: Deleting an episode file from the UI that was already deleted from disk
Fixes #1782
2017-05-19 12:07:57 -07:00
Mark McDowall
0d782e1cac Consistent formatting for MediaInfo in various locations
Fixed: Stream details for MP3 and EAC3 in Kodi metadata
Closes #1534
2017-05-19 11:18:50 -07:00
Mark McDowall
edf549d0fd
Fixed: Improved message when a conflicting slug is added 2017-05-17 21:42:57 -07:00
Mark McDowall
cf7ce9804f
Fixed: Ignore file quality matching release quality for unknown quality releases 2017-05-17 21:15:21 -07:00
Mark McDowall
e8d01daf03
Fixed: Ignore file quality matching release quality for season packs 2017-05-15 23:58:07 -07:00
Taloth Saldono
766520b851 Renamed DownloadClientStatus to DownloadClientInfo to avoid conflict. 2017-05-13 00:16:53 +02:00
Taloth Saldono
14144bd4d9 Renamed IndexerStatus.IndexerId to ProviderId. 2017-05-13 00:16:53 +02:00
Taloth Saldono
7b0e40d5d0 Replaced Url with BaseUrl in most indexers. 2017-05-13 00:16:53 +02:00
Taloth Saldono
2dbf095fd5 Fixed: Regression in Quality fallback by extension. 2017-05-13 00:13:12 +02:00
Taloth Saldono
2a86f8c241 Fixed: UI Series lookup autocomplete with diacritics.
Closes #1915
2017-05-11 20:37:07 +02:00
Taloth Saldono
c184e7ddcc Fixed: Multiple Scene Mapping exception even when the mappings pointed to the same tvdbid.
Closes #1917
2017-05-11 06:32:09 +02:00
Taloth Saldono
95c81f8905 Fixed exception in MountCheck if RootDirectory cannot be found. 2017-05-10 23:08:17 +02:00
Mark McDowall
db15949704
Fixed: Better error message when searching for episode without an absolute episode number 2017-05-09 20:00:21 -07:00
Mark McDowall
a63248401e
Fixed: Width in Kodi Metadata 2017-05-09 20:00:21 -07:00
Kyse
1b32411219 New: Health Check warning if series folder is mounted with 'ro' option on linux
Closes #1867
2017-05-09 21:07:24 +02:00
Taloth Saldono
cd7368512d Fixed Scene Mapping error message. 2017-05-09 17:26:45 +02:00
Taloth Saldono
a5bc4a8f11 Added ability to filter scene mappings by regex via services. 2017-05-06 14:05:49 +02:00
Drew Freyling
2ae41a3404 remove redundant IE meta tag as we use http header instead 2017-05-05 22:26:28 -07:00
Drew Freyling
312136a57c use cleancss for minification 2017-05-05 22:26:10 -07:00
Mark McDowall
53e51af9c7
Fixed: Don't import the same file again
Closes #929
2017-05-02 22:52:59 -07:00
Mark McDowall
f852ca91c0
Clean up GrabbedReleaseQualityFixture 2017-05-02 22:52:11 -07:00
Mark McDowall
413dd51db1
Fix unit test 2017-05-02 22:06:18 -07:00
Mark McDowall
9d93fc1092
New: Prevent automatic import if file quality differs from grabbed release quality 2017-05-02 18:44:42 -07:00
Lloyd Sparkes
0bbb82c67f Update SocksWebProxy to fix #1641 2017-04-29 17:11:55 +02:00
Mark McDowall
cd8ae0d036
Added test to validate season pack being grabbed when only one episode is monitored
Closes #1862
2017-04-28 17:37:29 -07:00
Taloth Saldono
d726a7acb2 Fixed: Sonarr UI Authentication cookie should be placed on path (UrlBase) instead of domain alone.
fixes #1874
2017-04-27 23:33:04 +02:00
Mark McDowall
c94636e2b3
Placeholders for language profile migrations 2017-04-26 11:45:07 -07:00
Taloth Saldono
3749f3e2e5 Fixed missing icon preventing detailed explanation validation errors explanations from appearing. 2017-04-25 19:16:43 +02:00
Taloth Saldono
1f93bec055 Updated Transmission tests. 2017-04-24 21:53:40 +02:00
Taloth Saldono
a003a89b14 Fixed: Sonarr not importing torrents in Vuze if the torrent already finished seeding and was stopped. 2017-04-24 21:08:50 +02:00
Taloth Saldono
35fca89dad Fixed: Incorrect imports with Vuze when torrent contains a single file.
fixes #1805
2017-04-23 17:24:07 +02:00
Mark McDowall
e97e13e897
Fixed: Smarter application update completed message
Closes #1864
2017-04-21 10:52:31 -07:00
Taloth Saldono
f8d5f1fc94 Added -Scrambled to the ReleaseGroup cleanup list. 2017-04-14 20:47:17 +02:00
Taloth Saldono
46a1ff3e2d Tweaked parser to handle S01.Ep01.
closes #1849
2017-04-14 20:44:14 +02:00
Mark McDowall
f36d5dc881 Moving and Removing of downloads in usenet clients
Fixed: Moving items triggered via post-processing scripts
Fixed: Removing failed downloads fromusenet clients
2017-04-12 17:41:22 -07:00
Taloth Saldono
f8b8fcfb8d Fixed: Handling of priority setting when queueing is disabled in qBittorrent.
fixes #1841
2017-04-12 20:49:28 +02:00
Taloth Saldono
de7f68570e Fixed: Regression causing nzbToMedia imports to be copied instead of moved. 2017-04-12 20:49:28 +02:00
Taloth Saldono
fa006d85fd New: Check whether an existing episode file was deleted before grabbing an upgrade, to avoid timing issues in combination with Ignore Deleted Episodes. 2017-04-12 20:46:36 +02:00
Mark McDowall
413ce1d9a7 Fixed: Double periods in extra file names after rename 2017-04-11 20:32:34 -07:00
Mark McDowall
41f769790d Fix issue adding a series when TitleSlug for another series is null
Fixed: Adding a series when an existing series is has a null slug
Closes #1840
2017-04-11 17:57:29 -07:00
Taloth Saldono
b63bcd16a7 Fixed: Sample check has too little margin for 2 min anime with 1 minute files. Lowered to 15 sec. 2017-04-10 17:46:08 +02:00
Mark McDowall
b485bdaeec Fixed: Unable to execute custom scripts if IMDB ID is null
Fixes #1825
2017-04-08 08:20:57 -07:00
Taloth Saldono
b70d167911 Apply Cleanse to Exception Data as well. 2017-04-08 12:38:39 +02:00
Taloth Saldono
924fe80997 Fixed RssParser test. 2017-04-07 22:36:39 +02:00
Taloth Saldono
cd450a44bf Should not empty install folder, MirrorFolder will take care of it. 2017-04-07 22:09:49 +02:00
Taloth Saldono
35741b9cae Added a few more files to ignore during file copy. 2017-04-07 22:07:42 +02:00
Taloth Saldono
c9d1807670 Sentry should use CleanseLogMessage. 2017-04-07 20:42:39 +02:00
Taloth Saldono
94886e767b Fixed: UnsupportedFeedException should log error for each item 2017-04-07 19:32:12 +02:00
Taloth Saldono
e4c3418987 Fixed: Failing Newznab capabilities request should trigger automatic indexer backoff logic. 2017-04-07 19:10:08 +02:00
Taloth Saldono
5613ab05e0 Fixed: Sabnzbd/NzbGet not processing history items properly after last update. 2017-03-31 18:56:45 +02:00
Taloth Saldono
372442af2c fixed broken tests. 2017-03-30 23:20:49 +02:00
Taloth Saldono
28c45f941b Cleanup of commented out code. 2017-03-30 22:28:00 +02:00
Marcelo Castagna
ea1616586f Fixed: Import from torrent Download Station should move since DS maintains an internal copy for seeding. 2017-03-30 22:26:11 +02:00
Mark McDowall
e48600da42 New: TvMaze and IMDB IDs added to custom script environment variables 2017-03-29 18:22:14 -07:00
Mark McDowall
5d9d2e684e New: Paths for deleted files when upgrading an existing file 2017-03-29 13:22:37 -07:00
Mark McDowall
2e392e0f5e New: Additional variables for custom script on grab events 2017-03-29 13:12:37 -07:00
701 changed files with 16730 additions and 4036 deletions

View file

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

28
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View file

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

View file

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

View file

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

7
.github/SUPPORT.md vendored Normal file
View file

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

1
.gitignore vendored
View file

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

11
COPYRIGHT.md Normal file
View file

@ -0,0 +1,11 @@
Copyright (C)
2014-2017 Mark McDowall, Keivan Beigi, Taloth Saldono and contributors
2010-2014 Mark McDowall, Keivan Beigi and contributors
_Please refer to the git commit log for details on all contributors and their respective contributions._
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You can find a copy of the GNU General Public License in the LICENSE.md file and <http://www.gnu.org/licenses/>.

675
LICENSE.md Normal file
View file

@ -0,0 +1,675 @@
### GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
<http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
### Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom
to share and change all versions of a program--to make sure it remains
free software for all its users. We, the Free Software Foundation, use
the GNU General Public License for most of our software; it applies
also to any other work released this way by its authors. You can apply
it to your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you
have certain responsibilities if you distribute copies of the
software, or if you modify it: responsibilities to respect the freedom
of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the
manufacturer can do so. This is fundamentally incompatible with the
aim of protecting users' freedom to change the software. The
systematic pattern of such abuse occurs in the area of products for
individuals to use, which is precisely where it is most unacceptable.
Therefore, we have designed this version of the GPL to prohibit the
practice for those products. If such problems arise substantially in
other domains, we stand ready to extend this provision to those
domains in future versions of the GPL, as needed to protect the
freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish
to avoid the special danger that patents applied to a free program
could make it effectively proprietary. To prevent this, the GPL
assures that patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
### TERMS AND CONDITIONS
#### 0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds
of works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of
an exact copy. The resulting work is called a "modified version" of
the earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user
through a computer network, with no transfer of a copy, is not
conveying.
An interactive user interface displays "Appropriate Legal Notices" to
the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
#### 1. Source Code.
The "source code" for a work means the preferred form of the work for
making modifications to it. "Object code" means any non-source form of
a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users can
regenerate automatically from other parts of the Corresponding Source.
The Corresponding Source for a work in source code form is that same
work.
#### 2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not convey,
without conditions so long as your license otherwise remains in force.
You may convey covered works to others for the sole purpose of having
them make modifications exclusively for you, or provide you with
facilities for running those works, provided that you comply with the
terms of this License in conveying all material for which you do not
control copyright. Those thus making or running the covered works for
you must do so exclusively on your behalf, under your direction and
control, on terms that prohibit them from making any copies of your
copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under the
conditions stated below. Sublicensing is not allowed; section 10 makes
it unnecessary.
#### 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such
circumvention is effected by exercising rights under this License with
respect to the covered work, and you disclaim any intention to limit
operation or modification of the work as a means of enforcing, against
the work's users, your or third parties' legal rights to forbid
circumvention of technological measures.
#### 4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
#### 5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these
conditions:
- a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
- b) The work must carry prominent notices stating that it is
released under this License and any conditions added under
section 7. This requirement modifies the requirement in section 4
to "keep intact all notices".
- c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
- d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
#### 6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms of
sections 4 and 5, provided that you also convey the machine-readable
Corresponding Source under the terms of this License, in one of these
ways:
- a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
- b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the Corresponding
Source from a network server at no charge.
- c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
- d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
- e) Convey the object code using peer-to-peer transmission,
provided you inform other peers where the object code and
Corresponding Source of the work are being offered to the general
public at no charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal,
family, or household purposes, or (2) anything designed or sold for
incorporation into a dwelling. In determining whether a product is a
consumer product, doubtful cases shall be resolved in favor of
coverage. For a particular product received by a particular user,
"normally used" refers to a typical or common use of that class of
product, regardless of the status of the particular user or of the way
in which the particular user actually uses, or expects or is expected
to use, the product. A product is a consumer product regardless of
whether the product has substantial commercial, industrial or
non-consumer uses, unless such uses represent the only significant
mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to
install and execute modified versions of a covered work in that User
Product from a modified version of its Corresponding Source. The
information must suffice to ensure that the continued functioning of
the modified object code is in no case prevented or interfered with
solely because modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or
updates for a work that has been modified or installed by the
recipient, or for the User Product in which it has been modified or
installed. Access to a network may be denied when the modification
itself materially and adversely affects the operation of the network
or violates the rules and protocols for communication across the
network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
#### 7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders
of that material) supplement the terms of this License with terms:
- a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
- b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
- c) Prohibiting misrepresentation of the origin of that material,
or requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
- d) Limiting the use for publicity purposes of names of licensors
or authors of the material; or
- e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
- f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions
of it) with contractual assumptions of liability to the recipient,
for any liability that these contractual assumptions directly
impose on those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions; the
above requirements apply either way.
#### 8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your license
from a particular copyright holder is reinstated (a) provisionally,
unless and until the copyright holder explicitly and finally
terminates your license, and (b) permanently, if the copyright holder
fails to notify you of the violation by some reasonable means prior to
60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
#### 9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or run
a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
#### 10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
#### 11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims owned
or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within the
scope of its coverage, prohibits the exercise of, or is conditioned on
the non-exercise of one or more of the rights that are specifically
granted under this License. You may not convey a covered work if you
are a party to an arrangement with a third party that is in the
business of distributing software, under which you make payment to the
third party based on the extent of your activity of conveying the
work, and under which the third party grants, to any of the parties
who would receive the covered work from you, a discriminatory patent
license (a) in connection with copies of the covered work conveyed by
you (or copies made from those copies), or (b) primarily for and in
connection with specific products or compilations that contain the
covered work, unless you entered into that arrangement, or that patent
license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
#### 12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under
this License and any other pertinent obligations, then as a
consequence you may not convey it at all. For example, if you agree to
terms that obligate you to collect a royalty for further conveying
from those to whom you convey the Program, the only way you could
satisfy both those terms and this License would be to refrain entirely
from conveying the Program.
#### 13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
#### 14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions
of the GNU General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in
detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies that a certain numbered version of the GNU General Public
License "or any later version" applies to it, you have the option of
following the terms and conditions either of that numbered version or
of any later version published by the Free Software Foundation. If the
Program does not specify a version number of the GNU General Public
License, you may choose any version ever published by the Free
Software Foundation.
If the Program specifies that a proxy can decide which future versions
of the GNU General Public License can be used, that proxy's public
statement of acceptance of a version permanently authorizes you to
choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
#### 15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT
WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND
PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
CORRECTION.
#### 16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR
CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES
ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT
NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR
LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM
TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER
PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
#### 17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
### How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these
terms.
To do so, attach the following notices to the program. It is safest to
attach them to the start of each source file to most effectively state
the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper
mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands \`show w' and \`show c' should show the
appropriate parts of the General Public License. Of course, your
program's commands might be different; for a GUI interface, you would
use an "about box".
You should also get your employer (if you work as a programmer) or
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. For more information on this, and how to apply and follow
the GNU GPL, see <http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your
program into proprietary programs. If your program is a subroutine
library, you may consider it more useful to permit linking proprietary
applications with the library. If this is what you want to do, use the
GNU Lesser General Public License instead of this License. But first,
please read <http://www.gnu.org/philosophy/why-not-lgpl.html>.

View file

@ -25,7 +25,7 @@ gulp.task('copyHtml', function () {
});
gulp.task('copyContent', function () {
return gulp.src([paths.src.content + '**/*.*', '!**/*.less'])
return gulp.src([paths.src.content + '**/*.*', '!**/*.less', '!**/*.css'])
.pipe(gulp.dest(paths.dest.content))
.pipe(livereload());
});

View file

@ -5,7 +5,7 @@ var postcss = require('gulp-postcss');
var sourcemaps = require('gulp-sourcemaps');
var autoprefixer = require('autoprefixer-core');
var livereload = require('gulp-livereload');
var cleancss = require('gulp-clean-css');
var print = require('gulp-print');
var paths = require('./paths');
var errorHandler = require('./errorHandler');
@ -16,6 +16,10 @@ gulp.task('less', function() {
paths.src.content + 'bootstrap.less',
paths.src.content + 'theme.less',
paths.src.content + 'overrides.less',
paths.src.content + 'bootstrap.toggle-switch.css',
paths.src.content + 'fullcalendar.css',
paths.src.content + 'Messenger/messenger.css',
paths.src.content + 'Messenger/messenger.flat.css',
paths.src.root + 'Series/series.less',
paths.src.root + 'Activity/activity.less',
paths.src.root + 'AddSeries/addSeries.less',
@ -33,12 +37,13 @@ gulp.task('less', function() {
.pipe(sourcemaps.init())
.pipe(less({
dumpLineNumbers : 'false',
compress : true,
yuicompress : true,
compress : false,
yuicompress : false,
ieCompat : true,
strictImports : true
}))
.pipe(postcss([ autoprefixer({ browsers: ['last 2 versions'] }) ]))
.pipe(cleancss())
.on('error', errorHandler.onError)
.pipe(sourcemaps.write(paths.dest.content))
.pipe(gulp.dest(paths.dest.content))

View file

@ -20,6 +20,7 @@
"del": "1.2.0",
"gulp": "3.9.0",
"gulp-cached": "1.1.0",
"gulp-clean-css": "^3.0.4",
"gulp-concat": "2.6.0",
"gulp-declare": "0.3.0",
"gulp-handlebars": "3.0.1",

View file

@ -1,3 +1,3 @@
#SET BUILD_NUMBER=1
#SET branch=develop
REM SET BUILD_NUMBER=1
REM SET branch=develop
inno\ISCC.exe nzbdrone.iss

View file

@ -40,8 +40,11 @@ VersionInfoVersion={#BuildNumber}
Name: "english"; MessagesFile: "compiler:Default.isl"
[Tasks]
;Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
Name: "windowsService"; Description: "Install as a Windows Service"
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"
Name: "windowsService"; Description: "Install Windows Service (Starts when the computer starts)"; GroupDescription: "Start automatically"; Flags: exclusive
Name: "startupShortcut"; Description: "Create shortcut in Startup folder (Starts when you log into Windows)"; GroupDescription: "Start automatically"; Flags: exclusive unchecked
Name: "none"; Description: "Do not start automatically"; GroupDescription: "Start automatically"; Flags: exclusive unchecked
[Files]
Source: "..\_output\NzbDrone.exe"; DestDir: "{app}"; Flags: ignoreversion
@ -51,10 +54,13 @@ Source: "..\_output\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs cr
[Icons]
Name: "{group}\{#AppName}"; Filename: "{app}\{#AppExeName}"; Parameters: "/icon"
Name: "{commondesktop}\{#AppName}"; Filename: "{app}\{#AppExeName}"; Parameters: "/icon"
Name: "{userstartup}\{#AppName}"; Filename: "{app}\NzbDrone.exe"; WorkingDir: "{app}"; Tasks: startupShortcut
[Run]
Filename: "{app}\nzbdrone.console.exe"; Parameters: "/u"; Flags: waituntilterminated;
Filename: "{app}\nzbdrone.console.exe"; Parameters: "/i"; Flags: waituntilterminated; Tasks: windowsService
Filename: "{app}\nzbdrone.console.exe"; Parameters: "/u"; Flags: runhidden waituntilterminated;
Filename: "{app}\nzbdrone.console.exe"; Parameters: "/i"; Flags: runhidden waituntilterminated; Tasks: windowsService
Filename: "{app}\NzbDrone.exe"; Description: "Open Sonarr"; Flags: postinstall skipifsilent nowait; Tasks: windowsService;
Filename: "{app}\NzbDrone.exe"; Description: "Start Sonarr"; Flags: postinstall skipifsilent nowait; Tasks: startupShortcut none;
[UninstallRun]
Filename: "{app}\nzbdrone.console.exe"; Parameters: "/u"; Flags: waituntilterminated skipifdoesntexist

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -33,12 +33,12 @@ namespace NzbDrone.Api.Authentication
{
if (_configFileProvider.AuthenticationMethod == AuthenticationType.Forms)
{
RegisterFormsAuth(pipelines);
RegisterFormsAuth(pipelines);
}
else if (_configFileProvider.AuthenticationMethod == AuthenticationType.Basic)
{
pipelines.EnableBasicAuthentication(new BasicAuthenticationConfiguration(_authenticationService, "Sonarr"));
pipelines.EnableBasicAuthentication(new BasicAuthenticationConfiguration(_authenticationService, "Sonarr"));
}
pipelines.BeforeRequest.AddItemToEndOfPipeline((Func<NancyContext, Response>) RequiresAuthentication);
@ -64,10 +64,13 @@ namespace NzbDrone.Api.Authentication
new DefaultHmacProvider(new PassphraseKeyGenerator(_configService.HmacPassphrase, Encoding.ASCII.GetBytes(_configService.HmacSalt)))
);
FormsAuthentication.FormsAuthenticationCookieName = "_ncfa_sonarr";
FormsAuthentication.Enable(pipelines, new FormsAuthenticationConfiguration
{
RedirectUrl = _configFileProvider.UrlBase + "/login",
UserMapper = _authenticationService,
Path = _configFileProvider.UrlBase,
CryptographyConfiguration = cryptographyConfiguration
});
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,16 +1,13 @@
using System.Collections.Generic;
using System.IO;
using NLog;
using NzbDrone.Api.REST;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.MediaFiles;
using NzbDrone.Core.MediaFiles.Events;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.Tv;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Exceptions;
using NzbDrone.SignalR;
using HttpStatusCode = System.Net.HttpStatusCode;
namespace NzbDrone.Api.EpisodeFiles
{
@ -18,27 +15,21 @@ namespace NzbDrone.Api.EpisodeFiles
IHandle<EpisodeFileAddedEvent>
{
private readonly IMediaFileService _mediaFileService;
private readonly IDiskProvider _diskProvider;
private readonly IRecycleBinProvider _recycleBinProvider;
private readonly IDeleteMediaFiles _mediaFileDeletionService;
private readonly ISeriesService _seriesService;
private readonly IQualityUpgradableSpecification _qualityUpgradableSpecification;
private readonly Logger _logger;
public EpisodeFileModule(IBroadcastSignalRMessage signalRBroadcaster,
IMediaFileService mediaFileService,
IDiskProvider diskProvider,
IRecycleBinProvider recycleBinProvider,
IDeleteMediaFiles mediaFileDeletionService,
ISeriesService seriesService,
IQualityUpgradableSpecification qualityUpgradableSpecification,
Logger logger)
IQualityUpgradableSpecification qualityUpgradableSpecification)
: base(signalRBroadcaster)
{
_mediaFileService = mediaFileService;
_diskProvider = diskProvider;
_recycleBinProvider = recycleBinProvider;
_mediaFileDeletionService = mediaFileDeletionService;
_seriesService = seriesService;
_qualityUpgradableSpecification = qualityUpgradableSpecification;
_logger = logger;
GetResourceById = GetEpisodeFile;
GetResourceAll = GetEpisodeFiles;
UpdateResource = SetQuality;
@ -77,13 +68,15 @@ namespace NzbDrone.Api.EpisodeFiles
private void DeleteEpisodeFile(int id)
{
var episodeFile = _mediaFileService.Get(id);
var series = _seriesService.GetSeries(episodeFile.SeriesId);
var fullPath = Path.Combine(series.Path, episodeFile.RelativePath);
var subfolder = _diskProvider.GetParentFolder(series.Path).GetRelativePath(_diskProvider.GetParentFolder(fullPath));
_logger.Info("Deleting episode file: {0}", fullPath);
_recycleBinProvider.DeleteFile(fullPath, subfolder);
_mediaFileService.Delete(episodeFile, DeleteMediaFileReason.Manual);
if (episodeFile == null)
{
throw new NzbDroneClientException(HttpStatusCode.NotFound, "Episode file not found");
}
var series = _seriesService.GetSeries(episodeFile.SeriesId);
_mediaFileDeletionService.DeleteEpisodeFile(series, episodeFile);
}
public void Handle(EpisodeFileAddedEvent message)

View file

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

View file

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

View file

@ -14,7 +14,7 @@ namespace NzbDrone.Api.Episodes
{
public abstract class EpisodeModuleWithSignalR : NzbDroneRestModuleWithSignalR<EpisodeResource, Episode>,
IHandle<EpisodeGrabbedEvent>,
IHandle<EpisodeDownloadedEvent>
IHandle<EpisodeImportedEvent>
{
protected readonly IEpisodeService _episodeService;
protected readonly ISeriesService _seriesService;
@ -115,9 +115,14 @@ namespace NzbDrone.Api.Episodes
}
}
public void Handle(EpisodeDownloadedEvent message)
public void Handle(EpisodeImportedEvent message)
{
foreach (var episode in message.Episode.Episodes)
if (!message.NewDownload)
{
return;
}
foreach (var episode in message.EpisodeInfo.Episodes)
{
BroadcastResourceChange(ModelAction.Updated, episode.Id);
}

View file

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

View file

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

View file

@ -33,7 +33,8 @@ namespace NzbDrone.Api.Extensions.Pipelines
try
{
if (
!response.ContentType.Contains("image")
response.Contents != Response.NoBody
&& !response.ContentType.Contains("image")
&& !response.ContentType.Contains("font")
&& request.Headers.AcceptEncoding.Any(x => x.Contains("gzip"))
&& !AlreadyGzipEncoded(response)
@ -80,4 +81,4 @@ namespace NzbDrone.Api.Extensions.Pipelines
return false;
}
}
}
}

View file

@ -0,0 +1,46 @@
using System;
using Nancy;
using Nancy.Bootstrapper;
using Nancy.Responses;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Api.Extensions.Pipelines
{
public class UrlBasePipeline : IRegisterNancyPipeline
{
private readonly string _urlBase;
public UrlBasePipeline(IConfigFileProvider configFileProvider)
{
_urlBase = configFileProvider.UrlBase;
}
public int Order => 99;
public void Register(IPipelines pipelines)
{
if (_urlBase.IsNotNullOrWhiteSpace())
{
pipelines.BeforeRequest.AddItemToStartOfPipeline((Func<NancyContext, Response>) Handle);
}
}
private Response Handle(NancyContext context)
{
var basePath = context.Request.Url.BasePath;
if (basePath.IsNullOrWhiteSpace())
{
return new RedirectResponse($"{_urlBase}{context.Request.Path}{context.Request.Url.Query}");
}
if (_urlBase != basePath)
{
return new NotFoundResponse();
}
return null;
}
}
}

View file

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

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using System.Text.RegularExpressions;
using Nancy;
@ -17,7 +17,7 @@ namespace NzbDrone.Api.Frontend.Mappers
private readonly IAnalyticsService _analyticsService;
private readonly Func<ICacheBreakerProvider> _cacheBreakProviderFactory;
private readonly string _indexPath;
private static readonly Regex ReplaceRegex = new Regex(@"(?:(?<attribute>href|src)=\"")(?<path>.*?(?<extension>css|js|png|ico|ics))(?:\"")(?:\s(?<nohash>data-no-hash))?", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex ReplaceRegex = new Regex(@"(?:(?<attribute>href|src)=\"")(?<path>.*?(?<extension>css|js|png|ico|ics|svg))(?:\"")(?:\s(?<nohash>data-no-hash))?", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static string API_KEY;
private static string URL_BASE;
@ -49,7 +49,12 @@ namespace NzbDrone.Api.Frontend.Mappers
public override bool CanHandle(string resourceUrl)
{
return !resourceUrl.Contains(".") && !resourceUrl.StartsWith("/login");
resourceUrl = resourceUrl.ToLowerInvariant();
return !resourceUrl.StartsWith("/content") &&
!resourceUrl.StartsWith("/mediacover") &&
!resourceUrl.Contains(".") &&
!resourceUrl.StartsWith("/login");
}
public override Response GetResponse(string resourceUrl)
@ -113,4 +118,4 @@ namespace NzbDrone.Api.Frontend.Mappers
return _generatedContent;
}
}
}
}

View file

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

View file

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

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.IO;
using NLog;
using Nancy;
@ -21,10 +21,7 @@ namespace NzbDrone.Api.Frontend.Mappers
_diskProvider = diskProvider;
_logger = logger;
if (!RuntimeInfo.IsProduction)
{
_caseSensitive = StringComparison.OrdinalIgnoreCase;
}
_caseSensitive = RuntimeInfo.IsProduction ? DiskProviderBase.PathStringComparison : StringComparison.OrdinalIgnoreCase;
}
public abstract string Map(string resourceUrl);
@ -50,6 +47,5 @@ namespace NzbDrone.Api.Frontend.Mappers
{
return File.OpenRead(filePath);
}
}
}
}

View file

@ -1,7 +1,6 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using Nancy.Responses;
using NLog;
using Nancy;
using NzbDrone.Api.Frontend.Mappers;
@ -38,20 +37,6 @@ namespace NzbDrone.Api.Frontend
return new NotFoundResponse();
}
//Redirect to the subfolder if the request went to the base URL
if (path.Equals("/"))
{
var urlBase = _configFileProvider.UrlBase;
if (!string.IsNullOrEmpty(urlBase))
{
if (Request.Url.BasePath != urlBase)
{
return new RedirectResponse(urlBase + "/");
}
}
}
var mapper = _requestMappers.SingleOrDefault(m => m.CanHandle(path));
if (mapper != null)

View file

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

View file

@ -53,7 +53,7 @@ namespace NzbDrone.Api.Indexers
private Response DownloadRelease(ReleaseResource release)
{
var remoteEpisode = _remoteEpisodeCache.Find(release.Guid);
var remoteEpisode = _remoteEpisodeCache.Find(GetCacheKey(release));
if (remoteEpisode == null)
{
@ -68,7 +68,7 @@ namespace NzbDrone.Api.Indexers
}
catch (ReleaseDownloadException ex)
{
_logger.Error(ex);
_logger.Error(ex, ex.Message);
throw new NzbDroneClientException(HttpStatusCode.Conflict, "Getting release from indexer failed");
}
@ -113,8 +113,14 @@ namespace NzbDrone.Api.Indexers
protected override ReleaseResource MapDecision(DownloadDecision decision, int initialWeight)
{
_remoteEpisodeCache.Set(decision.RemoteEpisode.Release.Guid, decision.RemoteEpisode, TimeSpan.FromMinutes(30));
return base.MapDecision(decision, initialWeight);
var resource = base.MapDecision(decision, initialWeight);
_remoteEpisodeCache.Set(GetCacheKey(resource), decision.RemoteEpisode, TimeSpan.FromMinutes(30));
return resource;
}
private string GetCacheKey(ReleaseResource resource)
{
return string.Concat(resource.IndexerId, "_", resource.Guid);
}
}
}

View file

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

View file

@ -166,9 +166,9 @@ namespace NzbDrone.Api.Indexers
model.DownloadProtocol = resource.DownloadProtocol;
model.TvdbId = resource.TvdbId;
model.TvRageId = resource.TvRageId;
model.PublishDate = resource.PublishDate;
model.PublishDate = resource.PublishDate.ToUniversalTime();
return model;
}
}
}
}

View file

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

View file

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

View file

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

View file

@ -1,5 +1,6 @@
using NzbDrone.Api.Episodes;
using NzbDrone.Api.Series;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Parser;
namespace NzbDrone.Api.Parse
@ -18,7 +19,8 @@ namespace NzbDrone.Api.Parse
private ParseResource Parse()
{
var title = Request.Query.Title.Value as string;
var parsedEpisodeInfo = Parser.ParseTitle(title);
var path = Request.Query.Path.Value as string;
var parsedEpisodeInfo = path.IsNotNullOrWhiteSpace() ? Parser.ParsePath(path) : Parser.ParseTitle(title);
if (parsedEpisodeInfo == null)
{

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -21,9 +21,9 @@ namespace NzbDrone.Api.System.Backup
return backups.Select(b => new BackupResource
{
Id = b.Path.GetHashCode(),
Name = Path.GetFileName(b.Path),
Path = b.Path,
Id = b.Name.GetHashCode(),
Name = b.Name,
Path = $"/backup/{b.Type.ToString().ToLower()}/{b.Name}",
Type = b.Type,
Time = b.Time
}).ToList();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Common.Test.ExtensionTests
{
[TestFixture]
public class UrlExtensionsFixture
{
[TestCase("http://my.local/url")]
[TestCase("https://my.local/url")]
public void should_report_as_valid_url(string url)
{
url.IsValidUrl().Should().BeTrue();
}
[TestCase("")]
[TestCase(" http://my.local/url")]
[TestCase("http://my.local/url ")]
public void should_report_as_invalid_url(string url)
{
url.IsValidUrl().Should().BeFalse();
}
}
}

View file

@ -61,7 +61,7 @@ namespace NzbDrone.Common.Test.Http
response.Content.Should().NotBeNullOrWhiteSpace();
}
[Test]
public void should_execute_https_get()
{
@ -139,7 +139,54 @@ namespace NzbDrone.Common.Test.Http
var request = new HttpRequest($"http://{_httpBinHost}/redirect/1");
request.AllowAutoRedirect = true;
Subject.Get(request);
var response = Subject.Get(request);
response.StatusCode.Should().Be(HttpStatusCode.OK);
ExceptionVerification.ExpectedErrors(0);
}
[Test]
public void should_not_follow_redirects()
{
var request = new HttpRequest($"http://{_httpBinHost}/redirect/1");
request.AllowAutoRedirect = false;
var response = Subject.Get(request);
response.StatusCode.Should().Be(HttpStatusCode.Found);
ExceptionVerification.ExpectedErrors(1);
}
[Test]
public void should_follow_redirects_to_https()
{
if (typeof(TDispatcher) == typeof(ManagedHttpDispatcher) && PlatformInfo.IsMono)
{
Assert.Ignore("Will fail on tls1.2 via managed dispatcher, ignore.");
}
var request = new HttpRequestBuilder($"http://{_httpBinHost}/redirect-to")
.AddQueryParam("url", $"https://sonarr.tv/")
.Build();
request.AllowAutoRedirect = true;
var response = Subject.Get(request);
response.StatusCode.Should().Be(HttpStatusCode.OK);
response.Content.Should().Contain("Sonarr");
ExceptionVerification.ExpectedErrors(0);
}
[Test]
public void should_throw_on_too_many_redirects()
{
var request = new HttpRequest($"http://{_httpBinHost}/redirect/4");
request.AllowAutoRedirect = true;
Assert.Throws<WebException>(() => Subject.Get(request));
ExceptionVerification.ExpectedErrors(0);
}
@ -238,19 +285,96 @@ namespace NzbDrone.Common.Test.Http
response.Resource.Headers.Should().NotContainKey("Cookie");
}
[Test]
public void should_not_store_request_cookie()
{
var requestGet = new HttpRequest($"http://{_httpBinHost}/get");
requestGet.Cookies.Add("my", "cookie");
requestGet.AllowAutoRedirect = false;
requestGet.StoreRequestCookie = false;
requestGet.StoreResponseCookie = false;
var responseGet = Subject.Get<HttpBinResource>(requestGet);
var requestCookies = new HttpRequest($"http://{_httpBinHost}/cookies");
requestCookies.AllowAutoRedirect = false;
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
responseCookies.Resource.Cookies.Should().BeEmpty();
ExceptionVerification.IgnoreErrors();
}
[Test]
public void should_store_request_cookie()
{
var requestGet = new HttpRequest($"http://{_httpBinHost}/get");
requestGet.Cookies.Add("my", "cookie");
requestGet.AllowAutoRedirect = false;
requestGet.StoreRequestCookie.Should().BeTrue();
requestGet.StoreResponseCookie = false;
var responseGet = Subject.Get<HttpBinResource>(requestGet);
var requestCookies = new HttpRequest($"http://{_httpBinHost}/cookies");
requestCookies.AllowAutoRedirect = false;
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
ExceptionVerification.IgnoreErrors();
}
[Test]
public void should_delete_request_cookie()
{
var requestDelete = new HttpRequest($"http://{_httpBinHost}/cookies/delete?my");
requestDelete.Cookies.Add("my", "cookie");
requestDelete.AllowAutoRedirect = true;
requestDelete.StoreRequestCookie = false;
requestDelete.StoreResponseCookie = false;
// Delete and redirect since that's the only way to check the internal temporary cookie container
var responseCookies = Subject.Get<HttpCookieResource>(requestDelete);
responseCookies.Resource.Cookies.Should().BeEmpty();
}
[Test]
public void should_clear_request_cookie()
{
var requestSet = new HttpRequest($"http://{_httpBinHost}/cookies");
requestSet.Cookies.Add("my", "cookie");
requestSet.AllowAutoRedirect = false;
requestSet.StoreRequestCookie = true;
requestSet.StoreResponseCookie = false;
var responseSet = Subject.Get<HttpCookieResource>(requestSet);
var requestClear = new HttpRequest($"http://{_httpBinHost}/cookies");
requestClear.Cookies.Add("my", null);
requestClear.AllowAutoRedirect = false;
requestClear.StoreRequestCookie = true;
requestClear.StoreResponseCookie = false;
var responseClear = Subject.Get<HttpCookieResource>(requestClear);
responseClear.Resource.Cookies.Should().BeEmpty();
}
[Test]
public void should_not_store_response_cookie()
{
var requestSet = new HttpRequest($"http://{_httpBinHost}/cookies/set?my=cookie");
requestSet.AllowAutoRedirect = false;
requestSet.StoreRequestCookie = false;
requestSet.StoreResponseCookie.Should().BeFalse();
var responseSet = Subject.Get(requestSet);
var request = new HttpRequest($"http://{_httpBinHost}/get");
var requestCookies = new HttpRequest($"http://{_httpBinHost}/cookies");
var response = Subject.Get<HttpBinResource>(request);
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
response.Resource.Headers.Should().NotContainKey("Cookie");
responseCookies.Resource.Cookies.Should().BeEmpty();
ExceptionVerification.IgnoreErrors();
}
@ -260,19 +384,31 @@ namespace NzbDrone.Common.Test.Http
{
var requestSet = new HttpRequest($"http://{_httpBinHost}/cookies/set?my=cookie");
requestSet.AllowAutoRedirect = false;
requestSet.StoreRequestCookie = false;
requestSet.StoreResponseCookie = true;
var responseSet = Subject.Get(requestSet);
var request = new HttpRequest($"http://{_httpBinHost}/get");
var requestCookies = new HttpRequest($"http://{_httpBinHost}/cookies");
var response = Subject.Get<HttpBinResource>(request);
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
response.Resource.Headers.Should().ContainKey("Cookie");
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
var cookie = response.Resource.Headers["Cookie"].ToString();
ExceptionVerification.IgnoreErrors();
}
cookie.Should().Contain("my=cookie");
[Test]
public void should_temp_store_response_cookie()
{
var requestSet = new HttpRequest($"http://{_httpBinHost}/cookies/set?my=cookie");
requestSet.AllowAutoRedirect = true;
requestSet.StoreRequestCookie = false;
requestSet.StoreResponseCookie.Should().BeFalse();
var responseSet = Subject.Get<HttpCookieResource>(requestSet);
// Set and redirect since that's the only way to check the internal temporary cookie container
responseSet.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
ExceptionVerification.IgnoreErrors();
}
@ -281,21 +417,129 @@ namespace NzbDrone.Common.Test.Http
public void should_overwrite_response_cookie()
{
var requestSet = new HttpRequest($"http://{_httpBinHost}/cookies/set?my=cookie");
requestSet.Cookies.Add("my", "oldcookie");
requestSet.AllowAutoRedirect = false;
requestSet.StoreRequestCookie = false;
requestSet.StoreResponseCookie = true;
requestSet.Cookies["my"] = "oldcookie";
var responseSet = Subject.Get(requestSet);
var request = new HttpRequest($"http://{_httpBinHost}/get");
var requestCookies = new HttpRequest($"http://{_httpBinHost}/cookies");
var response = Subject.Get<HttpBinResource>(request);
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
response.Resource.Headers.Should().ContainKey("Cookie");
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
var cookie = response.Resource.Headers["Cookie"].ToString();
ExceptionVerification.IgnoreErrors();
}
cookie.Should().Contain("my=cookie");
[Test]
public void should_overwrite_temp_response_cookie()
{
var requestSet = new HttpRequest($"http://{_httpBinHost}/cookies/set?my=cookie");
requestSet.Cookies.Add("my", "oldcookie");
requestSet.AllowAutoRedirect = true;
requestSet.StoreRequestCookie = true;
requestSet.StoreResponseCookie = false;
var responseSet = Subject.Get<HttpCookieResource>(requestSet);
responseSet.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
var requestCookies = new HttpRequest($"http://{_httpBinHost}/cookies");
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "oldcookie");
ExceptionVerification.IgnoreErrors();
}
[Test]
public void should_not_delete_response_cookie()
{
var requestCookies = new HttpRequest($"http://{_httpBinHost}/cookies");
requestCookies.Cookies.Add("my", "cookie");
requestCookies.AllowAutoRedirect = false;
requestCookies.StoreRequestCookie = true;
requestCookies.StoreResponseCookie = false;
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
var requestDelete = new HttpRequest($"http://{_httpBinHost}/cookies/delete?my");
requestDelete.AllowAutoRedirect = false;
requestDelete.StoreRequestCookie = false;
requestDelete.StoreResponseCookie = false;
var responseDelete = Subject.Get(requestDelete);
requestCookies = new HttpRequest($"http://{_httpBinHost}/cookies");
requestCookies.StoreRequestCookie = false;
requestCookies.StoreResponseCookie = false;
responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
ExceptionVerification.IgnoreErrors();
}
[Test]
public void should_delete_response_cookie()
{
var requestCookies = new HttpRequest($"http://{_httpBinHost}/cookies");
requestCookies.Cookies.Add("my", "cookie");
requestCookies.AllowAutoRedirect = false;
requestCookies.StoreRequestCookie = true;
requestCookies.StoreResponseCookie = false;
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
var requestDelete = new HttpRequest($"http://{_httpBinHost}/cookies/delete?my");
requestDelete.AllowAutoRedirect = false;
requestDelete.StoreRequestCookie = false;
requestDelete.StoreResponseCookie = true;
var responseDelete = Subject.Get(requestDelete);
requestCookies = new HttpRequest($"http://{_httpBinHost}/cookies");
requestCookies.StoreRequestCookie = false;
requestCookies.StoreResponseCookie = false;
responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
responseCookies.Resource.Cookies.Should().BeEmpty();
ExceptionVerification.IgnoreErrors();
}
[Test]
public void should_delete_temp_response_cookie()
{
var requestCookies = new HttpRequest($"http://{_httpBinHost}/cookies");
requestCookies.Cookies.Add("my", "cookie");
requestCookies.AllowAutoRedirect = false;
requestCookies.StoreRequestCookie = true;
requestCookies.StoreResponseCookie = false;
var responseCookies = Subject.Get<HttpCookieResource>(requestCookies);
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
var requestDelete = new HttpRequest($"http://{_httpBinHost}/cookies/delete?my");
requestDelete.AllowAutoRedirect = true;
requestDelete.StoreRequestCookie = false;
requestDelete.StoreResponseCookie = false;
var responseDelete = Subject.Get<HttpCookieResource>(requestDelete);
responseDelete.Resource.Cookies.Should().BeEmpty();
requestCookies = new HttpRequest($"http://{_httpBinHost}/cookies");
requestCookies.StoreRequestCookie = false;
requestCookies.StoreResponseCookie = false;
responseCookies.Resource.Cookies.Should().HaveCount(1).And.Contain("my", "cookie");
ExceptionVerification.IgnoreErrors();
}
@ -407,4 +651,9 @@ namespace NzbDrone.Common.Test.Http
public string Url { get; set; }
public string Data { get; set; }
}
}
public class HttpCookieResource
{
public Dictionary<string, string> Cookies { get; set; }
}
}

View file

@ -7,6 +7,13 @@ namespace NzbDrone.Common.Test.Http
{
public class HttpUriFixture : TestBase
{
[TestCase("abc://my_host.com:8080/root/api/")]
public void should_parse(string uri)
{
var newUri = new HttpUri(uri);
newUri.FullUri.Should().Be(uri);
}
[TestCase("", "", "")]
[TestCase("/", "", "/")]
[TestCase("base", "", "base")]
@ -77,7 +84,7 @@ namespace NzbDrone.Common.Test.Http
public void should_combine_relative_path(string basePath, string relativePath, string expected)
{
var newUri = new HttpUri(basePath).CombinePath(relativePath);
newUri.FullUri.Should().Be(expected);
}
}

View file

@ -15,6 +15,7 @@ namespace NzbDrone.Common.Test.InstrumentationTests
[TestCase(@"https://rss.omgwtfnzbs.org/rss-search.php?catid=19,20&user=sonarr&api=mySecret&eng=1")]
[TestCase(@"https://dognzb.cr/fetch/2b51db35e1912ffc138825a12b9933d2/2b51db35e1910123321025a12b9933d2")]
[TestCase(@"https://baconbits.org/feeds.php?feed=torrents_tv&user=12345&auth=2b51db35e1910123321025a12b9933d2&passkey=mySecret&authkey=2b51db35e1910123321025a12b9933d2")]
[TestCase(@"http://127.0.0.1:9117/dl/indexername?jackett_apikey=flwjiefewklfjacketmySecretsdfldskjfsdlk&path=we0re9f0sdfbase64sfdkfjsdlfjk&file=The+Torrent+File+Name.torrent")]
// NzbGet
[TestCase(@"{ ""Name"" : ""ControlUsername"", ""Value"" : ""mySecret"" }, { ""Name"" : ""ControlPassword"", ""Value"" : ""mySecret"" }, ")]
[TestCase(@"{ ""Name"" : ""Server1.Username"", ""Value"" : ""mySecret"" }, { ""Name"" : ""Server1.Password"", ""Value"" : ""mySecret"" }, ")]

View file

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

View file

@ -86,7 +86,7 @@ namespace NzbDrone.Common.Test
{
first.AsOsAgnostic().PathEquals(second.AsOsAgnostic()).Should().BeFalse();
}
[Test]
public void should_return_false_when_not_a_child()
{
@ -113,6 +113,7 @@ namespace NzbDrone.Common.Test
[TestCase(@"C:\Test\", @"C:\Test\mydir")]
[TestCase(@"C:\Test\", @"C:\Test\mydir\")]
[TestCase(@"C:\Test", @"C:\Test\30.Rock.S01E01.Pilot.avi")]
[TestCase(@"C:\", @"C:\Test\30.Rock.S01E01.Pilot.avi")]
public void path_should_be_parent(string parentPath, string childPath)
{
parentPath.AsOsAgnostic().IsParentPath(childPath.AsOsAgnostic()).Should().BeTrue();

View file

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

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.Diagnostics;
using System.IO;
using NzbDrone.Common.EnvironmentInfo;
@ -24,6 +24,8 @@ namespace NzbDrone.Common
Console.WriteLine(" /{0} Install the application as a Windows Service ({1}).", StartupContext.INSTALL_SERVICE, ServiceProvider.NZBDRONE_SERVICE_NAME);
Console.WriteLine(" /{0} Uninstall already installed Windows Service ({1}).", StartupContext.UNINSTALL_SERVICE, ServiceProvider.NZBDRONE_SERVICE_NAME);
Console.WriteLine(" /{0} Don't open Sonarr in a browser", StartupContext.NO_BROWSER);
Console.WriteLine(" /{0} Start Sonarr terminating any other instances", StartupContext.TERMINATE);
Console.WriteLine(" /{0}=path Path to use as the AppData location (stores database, config, logs, etc)", StartupContext.APPDATA);
Console.WriteLine(" <No Arguments> Run application in console mode.");
}
@ -37,4 +39,4 @@ namespace NzbDrone.Common
Console.WriteLine("Can't find service ({0})", ServiceProvider.NZBDRONE_SERVICE_NAME);
}
}
}
}

View file

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

View file

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

View file

@ -1,10 +1,11 @@
using System;
using System;
using System.IO;
using System.Linq;
using System.Threading;
using NLog;
using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Exceptions;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Common.Disk
@ -223,7 +224,7 @@ namespace NzbDrone.Common.Disk
_diskProvider.MoveFile(sourcePath, tempPath, true);
try
{
ClearTargetPath(targetPath, overwrite);
ClearTargetPath(sourcePath, targetPath, overwrite);
_diskProvider.MoveFile(tempPath, targetPath);
@ -253,7 +254,7 @@ namespace NzbDrone.Common.Disk
throw new IOException(string.Format("Destination cannot be a child of the source [{0}] => [{1}]", sourcePath, targetPath));
}
ClearTargetPath(targetPath, overwrite);
ClearTargetPath(sourcePath, targetPath, overwrite);
if (mode.HasFlag(TransferMode.HardLink))
{
@ -330,7 +331,7 @@ namespace NzbDrone.Common.Disk
return TransferMode.None;
}
private void ClearTargetPath(string targetPath, bool overwrite)
private void ClearTargetPath(string sourcePath, string targetPath, bool overwrite)
{
if (_diskProvider.FileExists(targetPath))
{
@ -340,7 +341,7 @@ namespace NzbDrone.Common.Disk
}
else
{
throw new IOException(string.Format("Destination already exists [{0}]", targetPath));
throw new DestinationAlreadyExistsException($"Destination {targetPath} already exists.");
}
}
}
@ -590,7 +591,7 @@ namespace NzbDrone.Common.Disk
private bool ShouldIgnore(FileInfo file)
{
if (file.Name.StartsWith(".nfs"))
if (file.Name.StartsWith(".nfs") || file.Name == "debug.log" || file.Name.EndsWith(".socket"))
{
_logger.Trace("Ignoring file {0}", file.FullName);
return true;

View file

@ -1,4 +1,5 @@
using System.IO;
using System.Collections.Generic;
using System.IO;
using NzbDrone.Common.Extensions;
namespace NzbDrone.Common.Disk
@ -8,10 +9,11 @@ namespace NzbDrone.Common.Disk
private readonly DriveInfo _driveInfo;
private readonly DriveType _driveType;
public DriveInfoMount(DriveInfo driveInfo, DriveType driveType = DriveType.Unknown)
public DriveInfoMount(DriveInfo driveInfo, DriveType driveType = DriveType.Unknown, MountOptions mountOptions = null)
{
_driveInfo = driveInfo;
_driveType = driveType;
MountOptions = mountOptions;
}
public long AvailableFreeSpace => _driveInfo.AvailableFreeSpace;
@ -33,6 +35,8 @@ namespace NzbDrone.Common.Disk
public bool IsReady => _driveInfo.IsReady;
public MountOptions MountOptions { get; private set; }
public string Name => _driveInfo.Name;
public string RootDirectory => _driveInfo.RootDirectory.FullName;
@ -47,7 +51,7 @@ namespace NzbDrone.Common.Disk
{
get
{
if (VolumeLabel.IsNullOrWhiteSpace())
if (VolumeLabel.IsNullOrWhiteSpace() || VolumeLabel.StartsWith("UUID=") || Name == VolumeLabel)
{
return Name;
}

View file

@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.IO;
namespace NzbDrone.Common.Disk
@ -8,6 +9,7 @@ namespace NzbDrone.Common.Disk
string DriveFormat { get; }
DriveType DriveType { get; }
bool IsReady { get; }
MountOptions MountOptions { get; }
string Name { get; }
string RootDirectory { get; }
long TotalFreeSpace { get; }

View file

@ -0,0 +1,16 @@
using System.Collections.Generic;
namespace NzbDrone.Common.Disk
{
public class MountOptions
{
private readonly Dictionary<string, string> _options;
public MountOptions(Dictionary<string, string> options)
{
_options = options;
}
public bool IsReadOnly => _options.ContainsKey("ro");
}
}

View file

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

View file

@ -1,8 +1,9 @@
using System;
using System;
using System.Security.AccessControl;
using System.Security.Principal;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Exceptions;
using NzbDrone.Common.Instrumentation;
namespace NzbDrone.Common.EnvironmentInfo
@ -27,12 +28,25 @@ namespace NzbDrone.Common.EnvironmentInfo
public void Register()
{
_diskProvider.EnsureFolder(_appFolderInfo.AppDataFolder);
try
{
_diskProvider.EnsureFolder(_appFolderInfo.AppDataFolder);
}
catch (UnauthorizedAccessException)
{
throw new SonarrStartupException("Cannot create AppFolder, Access to the path {0} is denied", _appFolderInfo.AppDataFolder);
}
if (OsInfo.IsWindows)
{
SetPermissions();
}
if (!_diskProvider.FolderWritable(_appFolderInfo.AppDataFolder))
{
throw new SonarrStartupException("AppFolder {0} is not writable", _appFolderInfo.AppDataFolder);
}
}
private void SetPermissions()

View file

@ -1,5 +1,3 @@
using System;
namespace NzbDrone.Common.EnvironmentInfo
{
public interface IRuntimeInfo
@ -7,8 +5,9 @@ namespace NzbDrone.Common.EnvironmentInfo
bool IsUserInteractive { get; }
bool IsAdmin { get; }
bool IsWindowsService { get; }
bool IsWindowsTray { get; }
bool IsExiting { get; set; }
bool RestartPending { get; set; }
string ExecutingApplication { get; }
}
}
}

View file

@ -1,10 +1,11 @@
using System;
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Security.Principal;
using System.ServiceProcess;
using NLog;
using NzbDrone.Common.Processes;
namespace NzbDrone.Common.EnvironmentInfo
{
@ -27,6 +28,7 @@ namespace NzbDrone.Common.EnvironmentInfo
if (entry != null)
{
ExecutingApplication = entry.Location;
IsWindowsTray = entry.ManifestModule.Name == $"{ProcessProvider.NZB_DRONE_PROCESS_NAME}.exe";
}
}
@ -102,5 +104,7 @@ namespace NzbDrone.Common.EnvironmentInfo
return true;
}
public bool IsWindowsTray { get; private set; }
}
}

View file

@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Common.Exceptions
{
public class SonarrStartupException : NzbDroneException
{
public SonarrStartupException(string message, params object[] args)
: base("Sonarr failed to start: " + string.Format(message, args))
{
}
public SonarrStartupException(string message)
: base("Sonarr failed to start: " + message)
{
}
public SonarrStartupException()
: base("Sonarr failed to start")
{
}
public SonarrStartupException(Exception innerException, string message, params object[] args)
: base("Sonarr failed to start: " + string.Format(message, args), innerException)
{
}
public SonarrStartupException(Exception innerException, string message)
: base("Sonarr failed to start: " + message, innerException)
{
}
public SonarrStartupException(Exception innerException)
: base("Sonarr failed to start: " + innerException.Message)
{
}
}
}

View file

@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace NzbDrone.Common.Extensions
{
public static class ExceptionExtensions
{
public static T WithData<T>(this T ex, string key, string value) where T : Exception
{
ex.AddData(key, value);
return ex;
}
public static T WithData<T>(this T ex, string key, int value) where T : Exception
{
ex.AddData(key, value.ToString());
return ex;
}
public static T WithData<T>(this T ex, string key, Http.HttpUri value) where T : Exception
{
ex.AddData(key, value.ToString());
return ex;
}
public static T WithData<T>(this T ex, Http.HttpResponse response, int maxSampleLength = 512) where T : Exception
{
if (response == null || response.Content == null) return ex;
var contentSample = response.Content.Substring(0, Math.Min(response.Content.Length, maxSampleLength));
if (response.Request != null)
{
ex.AddData("RequestUri", response.Request.Url.ToString());
if (response.Request.ContentSummary != null)
{
ex.AddData("RequestSummary", response.Request.ContentSummary);
}
}
ex.AddData("StatusCode", response.StatusCode.ToString());
if (response.Headers != null)
{
ex.AddData("ContentType", response.Headers.ContentType ?? string.Empty);
}
ex.AddData("ContentLength", response.Content.Length.ToString());
ex.AddData("ContentSample", contentSample);
return ex;
}
private static void AddData(this Exception ex, string key, string value)
{
if (value.IsNullOrWhiteSpace()) return;
ex.Data[key] = value;
}
}
}

View file

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

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
@ -59,7 +59,7 @@ namespace NzbDrone.Common.Extensions
{
if (!parentPath.IsParentPath(childPath))
{
throw new Exceptions.NotParentException("{0} is not a child of {1}", childPath, parentPath);
throw new NotParentException("{0} is not a child of {1}", childPath, parentPath);
}
return childPath.Substring(parentPath.Length).Trim(Path.DirectorySeparatorChar);
@ -80,11 +80,11 @@ namespace NzbDrone.Common.Extensions
public static bool IsParentPath(this string parentPath, string childPath)
{
if (parentPath != "/")
if (parentPath != "/" && !parentPath.EndsWith(":\\"))
{
parentPath = parentPath.TrimEnd(Path.DirectorySeparatorChar);
}
if (childPath != "/")
if (childPath != "/" && !parentPath.EndsWith(":\\"))
{
childPath = childPath.TrimEnd(Path.DirectorySeparatorChar);
}
@ -276,4 +276,4 @@ namespace NzbDrone.Common.Extensions
return Path.Combine(appFolderInfo.StartUpFolder, NLOG_CONFIG_FILE);
}
}
}
}

View file

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
@ -63,6 +64,11 @@ namespace NzbDrone.Common.Extensions
return text;
}
public static string Join(this IEnumerable<string> values, string separator)
{
return string.Join(separator, values);
}
public static string CleanSpaces(this string text)
{
return CollapseSpace.Replace(text, " ").Trim();
@ -78,6 +84,16 @@ namespace NzbDrone.Common.Extensions
return !string.IsNullOrWhiteSpace(text);
}
public static bool StartsWithIgnoreCase(this string text, string startsWith)
{
return text.StartsWith(startsWith, StringComparison.InvariantCultureIgnoreCase);
}
public static bool EqualsIgnoreCase(this string text, string equals)
{
return text.Equals(equals, StringComparison.InvariantCultureIgnoreCase);
}
public static bool ContainsIgnoreCase(this string text, string contains)
{
return text.IndexOf(contains, StringComparison.InvariantCultureIgnoreCase) > -1;
@ -118,4 +134,4 @@ namespace NzbDrone.Common.Extensions
return Encoding.ASCII.GetString(new [] { byteResult });
}
}
}
}

View file

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

View file

@ -11,6 +11,11 @@ namespace NzbDrone.Common.Extensions
return false;
}
if (path.StartsWith(" ") || path.EndsWith(" "))
{
return false;
}
Uri uri;
if (!Uri.TryCreate(path, UriKind.Absolute, out uri))
{

View file

@ -38,7 +38,7 @@ namespace NzbDrone.Common.Http.Dispatchers
_caBundleFilePath = _caBundleFileName;
}
}
public CurlHttpDispatcher(IHttpProxySettingsProvider proxySettingsProvider, IUserAgentBuilder userAgentBuilder, Logger logger)
{
_proxySettingsProvider = proxySettingsProvider;
@ -107,7 +107,7 @@ namespace NzbDrone.Common.Http.Dispatchers
throw new NotSupportedException($"HttpCurl method {request.Method} not supported");
}
curlEasy.UserAgent = _userAgentBuilder.GetUserAgent(request.UseSimplifiedUserAgent);
curlEasy.FollowLocation = request.AllowAutoRedirect;
curlEasy.FollowLocation = false;
if (request.RequestTimeout != TimeSpan.Zero)
{

View file

@ -34,18 +34,11 @@ namespace NzbDrone.Common.Http.Dispatchers
{
return _managedDispatcher.GetResponse(request, cookies);
}
catch (Exception ex)
catch (TlsFailureException)
{
if (ex.ToString().Contains("The authentication or decryption has failed."))
{
_logger.Debug("https request failed in tls error for {0}, trying curl fallback.", request.Url.Host);
_logger.Debug("https request failed in tls error for {0}, trying curl fallback.", request.Url.Host);
_curlTLSFallbackCache.Set(request.Url.Host, true);
}
else
{
throw;
}
_curlTLSFallbackCache.Set(request.Url.Host, true);
}
}

View file

@ -32,7 +32,7 @@ namespace NzbDrone.Common.Http.Dispatchers
webRequest.Method = request.Method.ToString();
webRequest.UserAgent = _userAgentBuilder.GetUserAgent(request.UseSimplifiedUserAgent);
webRequest.KeepAlive = request.ConnectionKeepAlive;
webRequest.AllowAutoRedirect = request.AllowAutoRedirect;
webRequest.AllowAutoRedirect = false;
webRequest.CookieContainer = cookies;
if (request.RequestTimeout != TimeSpan.Zero)
@ -47,19 +47,19 @@ namespace NzbDrone.Common.Http.Dispatchers
AddRequestHeaders(webRequest, request.Headers);
}
if (request.ContentData != null)
{
webRequest.ContentLength = request.ContentData.Length;
using (var writeStream = webRequest.GetRequestStream())
{
writeStream.Write(request.ContentData, 0, request.ContentData.Length);
}
}
HttpWebResponse httpWebResponse;
try
{
if (request.ContentData != null)
{
webRequest.ContentLength = request.ContentData.Length;
using (var writeStream = webRequest.GetRequestStream())
{
writeStream.Write(request.ContentData, 0, request.ContentData.Length);
}
}
httpWebResponse = (HttpWebResponse)webRequest.GetResponse();
}
catch (WebException e)
@ -73,7 +73,27 @@ namespace NzbDrone.Common.Http.Dispatchers
if (httpWebResponse == null)
{
throw;
// The default messages for WebException on mono are pretty horrible.
if (e.Status == WebExceptionStatus.NameResolutionFailure)
{
throw new WebException($"DNS Name Resolution Failure: '{webRequest.RequestUri.Host}'", e.Status);
}
else if (e.ToString().Contains("TLS Support not"))
{
throw new TlsFailureException(webRequest, e);
}
else if (e.ToString().Contains("The authentication or decryption has failed."))
{
throw new TlsFailureException(webRequest, e);
}
else if (OsInfo.IsNotWindows)
{
throw new WebException($"{e.Message}: '{webRequest.RequestUri}'", e, e.Status, e.Response);
}
else
{
throw;
}
}
}
@ -83,7 +103,14 @@ namespace NzbDrone.Common.Http.Dispatchers
{
if (responseStream != null)
{
data = responseStream.ToBytes();
try
{
data = responseStream.ToBytes();
}
catch (Exception ex)
{
throw new WebException("Failed to read complete http response", ex, WebExceptionStatus.ReceiveFailure, httpWebResponse);
}
}
}

View file

@ -7,6 +7,7 @@ using System.Net;
using NLog;
using NzbDrone.Common.Cache;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Common.Http.Dispatchers;
using NzbDrone.Common.TPL;
@ -51,46 +52,35 @@ namespace NzbDrone.Common.Http
public HttpResponse Execute(HttpRequest request)
{
foreach (var interceptor in _requestInterceptors)
var cookieContainer = InitializeRequestCookies(request);
var response = ExecuteRequest(request, cookieContainer);
if (request.AllowAutoRedirect && response.HasHttpRedirect)
{
request = interceptor.PreRequest(request);
var autoRedirectChain = new List<string>();
autoRedirectChain.Add(request.Url.ToString());
do
{
request.Url += new HttpUri(response.Headers.GetSingleValue("Location"));
autoRedirectChain.Add(request.Url.ToString());
_logger.Trace("Redirected to {0}", request.Url);
if (autoRedirectChain.Count > 3)
{
throw new WebException($"Too many automatic redirections were attempted for {autoRedirectChain.Join(" -> ")}", WebExceptionStatus.ProtocolError);
}
response = ExecuteRequest(request, cookieContainer);
}
while (response.HasHttpRedirect);
}
if (request.RateLimit != TimeSpan.Zero)
if (response.HasHttpRedirect && !RuntimeInfo.IsProduction)
{
_rateLimitService.WaitAndPulse(request.Url.Host, request.RateLimit);
}
_logger.Trace(request);
var stopWatch = Stopwatch.StartNew();
var cookies = PrepareRequestCookies(request);
var response = _httpDispatcher.GetResponse(request, cookies);
HandleResponseCookies(request, cookies);
stopWatch.Stop();
_logger.Trace("{0} ({1} ms)", response, stopWatch.ElapsedMilliseconds);
foreach (var interceptor in _requestInterceptors)
{
response = interceptor.PostResponse(response);
}
if (request.LogResponseContent)
{
_logger.Trace("Response content ({0} bytes): {1}", response.ResponseData.Length, response.Content);
}
if (!RuntimeInfo.IsProduction &&
(response.StatusCode == HttpStatusCode.Moved ||
response.StatusCode == HttpStatusCode.MovedPermanently ||
response.StatusCode == HttpStatusCode.Found))
{
_logger.Error("Server requested a redirect to [{0}]. Update the request URL to avoid this redirect.", response.Headers["Location"]);
_logger.Error("Server requested a redirect to [{0}] while in developer mode. Update the request URL to avoid this redirect.", response.Headers["Location"]);
}
if (!request.SuppressHttpError && response.HasHttpError)
@ -110,49 +100,130 @@ namespace NzbDrone.Common.Http
return response;
}
private CookieContainer PrepareRequestCookies(HttpRequest request)
private HttpResponse ExecuteRequest(HttpRequest request, CookieContainer cookieContainer)
{
foreach (var interceptor in _requestInterceptors)
{
request = interceptor.PreRequest(request);
}
if (request.RateLimit != TimeSpan.Zero)
{
_rateLimitService.WaitAndPulse(request.Url.Host, request.RateLimit);
}
_logger.Trace(request);
var stopWatch = Stopwatch.StartNew();
PrepareRequestCookies(request, cookieContainer);
var response = _httpDispatcher.GetResponse(request, cookieContainer);
HandleResponseCookies(response, cookieContainer);
stopWatch.Stop();
_logger.Trace("{0} ({1} ms)", response, stopWatch.ElapsedMilliseconds);
foreach (var interceptor in _requestInterceptors)
{
response = interceptor.PostResponse(response);
}
if (request.LogResponseContent)
{
_logger.Trace("Response content ({0} bytes): {1}", response.ResponseData.Length, response.Content);
}
return response;
}
private CookieContainer InitializeRequestCookies(HttpRequest request)
{
lock (_cookieContainerCache)
{
var persistentCookieContainer = _cookieContainerCache.Get("container", () => new CookieContainer());
var sourceContainer = new CookieContainer();
var presistentContainer = _cookieContainerCache.Get("container", () => new CookieContainer());
var persistentCookies = presistentContainer.GetCookies((Uri)request.Url);
sourceContainer.Add(persistentCookies);
if (request.Cookies.Count != 0)
{
foreach (var pair in request.Cookies)
{
persistentCookieContainer.Add(new Cookie(pair.Key, pair.Value, "/", request.Url.Host)
Cookie cookie;
if (pair.Value == null)
{
// Use Now rather than UtcNow to work around Mono cookie expiry bug.
// See https://gist.github.com/ta264/7822b1424f72e5b4c961
Expires = DateTime.Now.AddHours(1)
});
cookie = new Cookie(pair.Key, "", "/")
{
Expires = DateTime.Now.AddDays(-1)
};
}
else
{
cookie = new Cookie(pair.Key, pair.Value, "/")
{
// Use Now rather than UtcNow to work around Mono cookie expiry bug.
// See https://gist.github.com/ta264/7822b1424f72e5b4c961
Expires = DateTime.Now.AddHours(1)
};
}
sourceContainer.Add((Uri)request.Url, cookie);
if (request.StoreRequestCookie)
{
presistentContainer.Add((Uri)request.Url, cookie);
}
}
}
var requestCookies = persistentCookieContainer.GetCookies((Uri)request.Url);
var cookieContainer = new CookieContainer();
cookieContainer.Add(requestCookies);
return cookieContainer;
return sourceContainer;
}
}
private void HandleResponseCookies(HttpRequest request, CookieContainer cookieContainer)
private void PrepareRequestCookies(HttpRequest request, CookieContainer cookieContainer)
{
if (!request.StoreResponseCookie)
// Don't collect persistnet cookies for intermediate/redirected urls.
/*lock (_cookieContainerCache)
{
var presistentContainer = _cookieContainerCache.Get("container", () => new CookieContainer());
var persistentCookies = presistentContainer.GetCookies((Uri)request.Url);
var existingCookies = cookieContainer.GetCookies((Uri)request.Url);
cookieContainer.Add(persistentCookies);
cookieContainer.Add(existingCookies);
}*/
}
private void HandleResponseCookies(HttpResponse response, CookieContainer cookieContainer)
{
var cookieHeaders = response.GetCookieHeaders();
if (cookieHeaders.Empty())
{
return;
}
lock (_cookieContainerCache)
if (response.Request.StoreResponseCookie)
{
var persistentCookieContainer = _cookieContainerCache.Get("container", () => new CookieContainer());
lock (_cookieContainerCache)
{
var persistentCookieContainer = _cookieContainerCache.Get("container", () => new CookieContainer());
var cookies = cookieContainer.GetCookies((Uri)request.Url);
persistentCookieContainer.Add(cookies);
foreach (var cookieHeader in cookieHeaders)
{
try
{
persistentCookieContainer.SetCookies((Uri)response.Request.Url, cookieHeader);
}
catch (Exception ex)
{
_logger.Debug(ex, "Invalid cookie in {0}", response.Request.Url);
}
}
}
}
}
@ -217,4 +288,4 @@ namespace NzbDrone.Common.Http
return new HttpResponse<T>(response);
}
}
}
}

View file

@ -37,7 +37,7 @@ namespace NzbDrone.Common.Http
}
if (values.Length > 1)
{
throw new ApplicationException(string.Format("Expected {0} to occur only once.", key));
throw new ApplicationException($"Expected {key} to occur only once, but was {values.Join("|")}.");
}
return values[0];
@ -54,7 +54,7 @@ namespace NzbDrone.Common.Http
return converter(value);
}
protected void SetSingleValue(string key, string value)
{
{
if (value == null)
{
Remove(key);
@ -175,4 +175,4 @@ namespace NzbDrone.Common.Http
.ToList();
}
}
}
}

View file

@ -1,13 +1,14 @@
namespace NzbDrone.Common.Http
namespace NzbDrone.Common.Http
{
public enum HttpMethod
{
GET,
PUT,
POST,
HEAD,
PUT,
DELETE,
HEAD,
OPTIONS,
PATCH,
OPTIONS
MERGE
}
}

View file

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

View file

@ -35,7 +35,7 @@ namespace NzbDrone.Common.Http
private string _content;
public string Content
public string Content
{
get
{
@ -51,20 +51,26 @@ namespace NzbDrone.Common.Http
public bool HasHttpError => (int)StatusCode >= 400;
public bool HasHttpRedirect => StatusCode == HttpStatusCode.Moved ||
StatusCode == HttpStatusCode.MovedPermanently ||
StatusCode == HttpStatusCode.Found;
public string[] GetCookieHeaders()
{
return Headers.GetValues("Set-Cookie") ?? new string[0];
}
public Dictionary<string, string> GetCookies()
{
var result = new Dictionary<string, string>();
var setCookieHeaders = Headers.GetValues("Set-Cookie");
if (setCookieHeaders != null)
var setCookieHeaders = GetCookieHeaders();
foreach (var cookie in setCookieHeaders)
{
foreach (var cookie in setCookieHeaders)
var match = RegexSetCookie.Match(cookie);
if (match.Success)
{
var match = RegexSetCookie.Match(cookie);
if (match.Success)
{
result[match.Groups[1].Value] = match.Groups[2].Value;
}
result[match.Groups[1].Value] = match.Groups[2].Value;
}
}
@ -95,4 +101,4 @@ namespace NzbDrone.Common.Http
public T Resource { get; private set; }
}
}
}

View file

@ -8,7 +8,7 @@ namespace NzbDrone.Common.Http
{
public class HttpUri : IEquatable<HttpUri>
{
private static readonly Regex RegexUri = new Regex(@"^(?:(?<scheme>[a-z]+):)?(?://(?<host>[-A-Z0-9.]+)(?::(?<port>[0-9]{1,5}))?)?(?<path>(?:(?:(?<=^)|/)[^/?#\r\n]+)+/?|/)?(?:\?(?<query>[^#\r\n]*))?(?:\#(?<fragment>.*))?$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex RegexUri = new Regex(@"^(?:(?<scheme>[a-z]+):)?(?://(?<host>[-_A-Z0-9.]+)(?::(?<port>[0-9]{1,5}))?)?(?<path>(?:(?:(?<=^)|/)[^/?#\r\n]+)+/?|/)?(?:\?(?<query>[^#\r\n]*))?(?:\#(?<fragment>.*))?$", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private readonly string _uri;
public string FullUri => _uri;
@ -168,7 +168,7 @@ namespace NzbDrone.Common.Http
{
return basePath.Substring(0, baseSlashIndex) + "/" + relativePath;
}
return relativePath;
}
@ -263,7 +263,7 @@ namespace NzbDrone.Common.Http
{
return new HttpUri(baseUrl.Scheme, baseUrl.Host, baseUrl.Port, CombineRelativePath(baseUrl.Path, relativeUrl.Path), relativeUrl.Query, relativeUrl.Fragment);
}
return new HttpUri(baseUrl.Scheme, baseUrl.Host, baseUrl.Port, baseUrl.Path, relativeUrl.Query, relativeUrl.Fragment);
}
}

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