1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-03-06 13:31:28 -05:00

Compare commits

...

257 Commits

Author SHA1 Message Date
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
Mark McDowall
83370ddbbb New: Episode files sent to Recycling Bin are put into subfolders
Closes #401
2017-03-29 06:44:50 -07:00
Mark McDowall
c20b152c28 Fixed spelling in message 2017-03-26 13:21:29 -07:00
Mark McDowall
bf5067466d Guard against a null file showing an exception in release rejections
Fixes #1755
2017-03-26 13:01:59 -07:00
Taloth Saldono
ec7f749541 Tweaked default config for extra files import. 2017-03-26 21:22:58 +02:00
Taloth Saldono
56ecbf4a31 Fixed: Sabnzbd error when tv sorting enabled for all categories. 2017-03-26 17:09:22 +02:00
Mark McDowall
1b39911135 True/False for config settings value 2017-03-25 22:18:57 -07:00
Mark McDowall
6aaefae2d5 New: Explicit toggle for importing extra files 2017-03-25 09:13:28 -07:00
margaale
db9d601115 Revert Session name 2017-03-23 13:46:01 -03:00
Taloth Saldono
e7331539f0 Fixed: Newznab default capabilities erroneously cached if indexer is unavailable. 2017-03-23 17:12:10 +01:00
Taloth Saldono
58bd57bed6 New: Updated MediaInfo to 0.7.93. 2017-03-22 19:17:55 +01:00
Mark McDowall
7a58082cd7 smallicon for Join notifications
New: White icon with transparent background for Join notifications notification bar icon
Closes #1458
2017-03-19 23:31:50 -07:00
Taloth Saldono
2e08f195e4 Fixed: Zero length file causes MediaInfo hanging in 100% cpu load. 2017-03-19 22:02:52 +01:00
Taloth Saldono
a1a5e29c3e fixed sab tests. 2017-03-19 19:00:05 +01:00
margaale
5033886b90 Fixed: DownloadStation api client for DSM 5.x. 2017-03-19 18:50:56 +01:00
Mark McDowall
29419d6575 Update README.md 2017-03-18 23:34:37 -07:00
Mark McDowall
3c22f68f5a Fixed: Parsing releases with year added to the end of the series title
Fixes #1768
2017-03-18 22:45:47 -07:00
Mark McDowall
a0d98951aa Use MaterialisingResponse for static resource responses 2017-03-18 12:22:44 -07:00
Taloth Saldono
70f7404499 Fixed: Sabnzbd 2.0 api compatibility.
closes #1775
2017-03-18 16:32:13 +01:00
Mark McDowall
abd70f5381 New: UHD category for RARBG 2017-03-17 07:16:24 -07:00
Mark McDowall
878e973081 Fixed: Join grab messages
Fixes #1751
2017-03-13 19:43:07 -07:00
Taloth Saldono
2bf3b9e7dd fixed typo setting custom directory for rtorrent. 2017-03-12 11:18:51 +01:00
Taloth Saldono
2326db0dea Fixed: Refactored rtorrent interface to fix reliability issues with adding magnets & torrents.
fixes #1745
2017-03-11 12:15:42 +01:00
Taloth Saldono
3590fedeaf Fixed: Timing issue in rtorrent handling of magnet links.
ref #1745
2017-03-10 21:07:08 +01:00
Taloth Saldono
f4866cae69 fixed broken project file. 2017-03-10 20:43:16 +01:00
Mark McDowall
149d191f62 Remove NCrunch.Framework 2017-03-09 20:30:39 -08:00
Jamie Magee
bb9bd63382 Upgrade CommonServiceLocator
From 1.0 to 1.3
2017-03-09 20:30:39 -08:00
Jamie Magee
34fda24124 Upgrade Microsoft.AspNet.SignalR.Client
From 1.2.1 to 1.2.2
2017-03-09 20:30:39 -08:00
Jamie Magee
c8d10829a0 Upgrade Selenium.*
From 3.0.1 to 3.2.0
2017-03-09 19:49:05 -08:00
Jamie Magee
ae2bdb757a Upgrade NUnit
From 3.5.0 to 3.6.0
2017-03-09 19:49:02 -08:00
Jamie Magee
714ad075fc Upgrade FluentAssertions
From 4.18.0 to 4.19.0
2017-03-09 19:48:11 -08:00
Jamie Magee
87a05df2fd Upgrade TinyTwitter
From 1.1.1 to 1.1.2

NOTE: Sonarr was already using a modified version of TinyTwitter 1.1.2.
This change just modifies the packages.config file to reflect that
2017-03-09 19:48:11 -08:00
Jamie Magee
f3263efa52 Upgrade SharpRaven
From 2.1.0 to 2.2.0
2017-03-09 19:48:11 -08:00
Jamie Magee
1cad11d207 Upgrade Ical.Net
From 2.2.25 to 2.2.32
2017-03-09 19:48:10 -08:00
Jamie Magee
781df8b20a Upgrade NLog
From 4.4.1 to 4.4.3
2017-03-09 19:48:10 -08:00
Mark McDowall
ebcce05588 Fixed: Parsing headers that have a trailing semi-colon
Fixes #1749
2017-03-09 15:40:13 -08:00
Taloth Saldono
bbf2134fe1 Fixed: Deluge 1.3.14 API support due to changed json-rpc checks.
fixes #1738
2017-03-06 20:14:34 +01:00
Mark McDowall
081c5fc332 Broken ExtraFiles migration due to extentionless files
Fixed: Prevent extensionless files from being imported
Fixed: Broken migration due to extensionless extra files
2017-03-06 11:00:38 -08:00
Mark McDowall
47915d5e05 Fixed: Bad extension when importing extra files 2017-03-05 17:45:35 -08:00
Mark McDowall
47e221d9a0 Fixed: Delay profiles are no longer hidden under advanced settings 2017-03-03 21:16:29 -08:00
Mark McDowall
bf485f6f2c Log number of files found when getting video/non-video files 2017-03-03 20:57:05 -08:00
Mark McDowall
b365d8a537 Include language in suffix when importing 2017-03-03 19:44:31 -08:00
Taloth Saldono
fee8da88a6 Accept full language name as suffix. 2017-03-03 19:44:31 -08:00
Mark McDowall
cc0dbf1af4 New: Rename subtitles and extra files when renaming files
Towards #459
2017-03-03 19:44:31 -08:00
Mark McDowall
836131ebb1 New: Import subtitles and extra files when importing media files 2017-03-03 19:44:31 -08:00
Marcelo Castagna
9a870a3709 Fixed: DownloadStation interface stuck in infinite loop in some cases.
* removed empty spaces. changed dcaex => ex

* Changed error message

* changed error message

* Wrong message, ups

* Another message
2017-03-01 18:46:16 +01:00
Taloth Saldono
afe05189da Fixed series scan tests. 2017-02-28 21:06:41 +01:00
Taloth Saldono
2abaef16f1 Fixed Indexer Health Checks and tests. 2017-02-28 20:59:22 +01:00
Daniel Smith
37d5a3f2ad Fixed: Clear EpisodeFile records from database if Series folder is missing, but root folder appears to be mounted. 2017-02-28 17:01:12 +01:00
Mark McDowall
be4d70e3a9 Fixed: Health check failing and preventing others from running 2017-02-28 00:12:34 -08:00
Mark McDowall
79043f2c64 Improve indexer health check messages
Fixed: Improve health check message when all enabled indexers are disabled due to failures
Closes #1551
2017-02-28 00:12:34 -08:00
Mark McDowall
1dab0aee6a Fixed: Reduce parameters required to add a new series
Fixes #1403
2017-02-27 21:37:33 -08:00
Mark McDowall
9b162f2d5e Fixed: Clean RSS feed before detecting type
Fixes #1518
2017-02-27 21:37:00 -08:00
Mark McDowall
5518cf5362 Added Download decision comparator test to confirm quality is preferred over seeders 2017-02-25 16:18:00 -08:00
Taloth Saldono
f7e3d9b4c2 Fixed: DownloadStation regression in queue detection. 2017-02-23 08:58:50 +01:00
Taloth Saldono
6d9a952bd1 Fixed: DownloadStation proxy failing if non-bt/nzb downloads exist. 2017-02-22 19:10:39 +01:00
margaale
3501e33722 turn task type enum into string 2017-02-22 14:10:12 -03:00
margaale
fa89d33900 Fix for key not found, returning a generic error instead 2017-02-22 14:10:12 -03:00
Mark McDowall
0af48fb2e8 Fixed: NZBGet delete:scan treated as failure
Fixes #1394
2017-02-22 00:31:51 -08:00
Mark McDowall
7e9f0d0522 Updated analytics help text 2017-02-21 11:18:29 -08:00
Taloth Saldono
1f8bd8e1e9 Fixed typo in DL station hint text. 2017-02-21 18:19:55 +01:00
Taloth Saldono
2855090005 Fixed: Removed Womble indexer. 2017-02-21 17:03:10 +01:00
Taloth Saldono
060b9f6fd1 Fixed: Updated BTN api url. 2017-02-21 16:40:20 +01:00
margaale
9304547c95 Test if the OutputPath specified by TvDirectory/TvCategory exists. 2017-02-21 16:40:16 +01:00
margaale
c56c83e169 New: Added support for nzb downloads in Synology Download Station. 2017-02-20 18:57:11 +01:00
Mark McDowall
c6fa883662 Fixed: Saving nyaa settings
Fixes #1687
2017-02-16 09:19:28 -08:00
Mark McDowall
4043d07ab1 Verify LimeTorrents parsing 2017-02-15 22:30:03 -08:00
Mark McDowall
8af3348e7f Fixed: Slow loading root folders caused them to never appear 2017-02-15 22:30:03 -08:00
Taloth Saldono
49d0d4c357 Renamed DownloadStation implementation to TorrentDownloadStation. 2017-02-15 21:32:25 +01:00
Taloth Saldono
47b1157b96 Fixed: Permanently removed kickass rss/api implementation. 2017-02-15 21:32:21 +01:00
Taloth Saldono
adc79f0eba Added more sensible error for BTN html response. 2017-02-15 20:40:32 +01:00
Taloth Saldono
6b117427f8 Fixed double question mark in log. 2017-02-15 20:40:32 +01:00
Mark McDowall
7884dd9a39 New: Added omgwtfnzbs Newznab prefix 2017-02-13 22:46:26 -08:00
Marcelo Castagna
45d8b1e2ad Fixed: Delete data when removing torrent from Download Station
fixes #1676
2017-02-13 20:17:52 +01:00
Marcelo Castagna
cf306f4aba Throw exception with error message return by diskstation (#1672) 2017-02-12 20:20:16 +01:00
Mark McDowall
d7aa23388e New: Update Media info for Windows/macOS to 0.7.92.1 2017-02-11 16:29:49 -08:00
margaale
82a99b7f80 New: Added support for Synology Download Station as torrent client. 2017-02-11 21:06:23 +01:00
Taloth Saldono
2f6d9e191e Fixed: Ignore .nfs* files during copy actions since those files are special NFS files that should never be touched.
fixes #1552
2017-02-09 19:33:28 +01:00
Taloth Saldono
0782a15979 Remove backslashes from BTN release titles.
fixes #1075
2017-02-09 19:33:28 +01:00
vertigo235
ddd119a4eb New: Add paused option for NZBGet
Closes #346
2017-02-08 20:36:39 -08:00
Taloth Saldono
d4788b4cae Added tests for edge-case.
closes #1147
2017-02-08 22:10:30 +01:00
Taloth Saldono
812999423b Fixed: Don't try to show diskspace usage non-existing drives.
fixes #1639
2017-02-07 23:06:14 +01:00
Taloth Saldono
657730f4d2 Fixed: /var/lib/docker no longer shows up in DiskSpace. Caused warnings if the user used docker with zfs storage driver.
fixes #1663
2017-02-07 22:44:31 +01:00
Taloth Saldono
0255eb3aca Fixed: Increased timeout when waiting for rtorrent to finish adding torrent.
fixes #1665
2017-02-07 22:36:47 +01:00
Mark McDowall
fc15daa37e New: Improve parsing of audio channels from MediaInfo output 2017-02-04 22:04:12 -08:00
Mark McDowall
10264a5bfb New: Ensure folders are sorted alphabetically when importing
Closes #294
2017-02-04 22:04:12 -08:00
Mark McDowall
ef044f1ff5 Update README.md 2017-01-27 20:56:07 -08:00
Mark McDowall
ef03e9e9a7 Fixed: Proper port validation for download clients and connections
Closes #1642
2017-01-26 22:35:16 -08:00
Mark McDowall
3bd7c09acf Strip 2160p from titles before parsing 2017-01-23 23:53:15 -08:00
Keivan Beigi
fbd2f8dea4 Fixed: Growl download notification title 2017-01-22 13:07:21 -08:00
Keivan Beigi
15e07f72d4 Better Runtime names 2017-01-20 20:54:04 -08:00
Keivan Beigi
f25bfe9d28 don't log migrations during regular DB tests 2017-01-20 20:33:10 -08:00
Keivan Beigi
d5e720c404 include os name, runtime name in version tag for sentry 2017-01-20 20:16:34 -08:00
Keivan Beigi
c9a8ebc2e6 Create anonymous hash to detect issue duplication 2017-01-20 20:15:49 -08:00
Mark McDowall
5e7e816c03 AsOsAgnostic paths for root folder tests 2017-01-20 09:02:36 -08:00
vertigo235
f56076a135 Fixed: Pushover silent priority 2017-01-19 23:51:58 -08:00
Mark McDowall
54dd527f01 Exclude .grab and Plex Version folders 2017-01-19 01:38:37 -08:00
Mark McDowall
c6eb19c04d Exclude .grab and Plex Version folders
New: Ignore .grab folder (Plex DVR)
New: Ignore Plex Versions folder (Media Optimizer)
Closes #1610
2017-01-18 20:15:32 -08:00
Mitchell Cash
38b65ba27d Cleanup README (#1622) 2017-01-18 12:31:20 -08:00
Keivan Beigi
a2a49ce934 Revert "New: Upgraded SQLite binares for macOS"
This reverts commit 8d91f18823.
2017-01-18 10:04:36 -08:00
Keivan Beigi
047d5a4388 Revert "New: Upgraded SQLite binaries for Windows (3.16.0)"
This reverts commit 111e401a2c.
2017-01-18 10:04:26 -08:00
Keivan Beigi
aae69ff49a Revert "Upgraded System.Data.SQLite to 1.0.104.0"
This reverts commit 01e2f4e7e5.
2017-01-18 10:04:07 -08:00
Sander Ploegsma
da451cfe03 Option to convert ical feed items to all-day events 2017-01-17 22:36:48 +01:00
Keivan Beigi
01e2f4e7e5 Upgraded System.Data.SQLite to 1.0.104.0 2017-01-17 11:47:46 -08:00
Keivan Beigi
8aacc61c50 New: Switched nyaa.se to HTTPS 2017-01-17 11:47:46 -08:00
Keivan Beigi
111e401a2c New: Upgraded SQLite binaries for Windows (3.16.0) 2017-01-17 11:47:45 -08:00
Keivan
8d91f18823 New: Upgraded SQLite binares for macOS
Upgraded from 3.8.1 to 3.9.1
2017-01-17 11:47:45 -08:00
Keivan Beigi
cea6469ab8 Use nameof 2017-01-17 11:47:06 -08:00
Mark McDowall
ced7a7dce2 New: Prefer anime batch releases over single episode releases 2017-01-14 12:28:22 -08:00
Mitchell Cash
20a2cfe260 Use DOGnzb name as the default rather than the URL 2017-01-14 08:45:29 -08:00
Drew Freyling
5b0a285b84 New: Reduced image file sizes 2017-01-12 13:10:19 -08:00
Mark McDowall
68ea8a551c Fixed: Parsing of SABnzbd develop version 2017-01-12 00:38:56 -08:00
Keivan Beigi
2e36d35815 Added app.manifest to indicate proper windows 10 support 2017-01-06 16:02:57 -08:00
Keivan Beigi
ed2e4d0f1d Sentry will now back-off if it's API key is revoked. 2017-01-06 11:55:54 -08:00
Keivan Beigi
0bdc137093 Smaller sentry payload, send machine name as user name 2017-01-05 17:43:24 -08:00
Keivan Beigi
cd7e208efa Revert "Use line number instead of message for sentry fingerprint"
This reverts commit 5f339c0a92e3255890fb63dfa3e650852a81d60f.

# Conflicts:
#	src/NzbDrone.Common/Instrumentation/Sentry/SentryTarget.cs
2017-01-05 17:43:24 -08:00
Keivan Beigi
73840dcacc The great logger.Error cleanup! 2017-01-05 17:43:24 -08:00
Keivan Beigi
e45d4f60a4 Moved Fatal logging to use nlog patterns 2017-01-05 17:43:24 -08:00
Keivan Beigi
782efcfaf1 Added a one hour debounce of reporting the same errors to sentry 2017-01-05 17:43:24 -08:00
Keivan Beigi
76a7d4f866 Use line number instead of message for sentry fingerprint 2017-01-05 17:43:24 -08:00
Keivan Beigi
d61976251e Apparently RemoveTarget doesn't do what you expect it to do. 2017-01-05 10:46:16 -08:00
Keivan Beigi
2487e8ed49 Don't report errors that don't have exceptions 2017-01-05 10:25:50 -08:00
Keivan Beigi
54bc642476 Fixed: OS Version detection shouldn't break user agents. Fixes #1611 2017-01-05 10:25:05 -08:00
Keivan Beigi
6577b0a721 Don't include null in sentry fingerprint if even has no exception 2017-01-04 23:28:51 -08:00
Keivan Beigi
bcd67dee5e Added fingerprint to sentry events to allow better grouping 2017-01-04 20:01:43 -08:00
Keivan Beigi
e6705db743 Added platform version error handling 2017-01-04 19:15:47 -08:00
Keivan Beigi
dd7fdd8ace Disable Sentry Reporting based on analytics flag 2017-01-04 17:56:29 -08:00
Keivan Beigi
a1f112e62f Added branch name to Assembly Info 2017-01-04 14:20:13 -08:00
Keivan Beigi
053c730799 Added Sentry error reporting 2017-01-04 14:19:53 -08:00
Keivan Beigi
61579cfb7e Consider /buildAgent/ to be a none production startup path 2017-01-04 14:19:50 -08:00
Keivan Beigi
92d706a10d Updated bindingRedirect for Newtonsoft.Json 2017-01-04 14:18:38 -08:00
Keivan Beigi
6ae5829439 Removed duplicated code from ContainerBuilders 2017-01-04 14:18:37 -08:00
Keivan Beigi
8252a2a60f RIP 2017-01-04 00:19:32 -08:00
Keivan Beigi
009dc14805 Added Windows Server detection logic 2017-01-03 18:56:28 -08:00
Keivan Beigi
e0ff25d5be New: switched nyaa to HTTPS 2017-01-03 18:37:46 -08:00
Keivan Beigi
ad7d571b24 New: Better platform detection specifically for Non-Windows Systems 2017-01-03 18:37:23 -08:00
Keivan Beigi
598b5322b7 Fixed linux build 2017-01-03 18:31:48 -08:00
Keivan Beigi
76505bdaa1 Added Rider dir to git ignore. 2017-01-03 18:30:21 -08:00
Keivan Beigi
d64d35361c Revert "Upgraded Moq" (Mono compatibility)
This reverts commit 3140d5d4b0.
2017-01-03 10:25:01 -08:00
Mark McDowall
d5ef451bb4 Fixed: Parsing of 2017x123 episode format 2017-01-02 11:46:25 -08:00
Keivan Beigi
3140d5d4b0 Upgraded Moq 2016-12-28 22:41:48 -08:00
Keivan Beigi
fdb5ccdae1 Updated Newtonsoft.Json from 6.0.6 to 9.0.1 2016-12-28 22:41:47 -08:00
Keivan Beigi
a2ce435239 Upgraded Nlog, NCrunch 2016-12-28 22:41:46 -08:00
Keivan Beigi
a34e69b35b Notification API Cleanup 2016-12-27 23:52:20 -08:00
724 changed files with 15045 additions and 7617 deletions

2
.gitignore vendored
View File

@@ -133,3 +133,5 @@ output/*
_start
_temp_*/**/*
src/.idea/

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 707 B

After

Width:  |  Height:  |  Size: 490 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 34 KiB

BIN
Logo/96-Outline-White.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

52
README.md Normal file
View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,6 @@ using System.Collections.Generic;
using System.Collections.Specialized;
using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Owin.Infrastructure;
namespace Microsoft.AspNet.SignalR.Owin

View File

@@ -42,17 +42,17 @@
<HintPath>..\packages\NBuilder.4.0.0\lib\net40\FizzWare.NBuilder.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="FluentAssertions, Version=4.18.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
<HintPath>..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.dll</HintPath>
<Private>True</Private>
<Reference Include="FluentAssertions, Version=4.19.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
<HintPath>..\packages\FluentAssertions.4.19.0\lib\net40\FluentAssertions.dll</HintPath>
</Reference>
<Reference Include="FluentAssertions.Core, Version=4.18.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
<HintPath>..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.Core.dll</HintPath>
<Private>True</Private>
<Reference Include="FluentAssertions.Core, Version=4.19.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
<HintPath>..\packages\FluentAssertions.4.19.0\lib\net40\FluentAssertions.Core.dll</HintPath>
</Reference>
<Reference Include="nunit.framework, Version=3.5.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>..\packages\NUnit.3.5.0\lib\net40\nunit.framework.dll</HintPath>
<Private>True</Private>
<Reference Include="Moq, Version=4.2.1510.2205, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
<HintPath>..\packages\Moq.4.2.1510.2205\lib\net40\Moq.dll</HintPath>
</Reference>
<Reference Include="nunit.framework, Version=3.6.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>..\packages\NUnit.3.6.0\lib\net40\nunit.framework.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />

View File

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

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

@@ -37,6 +37,7 @@ namespace NzbDrone.Api.Calendar
var end = DateTime.Today.AddDays(futureDays);
var unmonitored = false;
var premiersOnly = false;
var asAllDay = false;
var tags = new List<int>();
// TODO: Remove start/end parameters in v3, they don't work well for iCal
@@ -46,6 +47,7 @@ namespace NzbDrone.Api.Calendar
var queryFutureDays = Request.Query.FutureDays;
var queryUnmonitored = Request.Query.Unmonitored;
var queryPremiersOnly = Request.Query.PremiersOnly;
var queryAsAllDay = Request.Query.AsAllDay;
var queryTags = Request.Query.Tags;
if (queryStart.HasValue) start = DateTime.Parse(queryStart.Value);
@@ -73,6 +75,11 @@ namespace NzbDrone.Api.Calendar
premiersOnly = bool.Parse(queryPremiersOnly.Value);
}
if (queryAsAllDay.HasValue)
{
asAllDay = bool.Parse(queryAsAllDay.Value);
}
if (queryTags.HasValue)
{
var tagInput = (string)queryTags.Value.ToString();
@@ -102,11 +109,19 @@ namespace NzbDrone.Api.Calendar
var occurrence = calendar.Create<Event>();
occurrence.Uid = "NzbDrone_episode_" + episode.Id;
occurrence.Status = episode.HasFile ? EventStatus.Confirmed : EventStatus.Tentative;
occurrence.Start = new CalDateTime(episode.AirDateUtc.Value) { HasTime = true };
occurrence.End = new CalDateTime(episode.AirDateUtc.Value.AddMinutes(episode.Series.Runtime)) { HasTime = true };
occurrence.Description = episode.Overview;
occurrence.Categories = new List<string>() { episode.Series.Network };
if (asAllDay)
{
occurrence.Start = new CalDateTime(episode.AirDateUtc.Value) { HasTime = false };
}
else
{
occurrence.Start = new CalDateTime(episode.AirDateUtc.Value) { HasTime = true };
occurrence.End = new CalDateTime(episode.AirDateUtc.Value.AddMinutes(episode.Series.Runtime)) { HasTime = true };
}
switch (episode.Series.SeriesType)
{
case SeriesTypes.Daily:

View File

@@ -73,14 +73,14 @@ namespace NzbDrone.Api.ClientSchema
if (propertyInfo.PropertyType == typeof(int))
{
var value = Convert.ToInt32(field.Value);
propertyInfo.SetValue(target, value, null);
var value = field.Value.ToString().ParseInt32();
propertyInfo.SetValue(target, value ?? 0, null);
}
else if (propertyInfo.PropertyType == typeof(long))
{
var value = Convert.ToInt64(field.Value);
propertyInfo.SetValue(target, value, null);
var value = field.Value.ToString().ParseInt64();
propertyInfo.SetValue(target, value ?? 0, null);
}
else if (propertyInfo.PropertyType == typeof(int?))

View File

@@ -20,6 +20,7 @@ namespace NzbDrone.Api.Config
public bool SkipFreeSpaceCheckWhenImporting { get; set; }
public bool CopyUsingHardlinks { get; set; }
public bool ImportExtraFiles { get; set; }
public string ExtraFileExtensions { get; set; }
public bool EnableMediaInfo { get; set; }
}
@@ -44,6 +45,7 @@ namespace NzbDrone.Api.Config
SkipFreeSpaceCheckWhenImporting = model.SkipFreeSpaceCheckWhenImporting,
CopyUsingHardlinks = model.CopyUsingHardlinks,
ImportExtraFiles = model.ImportExtraFiles,
ExtraFileExtensions = model.ExtraFileExtensions,
EnableMediaInfo = model.EnableMediaInfo
};

View File

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

View File

@@ -3,7 +3,6 @@ using NzbDrone.Api.REST;
using NzbDrone.Core.Tv;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.SignalR;
using Nancy;
namespace NzbDrone.Api.Episodes
{

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

@@ -63,18 +63,16 @@ namespace NzbDrone.Api.ErrorManagement
}.AsResponse(HttpStatusCode.Conflict);
}
var sqlErrorMessage = string.Format("[{0} {1}]", context.Request.Method, context.Request.Path);
_logger.Error(sqLiteException, sqlErrorMessage);
_logger.Error(sqLiteException, "[{0} {1}]", context.Request.Method, context.Request.Path);
}
_logger.Fatal(exception, "Request Failed");
_logger.Fatal(exception, "Request Failed. {0} {1}", context.Request.Method, context.Request.Path);
return new ErrorModel
{
Message = exception.Message,
Description = exception.ToString()
}.AsResponse(HttpStatusCode.InternalServerError);
{
Message = exception.Message,
Description = exception.ToString()
}.AsResponse(HttpStatusCode.InternalServerError);
}
}
}

View File

@@ -39,7 +39,7 @@ namespace NzbDrone.Api.Extensions.Pipelines
context.Items["ApiRequestStartTime"] = DateTime.UtcNow;
var reqPath = GetRequestPathAndQuery(context.Request);
_loggerHttp.Trace("Req: {0} [{1}] {2}", id, context.Request.Method, reqPath);
return null;
@@ -80,7 +80,7 @@ namespace NzbDrone.Api.Extensions.Pipelines
{
if (request.Url.Query.IsNotNullOrWhiteSpace())
{
return string.Concat(request.Url.Path, "?", request.Url.Query);
return string.Concat(request.Url.Path, request.Url.Query);
}
else
{
@@ -88,4 +88,4 @@ namespace NzbDrone.Api.Extensions.Pipelines
}
}
}
}
}

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

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

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

View File

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

View File

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

View File

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

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)
{
@@ -96,7 +96,7 @@ namespace NzbDrone.Api.Indexers
}
catch (Exception ex)
{
_logger.Error(ex, "Episode search failed: " + ex.Message);
_logger.Error(ex, "Episode search failed");
}
return new List<ReleaseResource>();
@@ -113,8 +113,14 @@ namespace NzbDrone.Api.Indexers
protected override ReleaseResource MapDecision(DownloadDecision decision, int initialWeight)
{
_remoteEpisodeCache.Set(decision.RemoteEpisode.Release.Guid, decision.RemoteEpisode, TimeSpan.FromMinutes(30));
return base.MapDecision(decision, initialWeight);
var resource = base.MapDecision(decision, initialWeight);
_remoteEpisodeCache.Set(GetCacheKey(resource), decision.RemoteEpisode, TimeSpan.FromMinutes(30));
return resource;
}
private string GetCacheKey(ReleaseResource resource)
{
return string.Concat(resource.IndexerId, "_", resource.Guid);
}
}
}

View File

@@ -24,9 +24,9 @@ namespace NzbDrone.Api
protected override void ApplicationStartup(TinyIoCContainer container, IPipelines pipelines)
{
Logger.Info("Starting NzbDrone API");
Logger.Info("Starting Web Server");
if (RuntimeInfoBase.IsProduction)
if (RuntimeInfo.IsProduction)
{
DiagnosticsHook.Disable(pipelines);
}

View File

@@ -41,20 +41,17 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="antlr.runtime, Version=2.7.6.2, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Ical.Net.2.2.25\lib\net40\antlr.runtime.dll</HintPath>
<Private>True</Private>
<HintPath>..\packages\Ical.Net.2.2.32\lib\net40\antlr.runtime.dll</HintPath>
</Reference>
<Reference Include="FluentValidation, Version=6.2.1.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\FluentValidation.6.2.1.0\lib\portable-net40+sl50+wp80+win8+wpa81\FluentValidation.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Ical.Net, Version=2.1.0.30332, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Ical.Net.2.2.25\lib\net40\Ical.Net.dll</HintPath>
<Private>True</Private>
<Reference Include="Ical.Net, Version=2.1.0.18776, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Ical.Net.2.2.32\lib\net40\Ical.Net.dll</HintPath>
</Reference>
<Reference Include="Ical.Net.Collections, Version=2.1.0.30331, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Ical.Net.2.2.25\lib\net40\Ical.Net.Collections.dll</HintPath>
<Private>True</Private>
<Reference Include="Ical.Net.Collections, Version=2.1.0.18775, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Ical.Net.2.2.32\lib\net40\Ical.Net.Collections.dll</HintPath>
</Reference>
<Reference Include="Nancy, Version=1.4.2.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Nancy.1.4.3\lib\net40\Nancy.dll</HintPath>
@@ -68,17 +65,15 @@
<HintPath>..\packages\Nancy.Authentication.Forms.1.4.1\lib\net40\Nancy.Authentication.Forms.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Newtonsoft.Json.6.0.6\lib\net40\Newtonsoft.Json.dll</HintPath>
<Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.9.0.1\lib\net40\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.3.11\lib\net40\NLog.dll</HintPath>
<Private>True</Private>
<HintPath>..\packages\NLog.4.4.3\lib\net40\NLog.dll</HintPath>
</Reference>
<Reference Include="NodaTime, Version=1.3.0.0, Culture=neutral, PublicKeyToken=4226afe0d9b296d1, processorArchitecture=MSIL">
<HintPath>..\packages\Ical.Net.2.2.25\lib\net40\NodaTime.dll</HintPath>
<Private>True</Private>
<HintPath>..\packages\Ical.Net.2.2.32\lib\net40\NodaTime.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
@@ -111,6 +106,7 @@
<Compile Include="Commands\CommandResource.cs" />
<Compile Include="Extensions\AccessControlHeaders.cs" />
<Compile Include="Extensions\Pipelines\CorsPipeline.cs" />
<Compile Include="Extensions\Pipelines\UrlBasePipeline.cs" />
<Compile Include="Extensions\Pipelines\RequestLoggingPipeline.cs" />
<Compile Include="Frontend\Mappers\LoginHtmlMapper.cs" />
<Compile Include="Frontend\Mappers\RobotsTxtMapper.cs" />

View File

@@ -41,7 +41,7 @@ namespace NzbDrone.Api.Series
public static List<Season> ToModel(this IEnumerable<SeasonResource> resources)
{
return resources.Select(ToModel).ToList();
return resources?.Select(ToModel).ToList() ?? new List<Season>();
}
}
}

View File

@@ -29,12 +29,14 @@ namespace NzbDrone.Api.Series
{
private readonly ISeriesService _seriesService;
private readonly IAddSeriesService _addSeriesService;
private readonly ISeriesStatisticsService _seriesStatisticsService;
private readonly ISceneMappingService _sceneMappingService;
private readonly IMapCoversToLocal _coverMapper;
public SeriesModule(IBroadcastSignalRMessage signalRBroadcaster,
ISeriesService seriesService,
IAddSeriesService addSeriesService,
ISeriesStatisticsService seriesStatisticsService,
ISceneMappingService sceneMappingService,
IMapCoversToLocal coverMapper,
@@ -48,6 +50,7 @@ namespace NzbDrone.Api.Series
: base(signalRBroadcaster)
{
_seriesService = seriesService;
_addSeriesService = addSeriesService;
_seriesStatisticsService = seriesStatisticsService;
_sceneMappingService = sceneMappingService;
@@ -74,7 +77,6 @@ namespace NzbDrone.Api.Series
PostValidator.RuleFor(s => s.Path).IsValidPath().When(s => s.RootFolderPath.IsNullOrWhiteSpace());
PostValidator.RuleFor(s => s.RootFolderPath).IsValidPath().When(s => s.Path.IsNullOrWhiteSpace());
PostValidator.RuleFor(s => s.Title).NotEmpty();
PostValidator.RuleFor(s => s.TvdbId).GreaterThan(0).SetValidator(seriesExistsValidator);
PutValidator.RuleFor(s => s.Path).IsValidPath();
@@ -114,7 +116,7 @@ namespace NzbDrone.Api.Series
{
var model = seriesResource.ToModel();
return _seriesService.AddSeries(model).Id;
return _addSeriesService.AddSeries(model).Id;
}
private void UpdateSeries(SeriesResource seriesResource)
@@ -180,7 +182,7 @@ namespace NzbDrone.Api.Series
foreach (var season in resource.Seasons)
{
season.Statistics = SeasonStatisticsResourceMapper.ToResource(dictSeasonStats.GetValueOrDefault(season.SeasonNumber));
season.Statistics = dictSeasonStats.GetValueOrDefault(season.SeasonNumber).ToResource();
}
}
}

View File

@@ -207,19 +207,9 @@ namespace NzbDrone.Api.Series
public static Core.Tv.Series ToModel(this SeriesResource resource, Core.Tv.Series series)
{
series.TvdbId = resource.TvdbId;
var updatedSeries = resource.ToModel();
series.Seasons = resource.Seasons.ToModel();
series.Path = resource.Path;
series.ProfileId = resource.ProfileId;
series.SeasonFolder = resource.SeasonFolder;
series.Monitored = resource.Monitored;
series.SeriesType = resource.SeriesType;
series.RootFolderPath = resource.RootFolderPath;
series.Tags = resource.Tags;
series.AddOptions = resource.AddOptions;
series.ApplyChanges(updatedSeries);
return series;
}

View File

@@ -13,6 +13,8 @@ namespace NzbDrone.Api.System
{
private readonly IAppFolderInfo _appFolderInfo;
private readonly IRuntimeInfo _runtimeInfo;
private readonly IPlatformInfo _platformInfo;
private readonly IOsInfo _osInfo;
private readonly IRouteCacheProvider _routeCacheProvider;
private readonly IConfigFileProvider _configFileProvider;
private readonly IMainDatabase _database;
@@ -20,14 +22,17 @@ namespace NzbDrone.Api.System
public SystemModule(IAppFolderInfo appFolderInfo,
IRuntimeInfo runtimeInfo,
IPlatformInfo platformInfo,
IOsInfo osInfo,
IRouteCacheProvider routeCacheProvider,
IConfigFileProvider configFileProvider,
IMainDatabase database,
ILifecycleService lifecycleService)
: base("system")
ILifecycleService lifecycleService) : base("system")
{
_appFolderInfo = appFolderInfo;
_runtimeInfo = runtimeInfo;
_platformInfo = platformInfo;
_osInfo = osInfo;
_routeCacheProvider = routeCacheProvider;
_configFileProvider = configFileProvider;
_database = database;
@@ -41,27 +46,29 @@ namespace NzbDrone.Api.System
private Response GetStatus()
{
return new
{
Version = BuildInfo.Version.ToString(),
BuildTime = BuildInfo.BuildDateTime,
IsDebug = BuildInfo.IsDebug,
IsProduction = RuntimeInfoBase.IsProduction,
IsAdmin = _runtimeInfo.IsAdmin,
IsUserInteractive = RuntimeInfoBase.IsUserInteractive,
StartupPath = _appFolderInfo.StartUpFolder,
AppData = _appFolderInfo.GetAppDataPath(),
OsVersion = OsInfo.Version.ToString(),
IsMonoRuntime = OsInfo.IsMonoRuntime,
IsMono = OsInfo.IsNotWindows,
IsLinux = OsInfo.IsLinux,
IsOsx = OsInfo.IsOsx,
IsWindows = OsInfo.IsWindows,
Branch = _configFileProvider.Branch,
Authentication = _configFileProvider.AuthenticationMethod,
SqliteVersion = _database.Version,
UrlBase = _configFileProvider.UrlBase,
RuntimeVersion = _runtimeInfo.RuntimeVersion
}.AsResponse();
{
Version = BuildInfo.Version.ToString(),
BuildTime = BuildInfo.BuildDateTime,
IsDebug = BuildInfo.IsDebug,
IsProduction = RuntimeInfo.IsProduction,
IsAdmin = _runtimeInfo.IsAdmin,
IsUserInteractive = RuntimeInfo.IsUserInteractive,
StartupPath = _appFolderInfo.StartUpFolder,
AppData = _appFolderInfo.GetAppDataPath(),
OsName = _osInfo.Name,
OsVersion = _osInfo.Version,
IsMonoRuntime = PlatformInfo.IsMono,
IsMono = PlatformInfo.IsMono,
IsLinux = OsInfo.IsLinux,
IsOsx = OsInfo.IsOsx,
IsWindows = OsInfo.IsWindows,
Branch = _configFileProvider.Branch,
Authentication = _configFileProvider.AuthenticationMethod,
SqliteVersion = _database.Version,
UrlBase = _configFileProvider.UrlBase,
RuntimeVersion = _platformInfo.Version,
RuntimeName = PlatformInfo.Platform
}.AsResponse();
}
private Response GetRoutes()

View File

@@ -22,7 +22,7 @@ namespace NzbDrone.Api
/// </summary>
public static IEnumerable<Func<Assembly, bool>> DefaultAutoRegisterIgnoredAssemblies = new Func<Assembly, bool>[]
{
asm => !asm.FullName.StartsWith("Nancy.", StringComparison.InvariantCulture),
asm => !asm.FullName.StartsWith("Nancy.", StringComparison.InvariantCulture)
};
/// <summary>

View File

@@ -4,7 +4,7 @@
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
<bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="FluentMigrator" publicKeyToken="aacfc7de5acabf05" culture="neutral" />

View File

@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="FluentValidation" version="6.2.1.0" targetFramework="net40" />
<package id="Ical.Net" version="2.2.25" targetFramework="net40" />
<package id="Ical.Net" version="2.2.32" targetFramework="net40" />
<package id="Nancy" version="1.4.3" targetFramework="net40" />
<package id="Nancy.Authentication.Basic" version="1.4.1" targetFramework="net40" />
<package id="Nancy.Authentication.Forms" version="1.4.1" targetFramework="net40" />
<package id="Newtonsoft.Json" version="6.0.6" targetFramework="net40" />
<package id="NLog" version="4.3.11" targetFramework="net40" />
<package id="Newtonsoft.Json" version="9.0.1" targetFramework="net40" />
<package id="NLog" version="4.4.3" targetFramework="net40" />
</packages>

View File

@@ -41,21 +41,17 @@
<HintPath>..\packages\NBuilder.4.0.0\lib\net40\FizzWare.NBuilder.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="FluentAssertions, Version=4.18.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
<HintPath>..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.dll</HintPath>
<Private>True</Private>
<Reference Include="FluentAssertions, Version=4.19.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
<HintPath>..\packages\FluentAssertions.4.19.0\lib\net40\FluentAssertions.dll</HintPath>
</Reference>
<Reference Include="FluentAssertions.Core, Version=4.18.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
<HintPath>..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.Core.dll</HintPath>
<Private>True</Private>
<Reference Include="FluentAssertions.Core, Version=4.19.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
<HintPath>..\packages\FluentAssertions.4.19.0\lib\net40\FluentAssertions.Core.dll</HintPath>
</Reference>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.3.11\lib\net40\NLog.dll</HintPath>
<Private>True</Private>
<HintPath>..\packages\NLog.4.4.3\lib\net40\NLog.dll</HintPath>
</Reference>
<Reference Include="nunit.framework, Version=3.5.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>..\packages\NUnit.3.5.0\lib\net40\nunit.framework.dll</HintPath>
<Private>True</Private>
<Reference Include="nunit.framework, Version=3.6.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>..\packages\NUnit.3.6.0\lib\net40\nunit.framework.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
@@ -112,8 +108,8 @@
xcopy /s /y "$(SolutionDir)\..\_output\NzbDrone.Windows.*" "$(TargetDir)"
</PostBuildEvent>
<PostBuildEvent Condition="('$(OS)' != 'Windows_NT')">
cp -rv $(SolutionDir)\..\_output\NzbDrone.Mono.* $(TargetDir)
cp -rv $(SolutionDir)\..\_output\NzbDrone.Windows.* $(TargetDir)
cp -rv $(SolutionDir)\..\_output\NzbDrone.Mono.* $(TargetDir) || true
cp -rv $(SolutionDir)\..\_output\NzbDrone.Windows.* $(TargetDir) || true
</PostBuildEvent>
</PropertyGroup>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.

View File

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

View File

@@ -38,21 +38,17 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="FluentAssertions, Version=4.18.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
<HintPath>..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.dll</HintPath>
<Private>True</Private>
<Reference Include="FluentAssertions, Version=4.19.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
<HintPath>..\packages\FluentAssertions.4.19.0\lib\net40\FluentAssertions.dll</HintPath>
</Reference>
<Reference Include="FluentAssertions.Core, Version=4.18.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
<HintPath>..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.Core.dll</HintPath>
<Private>True</Private>
<Reference Include="FluentAssertions.Core, Version=4.19.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
<HintPath>..\packages\FluentAssertions.4.19.0\lib\net40\FluentAssertions.Core.dll</HintPath>
</Reference>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.3.11\lib\net40\NLog.dll</HintPath>
<Private>True</Private>
<HintPath>..\packages\NLog.4.4.3\lib\net40\NLog.dll</HintPath>
</Reference>
<Reference Include="nunit.framework, Version=3.5.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>..\packages\NUnit.3.5.0\lib\net40\nunit.framework.dll</HintPath>
<Private>True</Private>
<Reference Include="nunit.framework, Version=3.6.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>..\packages\NUnit.3.6.0\lib\net40\nunit.framework.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
@@ -62,13 +58,11 @@
<Reference Include="System.Xml" />
<Reference Include="System.Xml.Linq" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="WebDriver, Version=3.0.1.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Selenium.WebDriver.3.0.1\lib\net40\WebDriver.dll</HintPath>
<Private>True</Private>
<Reference Include="WebDriver, Version=3.2.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Selenium.WebDriver.3.2.0\lib\net40\WebDriver.dll</HintPath>
</Reference>
<Reference Include="WebDriver.Support, Version=3.0.1.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Selenium.Support.3.0.1\lib\net40\WebDriver.Support.dll</HintPath>
<Private>True</Private>
<Reference Include="WebDriver.Support, Version=3.2.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Selenium.Support.3.2.0\lib\net40\WebDriver.Support.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>

View File

@@ -4,7 +4,7 @@
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
<bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="FluentMigrator" publicKeyToken="aacfc7de5acabf05" culture="neutral" />

View File

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

View File

@@ -16,6 +16,7 @@ namespace NzbDrone.Common.Test.DiskTests
private readonly string _targetPath = @"C:\target\my.video.mkv".AsOsAgnostic();
private readonly string _backupPath = @"C:\source\my.video.mkv.backup~".AsOsAgnostic();
private readonly string _tempTargetPath = @"C:\target\my.video.mkv.partial~".AsOsAgnostic();
private readonly string _nfsFile = ".nfs01231232";
[SetUp]
public void SetUp()
@@ -642,6 +643,21 @@ namespace NzbDrone.Common.Test.DiskTests
VerifyCopyFolder(source.FullName, destination.FullName);
}
[Test]
public void CopyFolder_should_ignore_nfs_temp_file()
{
WithRealDiskProvider();
var source = GetFilledTempFolder();
File.WriteAllText(Path.Combine(source.FullName, _nfsFile), "SubFile1");
var destination = new DirectoryInfo(GetTempFilePath());
Subject.TransferFolder(source.FullName, destination.FullName, TransferMode.Copy);
File.Exists(Path.Combine(destination.FullName, _nfsFile)).Should().BeFalse();
}
[Test]
public void MoveFolder_should_move_folder()
@@ -704,6 +720,26 @@ namespace NzbDrone.Common.Test.DiskTests
destination.GetFileSystemInfos().Should().BeEmpty();
}
[Test]
public void MirrorFolder_should_not_remove_nfs_files()
{
WithRealDiskProvider();
var original = GetFilledTempFolder();
var source = new DirectoryInfo(GetTempFilePath());
var destination = new DirectoryInfo(GetTempFilePath());
source.Create();
Subject.TransferFolder(original.FullName, destination.FullName, TransferMode.Copy);
File.WriteAllText(Path.Combine(destination.FullName, _nfsFile), "SubFile1");
var count = Subject.MirrorFolder(source.FullName, destination.FullName);
count.Should().Equals(0);
destination.GetFileSystemInfos().Should().HaveCount(1);
}
[Test]
public void MirrorFolder_should_add_new_files()
{
@@ -721,6 +757,24 @@ namespace NzbDrone.Common.Test.DiskTests
VerifyCopyFolder(original.FullName, destination.FullName);
}
[Test]
public void MirrorFolder_should_ignore_nfs_temp_file()
{
WithRealDiskProvider();
var source = GetFilledTempFolder();
File.WriteAllText(Path.Combine(source.FullName, _nfsFile), "SubFile1");
var destination = new DirectoryInfo(GetTempFilePath());
var count = Subject.MirrorFolder(source.FullName, destination.FullName);
count.Should().Equals(3);
File.Exists(Path.Combine(destination.FullName, _nfsFile)).Should().BeFalse();
}
[Test]
public void MirrorFolder_should_not_touch_equivalent_files()
{

View File

@@ -0,0 +1,23 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Common.Test.EnvironmentInfo
{
[TestFixture]
public class BuildInfoFixture
{
[Test]
public void should_return_version()
{
BuildInfo.Version.Major.Should().BeOneOf(2, 10);
}
[Test]
public void should_get_branch()
{
BuildInfo.Branch.Should().NotBe("unknow");
BuildInfo.Branch.Should().NotBeNullOrWhiteSpace();
}
}
}

View File

@@ -29,7 +29,7 @@ namespace NzbDrone.Common.Test
[Test]
public void IsProduction_should_return_false_when_run_within_nunit()
{
RuntimeInfoBase.IsProduction.Should().BeFalse("Process name is " + Process.GetCurrentProcess().ProcessName + " Folder is " + Directory.GetCurrentDirectory());
RuntimeInfo.IsProduction.Should().BeFalse("Process name is " + Process.GetCurrentProcess().ProcessName + " Folder is " + Directory.GetCurrentDirectory());
}
[Test]

View File

@@ -0,0 +1,21 @@
using FluentAssertions;
using NUnit.Framework;
namespace NzbDrone.Common.Test
{
[TestFixture]
public class HashUtilFixture
{
[Test]
public void should_create_anon_id()
{
HashUtil.AnonymousToken().Should().NotBeNullOrEmpty();
}
[Test]
public void should_create_the_same_id()
{
HashUtil.AnonymousToken().Should().Be(HashUtil.AnonymousToken());
}
}
}

View File

@@ -9,6 +9,7 @@ using Moq;
using NLog;
using NUnit.Framework;
using NzbDrone.Common.Cache;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Http;
using NzbDrone.Common.Http.Dispatchers;
using NzbDrone.Common.Http.Proxy;
@@ -30,6 +31,12 @@ namespace NzbDrone.Common.Test.Http
[SetUp]
public void SetUp()
{
Mocker.GetMock<IPlatformInfo>().Setup(c => c.Version).Returns(new Version("1.0.0"));
Mocker.GetMock<IOsInfo>().Setup(c => c.Name).Returns("TestOS");
Mocker.GetMock<IOsInfo>().Setup(c => c.Version).Returns("9.0.0");
Mocker.SetConstant<IUserAgentBuilder>(Mocker.Resolve<UserAgentBuilder>());
Mocker.SetConstant<ICacheManager>(Mocker.Resolve<CacheManager>());
Mocker.SetConstant<ICreateManagedWebProxy>(Mocker.Resolve<ManagedWebProxyFactory>());
Mocker.SetConstant<IRateLimitService>(Mocker.Resolve<RateLimitService>());
@@ -48,7 +55,7 @@ namespace NzbDrone.Common.Test.Http
[Test]
public void should_execute_simple_get()
{
var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost));
var request = new HttpRequest($"http://{_httpBinHost}/get");
var response = Subject.Execute(request);
@@ -58,7 +65,7 @@ namespace NzbDrone.Common.Test.Http
[Test]
public void should_execute_https_get()
{
var request = new HttpRequest(string.Format("https://{0}/get", _httpBinHost));
var request = new HttpRequest($"https://{_httpBinHost}/get");
var response = Subject.Execute(request);
@@ -68,7 +75,7 @@ namespace NzbDrone.Common.Test.Http
[Test]
public void should_execute_typed_get()
{
var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost));
var request = new HttpRequest($"http://{_httpBinHost}/get");
var response = Subject.Get<HttpBinResource>(request);
@@ -80,7 +87,7 @@ namespace NzbDrone.Common.Test.Http
{
var message = "{ my: 1 }";
var request = new HttpRequest(string.Format("http://{0}/post", _httpBinHost));
var request = new HttpRequest($"http://{_httpBinHost}/post");
request.SetContent(message);
var response = Subject.Post<HttpBinResource>(request);
@@ -91,7 +98,7 @@ namespace NzbDrone.Common.Test.Http
[TestCase("gzip")]
public void should_execute_get_using_gzip(string compression)
{
var request = new HttpRequest(string.Format("http://{0}/{1}", _httpBinHost, compression));
var request = new HttpRequest($"http://{_httpBinHost}/{compression}");
var response = Subject.Get<HttpBinResource>(request);
@@ -107,7 +114,7 @@ namespace NzbDrone.Common.Test.Http
[TestCase(HttpStatusCode.BadGateway)]
public void should_throw_on_unsuccessful_status_codes(int statusCode)
{
var request = new HttpRequest(string.Format("http://{0}/status/{1}", _httpBinHost, statusCode));
var request = new HttpRequest($"http://{_httpBinHost}/status/{statusCode}");
var exception = Assert.Throws<HttpException>(() => Subject.Get<HttpBinResource>(request));
@@ -119,7 +126,7 @@ namespace NzbDrone.Common.Test.Http
[Test]
public void should_not_follow_redirects_when_not_in_production()
{
var request = new HttpRequest(string.Format("http://{0}/redirect/1", _httpBinHost));
var request = new HttpRequest($"http://{_httpBinHost}/redirect/1");
Subject.Get(request);
@@ -129,7 +136,7 @@ namespace NzbDrone.Common.Test.Http
[Test]
public void should_follow_redirects()
{
var request = new HttpRequest(string.Format("http://{0}/redirect/1", _httpBinHost));
var request = new HttpRequest($"http://{_httpBinHost}/redirect/1");
request.AllowAutoRedirect = true;
Subject.Get(request);
@@ -140,7 +147,7 @@ namespace NzbDrone.Common.Test.Http
[Test]
public void should_send_user_agent()
{
var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost));
var request = new HttpRequest($"http://{_httpBinHost}/get");
var response = Subject.Get<HttpBinResource>(request);
@@ -154,7 +161,7 @@ namespace NzbDrone.Common.Test.Http
[TestCase("Accept", "text/xml, text/rss+xml, application/rss+xml")]
public void should_send_headers(string header, string value)
{
var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost));
var request = new HttpRequest($"http://{_httpBinHost}/get");
request.Headers.Add(header, value);
var response = Subject.Get<HttpBinResource>(request);
@@ -177,7 +184,7 @@ namespace NzbDrone.Common.Test.Http
[Test]
public void should_send_cookie()
{
var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost));
var request = new HttpRequest($"http://{_httpBinHost}/get");
request.Cookies["my"] = "cookie";
var response = Subject.Get<HttpBinResource>(request);
@@ -194,7 +201,7 @@ namespace NzbDrone.Common.Test.Http
var oldRequest = new HttpRequest("http://eu.httpbin.org/get");
oldRequest.Cookies["my"] = "cookie";
var oldClient = new HttpClient(new IHttpRequestInterceptor[0], Mocker.Resolve<ICacheManager>(), Mocker.Resolve<IRateLimitService>(), Mocker.Resolve<IHttpDispatcher>(), Mocker.Resolve<Logger>());
var oldClient = new HttpClient(new IHttpRequestInterceptor[0], Mocker.Resolve<ICacheManager>(), Mocker.Resolve<IRateLimitService>(), Mocker.Resolve<IHttpDispatcher>(), Mocker.GetMock<IUserAgentBuilder>().Object, Mocker.Resolve<Logger>());
oldClient.Should().NotBeSameAs(Subject);
@@ -234,12 +241,12 @@ namespace NzbDrone.Common.Test.Http
[Test]
public void should_not_store_response_cookie()
{
var requestSet = new HttpRequest(string.Format("http://{0}/cookies/set?my=cookie", _httpBinHost));
var requestSet = new HttpRequest($"http://{_httpBinHost}/cookies/set?my=cookie");
requestSet.AllowAutoRedirect = false;
var responseSet = Subject.Get(requestSet);
var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost));
var request = new HttpRequest($"http://{_httpBinHost}/get");
var response = Subject.Get<HttpBinResource>(request);
@@ -251,13 +258,13 @@ namespace NzbDrone.Common.Test.Http
[Test]
public void should_store_response_cookie()
{
var requestSet = new HttpRequest(string.Format("http://{0}/cookies/set?my=cookie", _httpBinHost));
var requestSet = new HttpRequest($"http://{_httpBinHost}/cookies/set?my=cookie");
requestSet.AllowAutoRedirect = false;
requestSet.StoreResponseCookie = true;
var responseSet = Subject.Get(requestSet);
var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost));
var request = new HttpRequest($"http://{_httpBinHost}/get");
var response = Subject.Get<HttpBinResource>(request);
@@ -273,14 +280,14 @@ namespace NzbDrone.Common.Test.Http
[Test]
public void should_overwrite_response_cookie()
{
var requestSet = new HttpRequest(string.Format("http://{0}/cookies/set?my=cookie", _httpBinHost));
var requestSet = new HttpRequest($"http://{_httpBinHost}/cookies/set?my=cookie");
requestSet.AllowAutoRedirect = false;
requestSet.StoreResponseCookie = true;
requestSet.Cookies["my"] = "oldcookie";
var responseSet = Subject.Get(requestSet);
var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost));
var request = new HttpRequest($"http://{_httpBinHost}/get");
var response = Subject.Get<HttpBinResource>(request);
@@ -296,7 +303,7 @@ namespace NzbDrone.Common.Test.Http
[Test]
public void should_throw_on_http429_too_many_requests()
{
var request = new HttpRequest(string.Format("http://{0}/status/429", _httpBinHost));
var request = new HttpRequest($"http://{_httpBinHost}/status/429");
Assert.Throws<TooManyRequestsException>(() => Subject.Get(request));
@@ -316,7 +323,7 @@ namespace NzbDrone.Common.Test.Http
.Setup(v => v.PostResponse(It.IsAny<HttpResponse>()))
.Returns<HttpResponse>(r => r);
var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost));
var request = new HttpRequest($"http://{_httpBinHost}/get");
Subject.Get(request);
@@ -338,7 +345,7 @@ namespace NzbDrone.Common.Test.Http
{
// the date is bad in the below - should be 13-Jul-2026
string malformedCookie = @"__cfduid=d29e686a9d65800021c66faca0a29b4261436890790; expires=Mon, 13-Jul-26 16:19:50 GMT; path=/; HttpOnly";
var requestSet = new HttpRequestBuilder(string.Format("http://{0}/response-headers", _httpBinHost))
var requestSet = new HttpRequestBuilder($"http://{_httpBinHost}/response-headers")
.AddQueryParam("Set-Cookie", malformedCookie)
.Build();
@@ -347,7 +354,7 @@ namespace NzbDrone.Common.Test.Http
var responseSet = Subject.Get(requestSet);
var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost));
var request = new HttpRequest($"http://{_httpBinHost}/get");
var response = Subject.Get<HttpBinResource>(request);
@@ -371,7 +378,7 @@ namespace NzbDrone.Common.Test.Http
{
try
{
string url = string.Format("http://{0}/response-headers?Set-Cookie={1}", _httpBinHost, Uri.EscapeUriString(malformedCookie));
string url = $"http://{_httpBinHost}/response-headers?Set-Cookie={Uri.EscapeUriString(malformedCookie)}";
var requestSet = new HttpRequest(url);
requestSet.AllowAutoRedirect = false;
@@ -379,7 +386,7 @@ namespace NzbDrone.Common.Test.Http
var responseSet = Subject.Get(requestSet);
var request = new HttpRequest(string.Format("http://{0}/get", _httpBinHost));
var request = new HttpRequest($"http://{_httpBinHost}/get");
var response = Subject.Get<HttpBinResource>(request);

View File

@@ -5,6 +5,7 @@ using System;
using System.Text;
using NzbDrone.Common.Http;
using System.Collections.Specialized;
using System.Linq;
namespace NzbDrone.Common.Test.Http
{
@@ -36,5 +37,17 @@ namespace NzbDrone.Common.Test.Http
Action action = () => httpheader.GetEncodingFromContentType();
action.ShouldThrow<ArgumentException>();
}
[Test]
public void should_parse_cookie_with_trailing_semi_colon()
{
var cookies = HttpHeader.ParseCookies("uid=123456; pass=123456b2f3abcde42ac3a123f3f1fc9f;");
cookies.Count.Should().Be(2);
cookies.First().Key.Should().Be("uid");
cookies.First().Value.Should().Be("123456");
cookies.Last().Key.Should().Be("pass");
cookies.Last().Value.Should().Be("123456b2f3abcde42ac3a123f3f1fc9f");
}
}
}

View File

@@ -0,0 +1,30 @@
using FluentAssertions;
using NUnit.Framework;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Http;
using NzbDrone.Test.Common;
namespace NzbDrone.Common.Test.Http
{
[TestFixture]
public class UserAgentBuilderFixture : TestBase<UserAgentBuilder>
{
[Test]
public void should_get_user_agent_if_os_version_is_null()
{
Mocker.GetMock<IOsInfo>().SetupGet(c => c.Version).Returns((string)null);
Mocker.GetMock<IOsInfo>().SetupGet(c => c.Name).Returns("TestOS");
Subject.GetUserAgent(false).Should().NotBeNullOrWhiteSpace();
}
[Test]
public void should_get_use_os_family_if_name_is_null()
{
Mocker.GetMock<IOsInfo>().SetupGet(c => c.Version).Returns((string)null);
Mocker.GetMock<IOsInfo>().SetupGet(c => c.Name).Returns((string)null);
Subject.GetUserAgent(false).Should().NotBeNullOrWhiteSpace();
}
}
}

View File

@@ -37,21 +37,17 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="FluentAssertions, Version=4.18.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
<HintPath>..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.dll</HintPath>
<Private>True</Private>
<Reference Include="FluentAssertions, Version=4.19.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
<HintPath>..\packages\FluentAssertions.4.19.0\lib\net40\FluentAssertions.dll</HintPath>
</Reference>
<Reference Include="FluentAssertions.Core, Version=4.18.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
<HintPath>..\packages\FluentAssertions.4.18.0\lib\net40\FluentAssertions.Core.dll</HintPath>
<Private>True</Private>
<Reference Include="FluentAssertions.Core, Version=4.19.0.0, Culture=neutral, PublicKeyToken=33f2691a05b67b6a, processorArchitecture=MSIL">
<HintPath>..\packages\FluentAssertions.4.19.0\lib\net40\FluentAssertions.Core.dll</HintPath>
</Reference>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.3.11\lib\net40\NLog.dll</HintPath>
<Private>True</Private>
<HintPath>..\packages\NLog.4.4.3\lib\net40\NLog.dll</HintPath>
</Reference>
<Reference Include="nunit.framework, Version=3.5.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>..\packages\NUnit.3.5.0\lib\net40\nunit.framework.dll</HintPath>
<Private>True</Private>
<Reference Include="nunit.framework, Version=3.6.0.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>..\packages\NUnit.3.6.0\lib\net40\nunit.framework.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
@@ -76,6 +72,7 @@
<Compile Include="DiskTests\IsParentFixtureBase.cs" />
<Compile Include="DiskTests\DiskTransferServiceFixture.cs" />
<Compile Include="EnsureTest\PathExtensionFixture.cs" />
<Compile Include="EnvironmentInfo\BuildInfoFixture.cs" />
<Compile Include="EnvironmentProviderTest.cs" />
<Compile Include="EnvironmentTests\EnvironmentProviderTest.cs" />
<Compile Include="EnvironmentTests\StartupArgumentsFixture.cs" />
@@ -83,11 +80,13 @@
<Compile Include="ExtensionTests\IEnumerableExtensionTests\ExceptByFixture.cs" />
<Compile Include="ExtensionTests\IEnumerableExtensionTests\IntersectByFixture.cs" />
<Compile Include="ExtensionTests\Int64ExtensionFixture.cs" />
<Compile Include="HashUtilFixture.cs" />
<Compile Include="Http\HttpClientFixture.cs" />
<Compile Include="Http\HttpHeaderFixture.cs" />
<Compile Include="Http\HttpRequestBuilderFixture.cs" />
<Compile Include="Http\HttpRequestFixture.cs" />
<Compile Include="Http\HttpUriFixture.cs" />
<Compile Include="Http\UserAgentBuilderFixture.cs" />
<Compile Include="InstrumentationTests\CleanseLogMessageFixture.cs" />
<Compile Include="LevenshteinDistanceFixture.cs" />
<Compile Include="OsPathFixture.cs" />

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.18.0" targetFramework="net40" />
<package id="FluentAssertions" version="4.19.0" targetFramework="net40" />
<package id="Moq" version="4.0.10827" />
<package id="NLog" version="4.3.11" targetFramework="net40" />
<package id="NUnit" version="3.5.0" targetFramework="net40" />
<package id="NLog" version="4.4.3" targetFramework="net40" />
<package id="NUnit" version="3.6.0" targetFramework="net40" />
</packages>

View File

@@ -20,8 +20,8 @@ namespace NzbDrone.Common.Cloud
.CreateFactory();
}
public IHttpRequestBuilderFactory Services { get; private set; }
public IHttpRequestBuilderFactory Services { get; }
public IHttpRequestBuilderFactory SkyHookTvdb { get; private set; }
public IHttpRequestBuilderFactory SkyHookTvdb { get; }
}
}

View File

@@ -6,19 +6,21 @@ using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Messaging;
using TinyIoC;
namespace NzbDrone.Common.Composition
{
public abstract class ContainerBuilderBase
{
private readonly List<Type> _loadedTypes;
public IContainer Container { get; private set; }
protected IContainer Container { get; }
protected ContainerBuilderBase(IStartupContext args, params string[] assemblies)
protected ContainerBuilderBase(IStartupContext args, List<string> assemblies)
{
_loadedTypes = new List<Type>();
assemblies.Add(OsInfo.IsWindows ? "NzbDrone.Windows" : "NzbDrone.Mono");
assemblies.Add("NzbDrone.Common");
foreach (var assembly in assemblies)
{
_loadedTypes.AddRange(Assembly.Load(assembly).GetTypes());

View File

@@ -16,6 +16,19 @@ namespace NzbDrone.Common.Disk
{
private static readonly Logger Logger = NzbDroneLogger.GetLogger(typeof(DiskProviderBase));
public static StringComparison PathStringComparison
{
get
{
if (OsInfo.IsWindows)
{
return StringComparison.OrdinalIgnoreCase;
}
return StringComparison.Ordinal;
}
}
public abstract long? GetAvailableSpace(string path);
public abstract void InheritFolderPermissions(string filename);
public abstract void SetPermissions(string path, string mask, string user, string group);
@@ -86,7 +99,7 @@ namespace NzbDrone.Common.Disk
public bool FileExists(string path)
{
Ensure.That(path, () => path).IsValidPath();
return FileExists(path, OsInfo.PathStringComparison);
return FileExists(path, PathStringComparison);
}
public bool FileExists(string path, StringComparison stringComparison)
@@ -95,16 +108,16 @@ namespace NzbDrone.Common.Disk
switch (stringComparison)
{
case StringComparison.CurrentCulture:
case StringComparison.InvariantCulture:
case StringComparison.Ordinal:
{
return File.Exists(path) && path == path.GetActualCasing();
}
case StringComparison.CurrentCulture:
case StringComparison.InvariantCulture:
case StringComparison.Ordinal:
{
return File.Exists(path) && path == path.GetActualCasing();
}
default:
{
return File.Exists(path);
}
{
return File.Exists(path);
}
}
}
@@ -115,7 +128,7 @@ namespace NzbDrone.Common.Disk
try
{
var testPath = Path.Combine(path, "sonarr_write_test.txt");
var testContent = string.Format("This file was created to verify if '{0}' is writable. It should've been automatically deleted. Feel free to delete it.", path);
var testContent = $"This file was created to verify if '{path}' is writable. It should've been automatically deleted. Feel free to delete it.";
File.WriteAllText(testPath, testContent);
File.Delete(testPath);
return true;

View File

@@ -64,11 +64,15 @@ namespace NzbDrone.Common.Disk
foreach (var subDir in _diskProvider.GetDirectoryInfos(sourcePath))
{
if (ShouldIgnore(subDir)) continue;
result &= TransferFolder(subDir.FullName, Path.Combine(targetPath, subDir.Name), mode, verificationMode);
}
foreach (var sourceFile in _diskProvider.GetFileInfos(sourcePath))
{
if (ShouldIgnore(sourceFile)) continue;
var destFile = Path.Combine(targetPath, sourceFile.Name);
result &= TransferFile(sourceFile.FullName, destFile, mode, true, verificationMode);
@@ -101,11 +105,15 @@ namespace NzbDrone.Common.Disk
foreach (var subDir in targetFolders.Where(v => !sourceFolders.Any(d => d.Name == v.Name)))
{
if (ShouldIgnore(subDir)) continue;
_diskProvider.DeleteFolder(subDir.FullName, true);
}
foreach (var subDir in sourceFolders)
{
if (ShouldIgnore(subDir)) continue;
filesCopied += MirrorFolder(subDir.FullName, Path.Combine(targetPath, subDir.Name));
}
@@ -114,11 +122,15 @@ namespace NzbDrone.Common.Disk
foreach (var targetFile in targetFiles.Where(v => !sourceFiles.Any(d => d.Name == v.Name)))
{
if (ShouldIgnore(targetFile)) continue;
_diskProvider.DeleteFile(targetFile.FullName);
}
foreach (var sourceFile in sourceFiles)
{
if (ShouldIgnore(sourceFile)) continue;
var targetFile = Path.Combine(targetPath, sourceFile.Name);
if (CompareFiles(sourceFile.FullName, targetFile))
@@ -352,7 +364,7 @@ namespace NzbDrone.Common.Disk
}
catch (Exception ex)
{
_logger.Error(ex, string.Format("Failed to properly rollback the file move [{0}] to [{1}], incomplete file may be left in target path.", sourcePath, targetPath));
_logger.Error(ex, "Failed to properly rollback the file move [{0}] to [{1}], incomplete file may be left in target path.", sourcePath, targetPath);
}
}
@@ -368,7 +380,7 @@ namespace NzbDrone.Common.Disk
}
catch (Exception ex)
{
_logger.Error(ex, string.Format("Failed to properly rollback the file move [{0}] to [{1}], file may be left in target path.", sourcePath, targetPath));
_logger.Error(ex, "Failed to properly rollback the file move [{0}] to [{1}], file may be left in target path.", sourcePath, targetPath);
}
}
@@ -387,7 +399,7 @@ namespace NzbDrone.Common.Disk
}
catch (Exception ex)
{
_logger.Error(ex, string.Format("Failed to properly rollback the file copy [{0}] to [{1}], file may be left in target path.", sourcePath, targetPath));
_logger.Error(ex, "Failed to properly rollback the file copy [{0}] to [{1}], file may be left in target path.", sourcePath, targetPath);
}
}
@@ -429,7 +441,7 @@ namespace NzbDrone.Common.Disk
if (i == RetryCount)
{
_logger.Error("Failed to completely transfer [{0}] to [{1}], aborting.", sourcePath, targetPath, i + 1, RetryCount);
_logger.Error("Failed to completely transfer [{0}] to [{1}], aborting.", sourcePath, targetPath);
}
else
{
@@ -564,5 +576,27 @@ namespace NzbDrone.Common.Disk
throw;
}
}
private bool ShouldIgnore(DirectoryInfo folder)
{
if (folder.Name.StartsWith(".nfs"))
{
_logger.Trace("Ignoring folder {0}", folder.FullName);
return true;
}
return false;
}
private bool ShouldIgnore(FileInfo file)
{
if (file.Name.StartsWith(".nfs") || file.Name == "debug.log" || file.Name.EndsWith(".socket"))
{
_logger.Trace("Ignoring file {0}", file.FullName);
return true;
}
return false;
}
}
}

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;

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

@@ -101,12 +101,12 @@ namespace NzbDrone.Common.EnsureThat
if (param.Value.IsPathValid()) return param;
if (OsInfo.IsNotWindows)
if (OsInfo.IsWindows)
{
throw ExceptionFactory.CreateForParamValidation(param.Name, string.Format("value [{0}] is not a valid *nix path. paths must start with /", param.Value));
throw ExceptionFactory.CreateForParamValidation(param.Name, string.Format("value [{0}] is not a valid Windows path. paths must be a full path eg. C:\\Windows", param.Value));
}
throw ExceptionFactory.CreateForParamValidation(param.Name, string.Format("value [{0}] is not a valid Windows path. paths must be a full path eg. C:\\Windows", param.Value));
throw ExceptionFactory.CreateForParamValidation(param.Name, string.Format("value [{0}] is not a valid *nix path. paths must start with /", param.Value));
}
}
}

View File

@@ -41,10 +41,10 @@ namespace NzbDrone.Common.EnvironmentInfo
TempFolder = Path.GetTempPath();
}
public string AppDataFolder { get; private set; }
public string AppDataFolder { get; }
public string StartUpFolder { get; private set; }
public string StartUpFolder { get; }
public string TempFolder { get; private set; }
public string TempFolder { get; }
}
}

View File

@@ -1,12 +1,35 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
namespace NzbDrone.Common.EnvironmentInfo
{
public static class BuildInfo
{
public static Version Version => Assembly.GetExecutingAssembly().GetName().Version;
static BuildInfo()
{
var assembly = Assembly.GetExecutingAssembly();
Version = assembly.GetName().Version;
var attributes = assembly.GetCustomAttributes(true);
Branch = "unknow";
var config = attributes.OfType<AssemblyConfigurationAttribute>().FirstOrDefault();
if (config != null)
{
Branch = config.Configuration;
}
Release = $"{Version}-{Branch}";
}
public static Version Version { get; }
public static String Branch { get; }
public static string Release { get; }
public static DateTime BuildDateTime
{

View File

@@ -0,0 +1,9 @@
namespace NzbDrone.Common.EnvironmentInfo
{
public interface IOperatingSystemVersionInfo
{
string Version { get; }
string Name { get; }
string FullName { get; }
}
}

View File

@@ -0,0 +1,9 @@
namespace NzbDrone.Common.EnvironmentInfo
{
public interface IOsVersionAdapter
{
bool Enabled { get; }
OsVersionModel Read();
}
}

View File

@@ -0,0 +1,50 @@
using System;
namespace NzbDrone.Common.EnvironmentInfo
{
public enum PlatformType
{
DotNet = 0,
Mono = 1
}
public interface IPlatformInfo
{
Version Version { get; }
}
public abstract class PlatformInfo : IPlatformInfo
{
static PlatformInfo()
{
if (Type.GetType("Mono.Runtime") != null)
{
Platform = PlatformType.Mono;
}
else
{
Platform = PlatformType.DotNet;
}
}
public static PlatformType Platform { get; }
public static bool IsMono => Platform == PlatformType.Mono;
public static bool IsDotNet => Platform == PlatformType.DotNet;
public static string PlatformName
{
get
{
if (IsDotNet)
{
return ".NET";
}
return "Mono";
}
}
public abstract Version Version { get; }
}
}

View File

@@ -1,14 +1,14 @@
namespace NzbDrone.Common.EnvironmentInfo
using System;
namespace NzbDrone.Common.EnvironmentInfo
{
public interface IRuntimeInfo
{
bool IsUserInteractive { get; }
bool IsAdmin { get; }
bool IsWindowsService { get; }
bool IsConsole { get; }
bool IsRunning { get; set; }
bool IsExiting { get; set; }
bool RestartPending { get; set; }
string ExecutingApplication { get; }
string RuntimeVersion { get; }
}
}

View File

@@ -1,87 +1,98 @@
using System;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NLog;
namespace NzbDrone.Common.EnvironmentInfo
{
public static class OsInfo
public class OsInfo : IOsInfo
{
public static Os Os { get; }
public static bool IsNotWindows => !IsWindows;
public static bool IsLinux => Os == Os.Linux;
public static bool IsOsx => Os == Os.Osx;
public static bool IsWindows => Os == Os.Windows;
public string Version { get; }
public string Name { get; }
public string FullName { get; }
static OsInfo()
{
var platform = (int)Environment.OSVersion.Platform;
var platform = Environment.OSVersion.Platform;
Version = Environment.OSVersion.Version;
IsMonoRuntime = Type.GetType("Mono.Runtime") != null;
IsNotWindows = (platform == 4) || (platform == 6) || (platform == 128);
IsOsx = IsRunningOnMac();
IsLinux = IsNotWindows && !IsOsx;
IsWindows = !IsNotWindows;
FirstDayOfWeek = CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek;
if (IsWindows)
switch (platform)
{
Os = Os.Windows;
PathStringComparison = StringComparison.OrdinalIgnoreCase;
case PlatformID.Win32NT:
{
Os = Os.Windows;
break;
}
case PlatformID.MacOSX:
case PlatformID.Unix:
{
// Sometimes Mac OS reports itself as Unix
if (Directory.Exists("/System/Library/CoreServices/") &&
(File.Exists("/System/Library/CoreServices/SystemVersion.plist") ||
File.Exists("/System/Library/CoreServices/ServerVersion.plist"))
)
{
Os = Os.Osx;
}
else
{
Os = Os.Linux;
}
break;
}
}
}
public OsInfo(IEnumerable<IOsVersionAdapter> versionAdapters, Logger logger)
{
OsVersionModel osInfo = null;
foreach (var osVersionAdapter in versionAdapters.Where(c => c.Enabled))
{
try
{
osInfo = osVersionAdapter.Read();
}
catch (Exception e)
{
logger.Error(e, "Couldn't get OS Version info");
}
if (osInfo != null)
{
break;
}
}
if (osInfo != null)
{
Name = osInfo.Name;
Version = osInfo.Version;
FullName = osInfo.FullName;
}
else
{
Os = IsOsx ? Os.Osx : Os.Linux;
PathStringComparison = StringComparison.Ordinal;
Name = Os.ToString();
FullName = Name;
}
Environment.SetEnvironmentVariable("OS_NAME", Name);
Environment.SetEnvironmentVariable("OS_VERSION", Version);
}
}
public static Version Version { get; private set; }
public static bool IsMonoRuntime { get; private set; }
public static bool IsNotWindows { get; private set; }
public static bool IsLinux { get; private set; }
public static bool IsOsx { get; private set; }
public static bool IsWindows { get; private set; }
public static Os Os { get; private set; }
public static DayOfWeek FirstDayOfWeek { get; private set; }
public static StringComparison PathStringComparison { get; private set; }
//Borrowed from: https://github.com/jpobst/Pinta/blob/master/Pinta.Core/Managers/SystemManager.cs
//From Managed.Windows.Forms/XplatUI
[DllImport("libc")]
static extern int uname(IntPtr buf);
[DebuggerStepThrough]
static bool IsRunningOnMac()
{
var buf = IntPtr.Zero;
try
{
buf = Marshal.AllocHGlobal(8192);
// This is a hacktastic way of getting sysname from uname ()
if (uname(buf) == 0)
{
var os = Marshal.PtrToStringAnsi(buf);
if (os == "Darwin")
{
return true;
}
}
}
catch
{
}
finally
{
if (buf != IntPtr.Zero)
{
Marshal.FreeHGlobal(buf);
}
}
return false;
}
public interface IOsInfo
{
string Version { get; }
string Name { get; }
string FullName { get; }
}
public enum Os

View File

@@ -0,0 +1,29 @@
namespace NzbDrone.Common.EnvironmentInfo
{
public class OsVersionModel
{
public OsVersionModel(string name, string version, string fullName = null)
{
Name = Trim(name);
Version = Trim(version);
if (string.IsNullOrWhiteSpace(fullName))
{
fullName = $"{Name} {Version}";
}
FullName = Trim(fullName);
}
private static string Trim(string source)
{
return source.Trim().Trim('"', '\'');
}
public string Name { get; }
public string FullName { get; }
public string Version { get; }
}
}

View File

@@ -5,15 +5,14 @@ using System.Reflection;
using System.Security.Principal;
using System.ServiceProcess;
using NLog;
using NzbDrone.Common.Processes;
namespace NzbDrone.Common.EnvironmentInfo
{
public abstract class RuntimeInfoBase : IRuntimeInfo
public class RuntimeInfo : IRuntimeInfo
{
private readonly Logger _logger;
public RuntimeInfoBase(IServiceProvider serviceProvider, Logger logger)
public RuntimeInfo(IServiceProvider serviceProvider, Logger logger)
{
_logger = logger;
@@ -31,7 +30,7 @@ namespace NzbDrone.Common.EnvironmentInfo
}
}
static RuntimeInfoBase()
static RuntimeInfo()
{
IsProduction = InternalIsProduction();
}
@@ -59,31 +58,18 @@ namespace NzbDrone.Common.EnvironmentInfo
public bool IsWindowsService { get; private set; }
public bool IsConsole
{
get
{
if (OsInfo.IsWindows)
{
return IsUserInteractive && Process.GetCurrentProcess().ProcessName.Equals(ProcessProvider.NZB_DRONE_CONSOLE_PROCESS_NAME, StringComparison.InvariantCultureIgnoreCase);
}
return true;
}
}
public bool IsRunning { get; set; }
public bool IsExiting { get; set; }
public bool RestartPending { get; set; }
public string ExecutingApplication { get; private set; }
public string ExecutingApplication { get; }
public abstract string RuntimeVersion { get; }
public static bool IsProduction { get; private set; }
public static bool IsProduction { get; }
private static bool InternalIsProduction()
{
if (BuildInfo.IsDebug || Debugger.IsAttached) return false;
if (BuildInfo.Version.Revision > 10000) return false; //Official builds will never have such a high revision
//Official builds will never have such a high revision
if (BuildInfo.Version.Revision > 10000) return false;
try
{
@@ -99,18 +85,19 @@ namespace NzbDrone.Common.EnvironmentInfo
}
try
{
var currentAssmeblyLocation = typeof(RuntimeInfoBase).Assembly.Location;
if(currentAssmeblyLocation.ToLower().Contains("_output"))return false;
}
catch
{
try
{
var currentAssemblyLocation = typeof(RuntimeInfo).Assembly.Location;
if (currentAssemblyLocation.ToLower().Contains("_output")) return false;
}
catch
{
}
}
string lowerCurrentDir = Directory.GetCurrentDirectory().ToLower();
var lowerCurrentDir = Directory.GetCurrentDirectory().ToLower();
if (lowerCurrentDir.Contains("teamcity")) return false;
if (lowerCurrentDir.Contains("buildagent")) return false;
if (lowerCurrentDir.Contains("_output")) return false;
return true;

View File

@@ -1,73 +0,0 @@
using System.ComponentModel;
using System.Configuration;
namespace NzbDrone.Common.Exceptron.Configuration
{
public class ExceptronConfiguration : ConfigurationSection
{
public ExceptronConfiguration()
{
Host = "http://exceptron.azurewebsites.net/api/v1/";
IncludeMachineName = true;
}
public static ExceptronConfiguration ReadConfig(string sectionName = "exceptron")
{
var configSection = ConfigurationManager.GetSection(sectionName);
if (configSection == null)
{
throw new ConfigurationErrorsException("ExceptronConfiguration section missing.");
}
return (ExceptronConfiguration)configSection;
}
/// <summary>
/// exceptron api address. Do not modify this property.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public string Host { get; set; }
/// <summary>
/// If ExceptronClinet should throw exceptions in case of an error. Default: <see cref="bool.False"/>
/// </summary>
/// <remarks>
/// Its recommended that this flag is set to True during development and <see cref="bool.False"/> in production systems.
/// If an exception is thrown while this flag is set to <see cref="bool.False"/> the thrown exception will be returned in <see cref="ExceptionResponse.Exception"/>
/// </remarks>
[ConfigurationProperty("throwExceptions", DefaultValue = false)]
public bool ThrowExceptions
{
get { return (bool)this["throwExceptions"]; }
set { this["throwExceptions"] = value; }
}
/// <summary>
/// The API of this application. Can find your API key in application settings page.
/// </summary>
[ConfigurationProperty("apiKey")]
public string ApiKey
{
get { return (string)this["apiKey"]; }
set { this["apiKey"] = value; }
}
/// <summary>
/// If the machine name should be attached to the exception report
/// </summary>
/// <remarks>Machine name can be usefull in webfarm enviroments when multiple
/// servers are running the same app and the issue could be machine specific.
/// Hoewever, You might want to disable this feature for privacy reasons.</remarks>
[ConfigurationProperty("includeMachineName", DefaultValue = true)]
public bool IncludeMachineName
{
get { return (bool)this["includeMachineName"]; }
set { this["includeMachineName"] = value; }
}
}
}

View File

@@ -1,55 +0,0 @@
using System;
namespace NzbDrone.Common.Exceptron
{
/// <summary>
/// Represents information that will be used to construct an exception report.
/// </summary>
public class ExceptionData
{
/// <summary>
/// Exception that is being reported
/// </summary>
public Exception Exception { get; set; }
/// <summary>
/// Component that experianced this exception.
/// </summary>
/// <remarks>
/// It is common to use the logger name that was used to log the exception as the component.
/// </remarks>
/// <example>
/// DataAccess, Configuration, Registration, etc.
/// </example>
public string Component { get; set; }
/// <summary>
/// ID that will uniquely identify the user
/// </summary>
/// <remarks>
/// This Id does not have to be tied to the user's identity.
/// You can use a system generated unique ID such as GUID.
/// This field is used to report how many unique users are experiencing an error.
/// </remarks>
/// <example>
/// "62E5C8EF-0CA2-43AB-B278-FC6994F776ED"
/// "Timmy@aol.com"
/// "26437"
/// </example>
public string UserId { get; set; }
/// <summary>
/// Any message that should be attached to this exceptions
/// </summary>
/// <example>
/// Something went wrong while checking for application updates.
/// </example>
public string Message { get; set; }
/// <summary>
/// Severity of the exception being reported
/// </summary>
public ExceptionSeverity Severity { get; set; }
}
}

View File

@@ -1,31 +0,0 @@
using System;
using NzbDrone.Common.EnvironmentInfo;
namespace NzbDrone.Common.Exceptron
{
public static class ExceptionExtentions
{
private const string IGNORE_FLAG = "exceptron_ignore";
public static Exception ExceptronIgnoreOnMono(this Exception exception)
{
if (OsInfo.IsNotWindows)
{
exception.ExceptronIgnore();
}
return exception;
}
public static Exception ExceptronIgnore(this Exception exception)
{
exception.Data.Add(IGNORE_FLAG, true);
return exception;
}
public static bool ExceptronShouldIgnore(this Exception exception)
{
return exception.Data.Contains(IGNORE_FLAG);
}
}
}

View File

@@ -1,28 +0,0 @@
namespace NzbDrone.Common.Exceptron
{
/// <summary>
/// Severity of the exception being reported
/// </summary>
public enum ExceptionSeverity
{
/// <summary>
/// Excepted Error. Can be ignored
/// </summary>
None = 0,
/// <summary>
/// Error that can be handled gracefully
/// </summary>
Warning = 1,
/// <summary>
/// Blocking user from completing their intended action
/// </summary>
Error = 2,
/// <summary>
/// Will most likely cause the application to crash
/// </summary>
Fatal = 3
}
}

View File

@@ -1,16 +0,0 @@
using System;
using System.Net;
namespace NzbDrone.Common.Exceptron
{
public class ExceptronApiException : Exception
{
public ExceptronApiException(WebException innerException, string message)
: base(message, innerException)
{
Response = (HttpWebResponse)innerException.Response;
}
public HttpWebResponse Response { get; private set; }
}
}

View File

@@ -1,285 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Threading;
using NzbDrone.Common.Exceptron.Configuration;
using NzbDrone.Common.Exceptron.Message;
namespace NzbDrone.Common.Exceptron
{
public class ExceptronClient : IExceptronClient
{
internal IRestClient RestClient { private get; set; }
/// <summary>
/// Version of Client
/// </summary>
public string ClientVersion => Assembly.GetExecutingAssembly().GetName().Version.ToString();
/// <summary>
/// Name of Client
/// </summary>
public string ClientName => "Official .NET";
/// <summary>
/// Client Configuration
/// </summary>
public ExceptronConfiguration Configuration { get; private set; }
/// <summary>
/// Framework Type of the Host Application (.Net/mono)
/// </summary>
public string FrameworkType { get; set; }
/// <summary>
/// Creates a new instance of <see cref="ExceptronClient"/>
/// Loads <see cref="ExceptronConfiguration"/> from application config file.
/// </summary>
/// <param name="applicationVersion">Version of the currently running application</param>
public ExceptronClient(Version applicationVersion)
: this(ExceptronConfiguration.ReadConfig(), applicationVersion)
{
FrameworkType = ".Net";
}
private readonly string _applicationVersion;
private readonly string _maxFrameworkVersion;
/// <param name="exceptronConfiguration">exceptron client configuration</param>
/// <param name="applicationVersion"> </param>
public ExceptronClient(ExceptronConfiguration exceptronConfiguration, Version applicationVersion)
{
if (exceptronConfiguration == null)
throw new ArgumentNullException("exceptronConfiguration");
if (applicationVersion == null)
throw new ArgumentNullException("applicationVersion");
if (string.IsNullOrEmpty(exceptronConfiguration.ApiKey))
throw new ArgumentException("An API Key was not provided");
Configuration = exceptronConfiguration;
RestClient = new RestClient();
_applicationVersion = applicationVersion.ToString();
_maxFrameworkVersion = GetMaximumFrameworkVersion();
FrameworkType = ".Net";
}
/// <summary>
/// Submit an exception to exceptron Servers.
/// </summary>
/// <param name="exception">Exception that is being reported</param>
/// <param name="component"
/// example="DataAccess, Configuration, Registration, etc."
/// remarks="It is common to use the logger name that was used to log the exception as the component.">Component that experienced this exception.</param>
/// <param name="severity">Severity of the exception being reported</param>
/// <param name="message"
/// example="Something went wrong while checking for application updates.">Any message that should be attached to this exceptions</param>
/// <param name="userId"
/// remarks="This Id does not have to be tied to the user's identity.
/// You can use a system generated unique ID such as GUID.
/// This field is used to report how many unique users are experiencing an error."
/// example="
/// 62E5C8EF-0CA2-43AB-B278-FC6994F776ED
/// Timmy@aol.com
/// 26437
/// ">ID that will uniquely identify the user</param>
/// <param name="httpContext"><see cref="System.Web.HttpContext"/> in which the exception occurred. If no <see cref="System.Web.HttpContext"/> is provided
/// <see cref="ExceptronClient"/> will try to get the current <see cref="System.Web.HttpContext"/> from <see cref="System.Web.HttpContext.Current"/></param>
/// <returns></returns>
public ExceptionResponse SubmitException(Exception exception, string component, ExceptionSeverity severity = ExceptionSeverity.None, string message = null, string userId = null)
{
var exceptionData = new ExceptionData
{
Exception = exception,
Component = component,
Severity = severity,
Message = message,
UserId = userId
};
return SubmitException(exceptionData);
}
/// <summary>
/// Submit an exception to exceptron Servers.
/// </summary>
/// <param name="exceptionData">Exception data to be reported to the server</param>
public ExceptionResponse SubmitException(ExceptionData exceptionData)
{
try
{
ValidateState(exceptionData);
var report = new ExceptionReport();
report.ap = Configuration.ApiKey;
report.dn = ClientName;
report.dv = ClientVersion;
report.aver = _applicationVersion;
report.ext = exceptionData.Exception.GetType().FullName;
report.stk = ConvertToFrames(exceptionData.Exception);
report.exm = exceptionData.Exception.Message;
report.cmp = exceptionData.Component;
report.uid = exceptionData.UserId;
report.msg = exceptionData.Message;
report.sv = (int)exceptionData.Severity;
report.fv = _maxFrameworkVersion;
report.ft = FrameworkType;
SetEnviromentInfo(report);
var exceptionResponse = RestClient.Put<ExceptionResponse>(Configuration.Host, report);
exceptionData.Exception.Data["et"] = exceptionResponse.RefId;
return exceptionResponse;
}
catch (Exception e)
{
Trace.WriteLine("Unable to submit exception to exceptron. ", e.ToString());
if (Configuration.ThrowExceptions)
{
//throw;
}
return new ExceptionResponse { Exception = e };
}
}
private void ValidateState(ExceptionData exceptionData)
{
if (string.IsNullOrEmpty(Configuration.ApiKey))
throw new InvalidOperationException("ApiKey has not been provided for this client.");
if (exceptionData == null)
throw new ArgumentNullException("exceptionData");
if (exceptionData.Exception == null)
throw new ArgumentException("ExceptionData.Exception Cannot be null.", "exceptionData");
}
private void SetEnviromentInfo(ExceptionReport report)
{
report.cul = Thread.CurrentThread.CurrentCulture.Name;
if (string.IsNullOrEmpty(report.cul))
report.cul = "en";
try
{
report.os = Environment.OSVersion.VersionString;
}
catch (Exception)
{
if (Configuration.ThrowExceptions) throw;
}
if (Configuration.IncludeMachineName)
{
try
{
report.hn = Environment.MachineName;
}
catch (Exception)
{
if (Configuration.ThrowExceptions) throw;
}
}
}
internal static List<Frame> ConvertToFrames(Exception exception)
{
if (exception == null) return null;
var stackTrace = new StackTrace(exception, true);
var frames = stackTrace.GetFrames();
if (frames == null) return null;
var result = new List<Frame>();
for (int index = 0; index < frames.Length; index++)
{
var frame = frames[index];
var method = frame.GetMethod();
var declaringType = method.DeclaringType;
var fileName = frame.GetFileName();
var currentFrame = new Frame
{
i = index,
fn = fileName,
ln = frame.GetFileLineNumber(),
m = method.ToString(),
};
currentFrame.m = currentFrame.m.Substring(currentFrame.m.IndexOf(' ')).Trim();
if (declaringType != null)
{
currentFrame.c = declaringType.FullName;
}
result.Add(currentFrame);
}
return result;
}
private string GetMaximumFrameworkVersion()
{
var clrVersion = Environment.Version;
if (clrVersion.Major == 2)
{
//Check if 2.0 or 3.5
try
{
Assembly.Load("System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089");
return "3.5";
}
catch (Exception)
{
}
return "2.0";
}
if (clrVersion.Major == 4)
{
//Check if 4.0 or 4.5
try
{
Assembly.Load("System.Threading.Tasks.Parallel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
return "4.5";
}
catch (Exception)
{
}
return "4.0";
}
return "Unknown";
}
}
}

View File

@@ -1,44 +0,0 @@
using System;
using NzbDrone.Common.Exceptron.Configuration;
using NzbDrone.Common.Exceptron.Message;
namespace NzbDrone.Common.Exceptron
{
public interface IExceptronClient
{
/// <summary>
/// Client Configuration
/// </summary>
ExceptronConfiguration Configuration { get; }
/// <summary>
/// Submit an exception to exceptron Servers.
/// </summary>
/// <param name="exceptionData">Exception data to be reported to the server</param>
ExceptionResponse SubmitException(ExceptionData exceptionData);
/// <summary>
/// Submit an exception to exceptron Servers.
/// </summary>
/// <param name="exception">Exception that is being reported</param>
/// <param name="component"
/// example="DataAccess, Configuration, Registration, etc."
/// remarks="It is common to use the logger name that was used to log the exception as the component.">Component that experienced this exception.</param>
/// <param name="severity">Severity of the exception being reported</param>
/// <param name="message"
/// example="Something went wrong while checking for application updates.">Any message that should be attached to this exceptions</param>
/// <param name="userId"
/// remarks="This Id does not have to be tied to the user's identity.
/// You can use a system generated unique ID such as GUID.
/// This field is used to report how many unique users are experiencing an error."
/// example="
/// 62E5C8EF-0CA2-43AB-B278-FC6994F776ED
/// Timmy@aol.com
/// 26437
/// ">ID that will uniquely identify the user</param>
/// <param name="httpContext"><see cref="System.Web.HttpContext"/> in which the exception occurred. If no <see cref="System.Web.HttpContext"/> is provided
/// <see cref="ExceptronClient"/> will try to get the current <see cref="System.Web.HttpContext"/> from <see cref="System.Web.HttpContext.Current"/></param>
/// <returns></returns>
ExceptionResponse SubmitException(Exception exception, string component, ExceptionSeverity severity = ExceptionSeverity.None, string message = null, string userId = null);
}
}

View File

@@ -1,7 +0,0 @@
namespace NzbDrone.Common.Exceptron
{
internal interface IRestClient
{
TResponse Put<TResponse>(string url, object report) where TResponse : class, new();
}
}

View File

@@ -1,111 +0,0 @@
using System.Collections.Generic;
namespace NzbDrone.Common.Exceptron.Message
{
internal class ExceptionReport
{
/// <summary>
/// API key
/// </summary>
public string ap { get; set; }
/// <summary>
/// Application Version
/// </summary>
public string aver { get; set; }
/// <summary>
/// Exception Severity
/// </summary>
public int sv { get; set; }
/// <summary>
/// User or Instance ID
/// </summary>
public string uid { get; set; }
/// <summary>
/// Type of exception
/// </summary>
public string ext { get; set; }
/// <summary>
/// Exception message
/// </summary>
public string exm { get; set; }
/// <summary>
/// List of frames that make up the StackTrace of the exception
/// </summary>
public List<Frame> stk { get; set; }
/// <summary>
/// Component that experienced this exception
/// </summary>
public string cmp { get; set; }
/// <summary>
/// Message that was logged along with the exception.
/// </summary>
public string msg { get; set; }
/// <summary>
/// User's culture in
/// </summary>
/// <remarks>http://msdn.microsoft.com/en-us/library/system.globalization.cultureinfo.name.aspx</remarks>
public string cul { get; set; }
/// <summary>
/// OS Version
/// </summary>
public string os { get; set; }
/// <summary>
/// Name of the Client that generated and is sending this message
/// </summary>
public string dn { get; set; }
/// <summary>
/// Version of the Client that generated and is sending this message
/// </summary>
public string dv { get; set; }
/// <summary>
/// Host name of the machine that encountered this exception
/// </summary>
public string hn { get; set; }
/// <summary>
/// Request url
/// <remarks>Only used for exception in context of a web request/</remarks>
public string url { get; set; }
/// <summary>
/// Browser useragent
/// </summary>
/// <remarks>Only used for exception in context of a web request/</remarks>
public string ua { get; set; }
/// <summary>
/// HTTP response status code
/// </summary>
/// <remarks>Only used for exception in context of a web request/</remarks>
public int sc { get; set; }
/// <summary>
/// Indicates the HTTP data transfer method used by the client.
/// </summary>
/// <example>GET, POST, PUT, DELETE</example>
public string hm { get; set; }
/// <summary>
/// Framework Version (CLR) of the Host Application
/// </summary>
public string fv { get; set; }
/// <summary>
/// Framework Type of the Host Application
/// </summary>
public string ft { get; set; }
}
}

View File

@@ -1,27 +0,0 @@
using System;
namespace NzbDrone.Common.Exceptron.Message
{
public class ExceptionResponse
{
/// <summary>
/// Exception report reference ID. This ID will be shared across
/// similar exceptions
/// </summary>
public string RefId { get; internal set; }
/// <summary>
/// Was the report successfully processed on the server
/// </summary>
public bool Successful => !string.IsNullOrEmpty(RefId);
/// <summary>
/// Exception that caused the message to fail.
/// </summary>
/// <remarks>
/// This property will only be populated if <see cref="ExceptronConfiguration.ThrowExceptions"/> is set to <see cref="bool.False"/>/>
/// Exception is thrown if <see cref="ExceptronConfiguration.ThrowExceptions"/> is set to <see cref="bool.True"/>.
/// </remarks>
public Exception Exception { get; internal set; }
}
}

View File

@@ -1,30 +0,0 @@
namespace NzbDrone.Common.Exceptron.Message
{
internal class Frame
{
/// <summary>
/// Order of current frame
/// </summary>
public int i { get; set; }
/// <summary>
/// Line number of the current frame
/// </summary>
public int ln { get; set; }
/// <summary>
/// Method name for current frame
/// </summary>
public string m { get; set; }
/// <summary>
/// Class name for current frame
/// </summary>
public string c { get; set; }
/// <summary>
/// File name for current frame
/// </summary>
public string fn { get; set; }
}
}

View File

@@ -1,72 +0,0 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Text;
using NzbDrone.Common.Exceptron.fastJSON;
namespace NzbDrone.Common.Exceptron
{
public sealed class RestClient : IRestClient
{
public TResponse Put<TResponse>(string url, object content) where TResponse : class ,new()
{
if (content == null)
throw new ArgumentNullException("content can not be null", "content");
if (string.IsNullOrEmpty(url))
throw new ArgumentNullException("url can not be null or empty", "url");
Trace.WriteLine("Attempting PUT to " + url);
var json = JSON.Instance.ToJSON(content);
var bytes = Encoding.UTF8.GetBytes(json);
var request = (HttpWebRequest)WebRequest.Create(url);
request.Timeout = 10000;
request.Method = "PUT";
request.ContentType = "application/json";
request.ContentLength = bytes.Length;
request.Accept = "application/json";
var dataStream = request.GetRequestStream();
dataStream.Write(bytes, 0, bytes.Length);
dataStream.Close();
string responseContent = string.Empty;
try
{
var webResponse = request.GetResponse();
responseContent = ReadResponse(webResponse);
var response = JSON.Instance.ToObject<TResponse>(responseContent);
return response;
}
catch (WebException e)
{
Trace.WriteLine(e.ToString());
responseContent = ReadResponse(e.Response);
throw new ExceptronApiException(e, responseContent);
}
finally
{
Trace.WriteLine(responseContent);
}
}
public static string ReadResponse(WebResponse webResponse)
{
if (webResponse == null) return string.Empty;
var responseStream = webResponse.GetResponseStream();
if (responseStream == null) return string.Empty;
var decodedStream = new StreamReader(responseStream, Encoding.GetEncoding(1252));
return decodedStream.ReadToEnd();
}
}
}

View File

@@ -1,21 +0,0 @@
//http://fastjson.codeplex.com/
//http://fastjson.codeplex.com/license
using System;
using System.Collections.Generic;
namespace NzbDrone.Common.Exceptron.fastJSON
{
internal class Getters
{
public string Name;
public JSON.GenericGetter Getter;
public Type propertyType;
}
internal class DatasetSchema
{
public List<string> Info { get; set; }
public string Name { get; set; }
}
}

View File

@@ -1,820 +0,0 @@
//http://fastjson.codeplex.com/
//http://fastjson.codeplex.com/license
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
using System.Xml.Serialization;
namespace NzbDrone.Common.Exceptron.fastJSON
{
internal class JSON
{
public readonly static JSON Instance = new JSON();
private JSON()
{
UseSerializerExtension = false;
SerializeNullValues = false;
UseOptimizedDatasetSchema = false;
UsingGlobalTypes = false;
}
public bool UseOptimizedDatasetSchema = true;
public bool UseFastGuid = true;
public bool UseSerializerExtension = true;
public bool IndentOutput = false;
public bool SerializeNullValues = true;
public bool UseUTCDateTime = false;
public bool ShowReadOnlyProperties = false;
public bool UsingGlobalTypes = true;
public string ToJSON(object obj)
{
return ToJSON(obj, UseSerializerExtension, UseFastGuid, UseOptimizedDatasetSchema, SerializeNullValues);
}
public string ToJSON(object obj,
bool enableSerializerExtensions,
bool enableFastGuid,
bool enableOptimizedDatasetSchema,
bool serializeNullValues)
{
return new JSONSerializer(enableOptimizedDatasetSchema, enableFastGuid, enableSerializerExtensions, serializeNullValues, IndentOutput).ConvertToJSON(obj);
}
public T ToObject<T>(string json)
{
return (T)ToObject(json, typeof(T));
}
public object ToObject(string json, Type type)
{
var ht = new JsonParser(json).Decode() as Dictionary<string, object>;
if (ht == null) return null;
return ParseDictionary(ht, null, type);
}
#if CUSTOMTYPE
internal SafeDictionary<Type, Serialize> _customSerializer = new SafeDictionary<Type, Serialize>();
internal SafeDictionary<Type, Deserialize> _customDeserializer = new SafeDictionary<Type, Deserialize>();
public void RegisterCustomType(Type type, Serialize serializer, Deserialize deserializer)
{
if (type != null && serializer != null && deserializer != null)
{
_customSerializer.Add(type, serializer);
_customDeserializer.Add(type, deserializer);
// reset property cache
_propertycache = new SafeDictionary<string, SafeDictionary<string, myPropInfo>>();
}
}
internal bool IsTypeRegistered(Type t)
{
Serialize s;
return _customSerializer.TryGetValue(t, out s);
}
#endif
#region [ PROPERTY GET SET CACHE ]
readonly SafeDictionary<Type, string> _tyname = new SafeDictionary<Type, string>();
internal string GetTypeAssemblyName(Type t)
{
string val = "";
if (_tyname.TryGetValue(t, out val))
return val;
string s = t.AssemblyQualifiedName;
_tyname.Add(t, s);
return s;
}
readonly SafeDictionary<string, Type> _typecache = new SafeDictionary<string, Type>();
private Type GetTypeFromCache(string typename)
{
Type val = null;
if (_typecache.TryGetValue(typename, out val))
return val;
Type t = Type.GetType(typename);
_typecache.Add(typename, t);
return t;
}
readonly SafeDictionary<Type, CreateObject> _constrcache = new SafeDictionary<Type, CreateObject>();
private delegate object CreateObject();
private object FastCreateInstance(Type objtype)
{
try
{
CreateObject c = null;
if (_constrcache.TryGetValue(objtype, out c))
{
return c();
}
DynamicMethod dynMethod = new DynamicMethod("_", objtype, null, true);
ILGenerator ilGen = dynMethod.GetILGenerator();
ilGen.Emit(OpCodes.Newobj, objtype.GetConstructor(Type.EmptyTypes));
ilGen.Emit(OpCodes.Ret);
c = (CreateObject)dynMethod.CreateDelegate(typeof(CreateObject));
_constrcache.Add(objtype, c);
return c();
}
catch (Exception exc)
{
throw new Exception(string.Format("Failed to fast create instance for type '{0}' from assemebly '{1}'",
objtype.FullName, objtype.AssemblyQualifiedName), exc);
}
}
private struct myPropInfo
{
public bool filled;
public Type pt;
public Type bt;
public Type changeType;
public bool isDictionary;
public bool isValueType;
public bool isGenericType;
public bool isArray;
public bool isByteArray;
public bool isGuid;
#if !SILVERLIGHT
public bool isDataSet;
public bool isDataTable;
public bool isHashtable;
#endif
public GenericSetter setter;
public bool isEnum;
public bool isDateTime;
public Type[] GenericTypes;
public bool isInt;
public bool isLong;
public bool isString;
public bool isBool;
public bool isClass;
public GenericGetter getter;
public bool isStringDictionary;
public string Name;
#if CUSTOMTYPE
public bool isCustomType;
#endif
public bool CanWrite;
}
readonly SafeDictionary<string, SafeDictionary<string, myPropInfo>> _propertycache = new SafeDictionary<string, SafeDictionary<string, myPropInfo>>();
private SafeDictionary<string, myPropInfo> Getproperties(Type type, string typename)
{
SafeDictionary<string, myPropInfo> sd = null;
if (_propertycache.TryGetValue(typename, out sd))
{
return sd;
}
sd = new SafeDictionary<string, myPropInfo>();
var pr = type.GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
foreach (var p in pr)
{
myPropInfo d = CreateMyProp(p.PropertyType, p.Name);
d.CanWrite = p.CanWrite;
d.setter = CreateSetMethod(p);
d.getter = CreateGetMethod(p);
sd.Add(p.Name, d);
}
_propertycache.Add(typename, sd);
return sd;
}
private myPropInfo CreateMyProp(Type t, string name)
{
myPropInfo d = new myPropInfo();
d.filled = true;
d.CanWrite = true;
d.pt = t;
d.Name = name;
d.isDictionary = t.Name.Contains("Dictionary");
if (d.isDictionary)
d.GenericTypes = t.GetGenericArguments();
d.isValueType = t.IsValueType;
d.isGenericType = t.IsGenericType;
d.isArray = t.IsArray;
if (d.isArray)
d.bt = t.GetElementType();
if (d.isGenericType)
d.bt = t.GetGenericArguments()[0];
d.isByteArray = t == typeof(byte[]);
d.isGuid = (t == typeof(Guid) || t == typeof(Guid?));
#if !SILVERLIGHT
d.isHashtable = t == typeof(Hashtable);
d.isDataSet = t == typeof(DataSet);
d.isDataTable = t == typeof(DataTable);
#endif
d.changeType = GetChangeType(t);
d.isEnum = t.IsEnum;
d.isDateTime = t == typeof(DateTime) || t == typeof(DateTime?);
d.isInt = t == typeof(int) || t == typeof(int?);
d.isLong = t == typeof(long) || t == typeof(long?);
d.isString = t == typeof(string);
d.isBool = t == typeof(bool) || t == typeof(bool?);
d.isClass = t.IsClass;
if (d.isDictionary && d.GenericTypes.Length > 0 && d.GenericTypes[0] == typeof(string))
d.isStringDictionary = true;
#if CUSTOMTYPE
if (IsTypeRegistered(t))
d.isCustomType = true;
#endif
return d;
}
private delegate void GenericSetter(object target, object value);
private static GenericSetter CreateSetMethod(PropertyInfo propertyInfo)
{
MethodInfo setMethod = propertyInfo.GetSetMethod(nonPublic: true);
if (setMethod == null)
return null;
var arguments = new Type[2];
arguments[0] = arguments[1] = typeof(object);
DynamicMethod setter = new DynamicMethod("_", typeof(void), arguments, true);
ILGenerator il = setter.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Castclass, propertyInfo.DeclaringType);
il.Emit(OpCodes.Ldarg_1);
if (propertyInfo.PropertyType.IsClass)
il.Emit(OpCodes.Castclass, propertyInfo.PropertyType);
else
il.Emit(OpCodes.Unbox_Any, propertyInfo.PropertyType);
il.EmitCall(OpCodes.Callvirt, setMethod, null);
il.Emit(OpCodes.Ret);
return (GenericSetter)setter.CreateDelegate(typeof(GenericSetter));
}
internal delegate object GenericGetter(object obj);
private GenericGetter CreateGetMethod(PropertyInfo propertyInfo)
{
MethodInfo getMethod = propertyInfo.GetGetMethod();
if (getMethod == null)
return null;
var arguments = new Type[1];
arguments[0] = typeof(object);
DynamicMethod getter = new DynamicMethod("_", typeof(object), arguments, true);
ILGenerator il = getter.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Castclass, propertyInfo.DeclaringType);
il.EmitCall(OpCodes.Callvirt, getMethod, null);
if (!propertyInfo.PropertyType.IsClass)
il.Emit(OpCodes.Box, propertyInfo.PropertyType);
il.Emit(OpCodes.Ret);
return (GenericGetter)getter.CreateDelegate(typeof(GenericGetter));
}
readonly SafeDictionary<Type, List<Getters>> _getterscache = new SafeDictionary<Type, List<Getters>>();
internal List<Getters> GetGetters(Type type)
{
List<Getters> val = null;
if (_getterscache.TryGetValue(type, out val))
return val;
var props = type.GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
var getters = new List<Getters>();
foreach (var p in props)
{
if (!p.CanWrite && ShowReadOnlyProperties == false) continue;
var att = p.GetCustomAttributes(typeof(XmlIgnoreAttribute), false);
if (att != null && att.Length > 0)
continue;
GenericGetter g = CreateGetMethod(p);
if (g != null)
{
Getters gg = new Getters();
gg.Name = p.Name;
gg.Getter = g;
gg.propertyType = p.PropertyType;
getters.Add(gg);
}
}
_getterscache.Add(type, getters);
return getters;
}
private object ChangeType(object value, Type conversionType)
{
if (conversionType == typeof(int))
return (int)CreateLong((string)value);
if (conversionType == typeof(long))
return CreateLong((string)value);
if (conversionType == typeof(string))
return value;
if (conversionType == typeof(Guid))
return CreateGuid((string)value);
if (conversionType.IsEnum)
return CreateEnum(conversionType, (string)value);
return Convert.ChangeType(value, conversionType, CultureInfo.InvariantCulture);
}
#endregion
private object ParseDictionary(Dictionary<string, object> d, Dictionary<string, object> globaltypes, Type type)
{
object tn = "";
if (d.TryGetValue("$types", out tn))
{
UsingGlobalTypes = true;
globaltypes = new Dictionary<string, object>();
foreach (var kv in (Dictionary<string, object>)tn)
{
globaltypes.Add((string)kv.Value, kv.Key);
}
}
bool found = d.TryGetValue("$type", out tn);
#if !SILVERLIGHT
if (found == false && type == typeof(object))
{
return CreateDataset(d, globaltypes);
}
#endif
if (found)
{
if (UsingGlobalTypes)
{
object tname = "";
if (globaltypes.TryGetValue((string)tn, out tname))
tn = tname;
}
type = GetTypeFromCache((string)tn);
}
if (type == null)
throw new Exception("Cannot determine type");
string typename = type.FullName;
object o = FastCreateInstance(type);
var props = Getproperties(type, typename);
foreach (var name in d.Keys)
{
if (name == "$map")
{
ProcessMap(o, props, (Dictionary<string, object>)d[name]);
continue;
}
myPropInfo pi;
if (props.TryGetValue(name, out pi) == false)
continue;
if (pi.filled)
{
object v = d[name];
if (v != null)
{
object oset = null;
if (pi.isInt)
oset = (int)CreateLong((string)v);
#if CUSTOMTYPE
else if (pi.isCustomType)
oset = CreateCustom((string)v, pi.pt);
#endif
else if (pi.isLong)
oset = CreateLong((string)v);
else if (pi.isString)
oset = v;
else if (pi.isBool)
oset = (bool)v;
else if (pi.isGenericType && pi.isValueType == false && pi.isDictionary == false)
#if SILVERLIGHT
oset = CreateGenericList((List<object>)v, pi.pt, pi.bt, globaltypes);
#else
oset = CreateGenericList((ArrayList)v, pi.pt, pi.bt, globaltypes);
#endif
else if (pi.isByteArray)
oset = Convert.FromBase64String((string)v);
else if (pi.isArray && pi.isValueType == false)
#if SILVERLIGHT
oset = CreateArray((List<object>)v, pi.pt, pi.bt, globaltypes);
#else
oset = CreateArray((ArrayList)v, pi.pt, pi.bt, globaltypes);
#endif
else if (pi.isGuid)
oset = CreateGuid((string)v);
#if !SILVERLIGHT
else if (pi.isDataSet)
oset = CreateDataset((Dictionary<string, object>)v, globaltypes);
else if (pi.isDataTable)
oset = CreateDataTable((Dictionary<string, object>)v, globaltypes);
#endif
else if (pi.isStringDictionary)
oset = CreateStringKeyDictionary((Dictionary<string, object>)v, pi.pt, pi.GenericTypes, globaltypes);
#if !SILVERLIGHT
else if (pi.isDictionary || pi.isHashtable)
oset = CreateDictionary((ArrayList)v, pi.pt, pi.GenericTypes, globaltypes);
#else
else if (pi.isDictionary)
oset = CreateDictionary((List<object>)v, pi.pt, pi.GenericTypes, globaltypes);
#endif
else if (pi.isEnum)
oset = CreateEnum(pi.pt, (string)v);
else if (pi.isDateTime)
oset = CreateDateTime((string)v);
else if (pi.isClass && v is Dictionary<string, object>)
oset = ParseDictionary((Dictionary<string, object>)v, globaltypes, pi.pt);
else if (pi.isValueType)
oset = ChangeType(v, pi.changeType);
#if SILVERLIGHT
else if (v is List<object>)
oset = CreateArray((List<object>)v, pi.pt, typeof(object), globaltypes);
#else
else if (v is ArrayList)
oset = CreateArray((ArrayList)v, pi.pt, typeof(object), globaltypes);
#endif
else
oset = v;
if (pi.CanWrite)
pi.setter(o, oset);
}
}
}
return o;
}
#if CUSTOMTYPE
private object CreateCustom(string v, Type type)
{
Deserialize d;
_customDeserializer.TryGetValue(type, out d);
return d(v);
}
#endif
private void ProcessMap(object obj, SafeDictionary<string, myPropInfo> props, Dictionary<string, object> dic)
{
foreach (var kv in dic)
{
myPropInfo p = props[kv.Key];
object o = p.getter(obj);
Type t = Type.GetType((string)kv.Value);
if (t == typeof(Guid))
p.setter(obj, CreateGuid((string)o));
}
}
private long CreateLong(string s)
{
long num = 0;
bool neg = false;
foreach (var cc in s)
{
if (cc == '-')
neg = true;
else if (cc == '+')
neg = false;
else
{
num *= 10;
num += (cc - '0');
}
}
return neg ? -num : num;
}
private object CreateEnum(Type pt, string v)
{
// TODO : optimize create enum
#if !SILVERLIGHT
return Enum.Parse(pt, v);
#else
return Enum.Parse(pt, v, true);
#endif
}
private Guid CreateGuid(string s)
{
if (s.Length > 30)
return new Guid(s);
return new Guid(Convert.FromBase64String(s));
}
private DateTime CreateDateTime(string value)
{
bool utc = false;
// 0123456789012345678
// datetime format = yyyy-MM-dd HH:mm:ss
int year = (int)CreateLong(value.Substring(0, 4));
int month = (int)CreateLong(value.Substring(5, 2));
int day = (int)CreateLong(value.Substring(8, 2));
int hour = (int)CreateLong(value.Substring(11, 2));
int min = (int)CreateLong(value.Substring(14, 2));
int sec = (int)CreateLong(value.Substring(17, 2));
if (value.EndsWith("Z"))
utc = true;
if (UseUTCDateTime == false && utc == false)
return new DateTime(year, month, day, hour, min, sec);
return new DateTime(year, month, day, hour, min, sec, DateTimeKind.Utc).ToLocalTime();
}
#if SILVERLIGHT
private object CreateArray(List<object> data, Type pt, Type bt, Dictionary<string, object> globalTypes)
{
Array col = Array.CreateInstance(bt, data.Count);
// create an array of objects
for (int i = 0; i < data.Count; i++)// each (object ob in data)
{
object ob = data[i];
if (ob is IDictionary)
col.SetValue(ParseDictionary((Dictionary<string, object>)ob, globalTypes, bt), i);
else
col.SetValue(ChangeType(ob, bt), i);
}
return col;
}
#else
private object CreateArray(ArrayList data, Type pt, Type bt, Dictionary<string, object> globalTypes)
{
ArrayList col = new ArrayList();
// create an array of objects
foreach (var ob in data)
{
if (ob is IDictionary)
col.Add(ParseDictionary((Dictionary<string, object>)ob, globalTypes, bt));
else
col.Add(ChangeType(ob, bt));
}
return col.ToArray(bt);
}
#endif
#if SILVERLIGHT
private object CreateGenericList(List<object> data, Type pt, Type bt, Dictionary<string, object> globalTypes)
#else
private object CreateGenericList(ArrayList data, Type pt, Type bt, Dictionary<string, object> globalTypes)
#endif
{
IList col = (IList)FastCreateInstance(pt);
// create an array of objects
foreach (var ob in data)
{
if (ob is IDictionary)
col.Add(ParseDictionary((Dictionary<string, object>)ob, globalTypes, bt));
#if SILVERLIGHT
else if (ob is List<object>)
col.Add(((List<object>)ob).ToArray());
#else
else if (ob is ArrayList)
col.Add(((ArrayList)ob).ToArray());
#endif
else
col.Add(ChangeType(ob, bt));
}
return col;
}
private object CreateStringKeyDictionary(Dictionary<string, object> reader, Type pt, Type[] types, Dictionary<string, object> globalTypes)
{
var col = (IDictionary)FastCreateInstance(pt);
Type t1 = null;
Type t2 = null;
if (types != null)
{
t1 = types[0];
t2 = types[1];
}
foreach (var values in reader)
{
var key = values.Key;//ChangeType(values.Key, t1);
object val = null;
if (values.Value is Dictionary<string, object>)
val = ParseDictionary((Dictionary<string, object>)values.Value, globalTypes, t2);
else
val = ChangeType(values.Value, t2);
col.Add(key, val);
}
return col;
}
#if SILVERLIGHT
private object CreateDictionary(List<object> reader, Type pt, Type[] types, Dictionary<string, object> globalTypes)
#else
private object CreateDictionary(ArrayList reader, Type pt, Type[] types, Dictionary<string, object> globalTypes)
#endif
{
IDictionary col = (IDictionary)FastCreateInstance(pt);
Type t1 = null;
Type t2 = null;
if (types != null)
{
t1 = types[0];
t2 = types[1];
}
foreach (Dictionary<string, object> values in reader)
{
object key = values["k"];
object val = values["v"];
if (key is Dictionary<string, object>)
key = ParseDictionary((Dictionary<string, object>)key, globalTypes, t1);
else
key = ChangeType(key, t1);
if (val is Dictionary<string, object>)
val = ParseDictionary((Dictionary<string, object>)val, globalTypes, t2);
else
val = ChangeType(val, t2);
col.Add(key, val);
}
return col;
}
private Type GetChangeType(Type conversionType)
{
if (conversionType.IsGenericType && conversionType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
return conversionType.GetGenericArguments()[0];
return conversionType;
}
#if !SILVERLIGHT
private DataSet CreateDataset(Dictionary<string, object> reader, Dictionary<string, object> globalTypes)
{
DataSet ds = new DataSet();
ds.EnforceConstraints = false;
ds.BeginInit();
// read dataset schema here
ReadSchema(reader, ds, globalTypes);
foreach (var pair in reader)
{
if (pair.Key == "$type" || pair.Key == "$schema") continue;
ArrayList rows = (ArrayList)pair.Value;
if (rows == null) continue;
DataTable dt = ds.Tables[pair.Key];
ReadDataTable(rows, dt);
}
ds.EndInit();
return ds;
}
private void ReadSchema(Dictionary<string, object> reader, DataSet ds, Dictionary<string, object> globalTypes)
{
var schema = reader["$schema"];
if (schema is string)
{
TextReader tr = new StringReader((string)schema);
ds.ReadXmlSchema(tr);
}
else
{
DatasetSchema ms = (DatasetSchema)ParseDictionary((Dictionary<string, object>)schema, globalTypes, typeof(DatasetSchema));
ds.DataSetName = ms.Name;
for (int i = 0; i < ms.Info.Count; i += 3)
{
if (ds.Tables.Contains(ms.Info[i]) == false)
ds.Tables.Add(ms.Info[i]);
ds.Tables[ms.Info[i]].Columns.Add(ms.Info[i + 1], Type.GetType(ms.Info[i + 2]));
}
}
}
private void ReadDataTable(ArrayList rows, DataTable dt)
{
dt.BeginInit();
dt.BeginLoadData();
var guidcols = new List<int>();
var datecol = new List<int>();
foreach (DataColumn c in dt.Columns)
{
if (c.DataType == typeof(Guid) || c.DataType == typeof(Guid?))
guidcols.Add(c.Ordinal);
if (UseUTCDateTime && (c.DataType == typeof(DateTime) || c.DataType == typeof(DateTime?)))
datecol.Add(c.Ordinal);
}
foreach (ArrayList row in rows)
{
var v = new object[row.Count];
row.CopyTo(v, 0);
foreach (var i in guidcols)
{
string s = (string)v[i];
if (s != null && s.Length < 36)
v[i] = new Guid(Convert.FromBase64String(s));
}
if (UseUTCDateTime)
{
foreach (var i in datecol)
{
string s = (string)v[i];
if (s != null)
v[i] = CreateDateTime(s);
}
}
dt.Rows.Add(v);
}
dt.EndLoadData();
dt.EndInit();
}
DataTable CreateDataTable(Dictionary<string, object> reader, Dictionary<string, object> globalTypes)
{
var dt = new DataTable();
// read dataset schema here
var schema = reader["$schema"];
if (schema is string)
{
TextReader tr = new StringReader((string)schema);
dt.ReadXmlSchema(tr);
}
else
{
var ms = (DatasetSchema)ParseDictionary((Dictionary<string, object>)schema, globalTypes, typeof(DatasetSchema));
dt.TableName = ms.Info[0];
for (int i = 0; i < ms.Info.Count; i += 3)
{
dt.Columns.Add(ms.Info[i + 1], Type.GetType(ms.Info[i + 2]));
}
}
foreach (var pair in reader)
{
if (pair.Key == "$type" || pair.Key == "$schema")
continue;
var rows = (ArrayList)pair.Value;
if (rows == null)
continue;
if (!dt.TableName.Equals(pair.Key, StringComparison.InvariantCultureIgnoreCase))
continue;
ReadDataTable(rows, dt);
}
return dt;
}
#endif
}
}

View File

@@ -1,409 +0,0 @@
//http://fastjson.codeplex.com/
//http://fastjson.codeplex.com/license
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
namespace NzbDrone.Common.Exceptron.fastJSON
{
/// <summary>
/// This class encodes and decodes JSON strings.
/// Spec. details, see http://www.json.org/
///
/// JSON uses Arrays and Objects. These correspond here to the datatypes ArrayList and Hashtable.
/// All numbers are parsed to doubles.
/// </summary>
internal class JsonParser
{
enum Token
{
None = -1, // Used to denote no Lookahead available
Curly_Open,
Curly_Close,
Squared_Open,
Squared_Close,
Colon,
Comma,
String,
Number,
True,
False,
Null
}
readonly char[] json;
readonly StringBuilder s = new StringBuilder();
Token lookAheadToken = Token.None;
int index;
internal JsonParser(string json)
{
this.json = json.ToCharArray();
}
public object Decode()
{
return ParseValue();
}
private Dictionary<string, object> ParseObject()
{
var table = new Dictionary<string, object>();
ConsumeToken(); // {
while (true)
{
switch (LookAhead())
{
case Token.Comma:
ConsumeToken();
break;
case Token.Curly_Close:
ConsumeToken();
return table;
default:
{
// name
string name = ParseString();
// :
if (NextToken() != Token.Colon)
{
throw new Exception("Expected colon at index " + index);
}
// value
object value = ParseValue();
table[name] = value;
}
break;
}
}
}
#if SILVERLIGHT
private List<object> ParseArray()
{
List<object> array = new List<object>();
#else
private ArrayList ParseArray()
{
ArrayList array = new ArrayList();
#endif
ConsumeToken(); // [
while (true)
{
switch (LookAhead())
{
case Token.Comma:
ConsumeToken();
break;
case Token.Squared_Close:
ConsumeToken();
return array;
default:
{
array.Add(ParseValue());
}
break;
}
}
}
private object ParseValue()
{
switch (LookAhead())
{
case Token.Number:
return ParseNumber();
case Token.String:
return ParseString();
case Token.Curly_Open:
return ParseObject();
case Token.Squared_Open:
return ParseArray();
case Token.True:
ConsumeToken();
return true;
case Token.False:
ConsumeToken();
return false;
case Token.Null:
ConsumeToken();
return null;
}
throw new Exception("Unrecognized token at index" + index);
}
private string ParseString()
{
ConsumeToken(); // "
s.Length = 0;
int runIndex = -1;
while (index < json.Length)
{
var c = json[index++];
if (c == '"')
{
if (runIndex != -1)
{
if (s.Length == 0)
return new string(json, runIndex, index - runIndex - 1);
s.Append(json, runIndex, index - runIndex - 1);
}
return s.ToString();
}
if (c != '\\')
{
if (runIndex == -1)
runIndex = index - 1;
continue;
}
if (index == json.Length) break;
if (runIndex != -1)
{
s.Append(json, runIndex, index - runIndex - 1);
runIndex = -1;
}
switch (json[index++])
{
case '"':
s.Append('"');
break;
case '\\':
s.Append('\\');
break;
case '/':
s.Append('/');
break;
case 'b':
s.Append('\b');
break;
case 'f':
s.Append('\f');
break;
case 'n':
s.Append('\n');
break;
case 'r':
s.Append('\r');
break;
case 't':
s.Append('\t');
break;
case 'u':
{
int remainingLength = json.Length - index;
if (remainingLength < 4) break;
// parse the 32 bit hex into an integer codepoint
uint codePoint = ParseUnicode(json[index], json[index + 1], json[index + 2], json[index + 3]);
s.Append((char)codePoint);
// skip 4 chars
index += 4;
}
break;
}
}
throw new Exception("Unexpectedly reached end of string");
}
private uint ParseSingleChar(char c1, uint multipliyer)
{
uint p1 = 0;
if (c1 >= '0' && c1 <= '9')
p1 = (uint)(c1 - '0') * multipliyer;
else if (c1 >= 'A' && c1 <= 'F')
p1 = (uint)((c1 - 'A') + 10) * multipliyer;
else if (c1 >= 'a' && c1 <= 'f')
p1 = (uint)((c1 - 'a') + 10) * multipliyer;
return p1;
}
private uint ParseUnicode(char c1, char c2, char c3, char c4)
{
uint p1 = ParseSingleChar(c1, 0x1000);
uint p2 = ParseSingleChar(c2, 0x100);
uint p3 = ParseSingleChar(c3, 0x10);
uint p4 = ParseSingleChar(c4, 1);
return p1 + p2 + p3 + p4;
}
private string ParseNumber()
{
ConsumeToken();
// Need to start back one place because the first digit is also a token and would have been consumed
var startIndex = index - 1;
do
{
var c = json[index];
if ((c >= '0' && c <= '9') || c == '.' || c == '-' || c == '+' || c == 'e' || c == 'E')
{
if (++index == json.Length) throw new Exception("Unexpected end of string whilst parsing number");
continue;
}
break;
} while (true);
return new string(json, startIndex, index - startIndex);
}
private Token LookAhead()
{
if (lookAheadToken != Token.None) return lookAheadToken;
return lookAheadToken = NextTokenCore();
}
private void ConsumeToken()
{
lookAheadToken = Token.None;
}
private Token NextToken()
{
var result = lookAheadToken != Token.None ? lookAheadToken : NextTokenCore();
lookAheadToken = Token.None;
return result;
}
private Token NextTokenCore()
{
char c;
// Skip past whitespace
do
{
c = json[index];
if (c > ' ') break;
if (c != ' ' && c != '\t' && c != '\n' && c != '\r') break;
} while (++index < json.Length);
if (index == json.Length)
{
throw new Exception("Reached end of string unexpectedly");
}
c = json[index];
index++;
//if (c >= '0' && c <= '9')
// return Token.Number;
switch (c)
{
case '{':
return Token.Curly_Open;
case '}':
return Token.Curly_Close;
case '[':
return Token.Squared_Open;
case ']':
return Token.Squared_Close;
case ',':
return Token.Comma;
case '"':
return Token.String;
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
case '-': case '+': case '.':
return Token.Number;
case ':':
return Token.Colon;
case 'f':
if (json.Length - index >= 4 &&
json[index + 0] == 'a' &&
json[index + 1] == 'l' &&
json[index + 2] == 's' &&
json[index + 3] == 'e')
{
index += 4;
return Token.False;
}
break;
case 't':
if (json.Length - index >= 3 &&
json[index + 0] == 'r' &&
json[index + 1] == 'u' &&
json[index + 2] == 'e')
{
index += 3;
return Token.True;
}
break;
case 'n':
if (json.Length - index >= 3 &&
json[index + 0] == 'u' &&
json[index + 1] == 'l' &&
json[index + 2] == 'l')
{
index += 3;
return Token.Null;
}
break;
}
throw new Exception("Could not find token at index " + --index);
}
}
}

View File

@@ -1,519 +0,0 @@
//http://fastjson.codeplex.com/
//http://fastjson.codeplex.com/license
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.IO;
using System.Text;
namespace NzbDrone.Common.Exceptron.fastJSON
{
internal class JSONSerializer
{
private readonly StringBuilder _output = new StringBuilder();
readonly bool useMinimalDataSetSchema;
readonly bool fastguid = true;
readonly bool useExtension = true;
readonly bool serializeNulls = true;
readonly int _MAX_DEPTH = 10;
readonly bool _Indent;
readonly bool _useGlobalTypes = true;
int _current_depth;
private readonly Dictionary<string, int> _globalTypes = new Dictionary<string, int>();
internal JSONSerializer(bool UseMinimalDataSetSchema, bool UseFastGuid, bool UseExtensions, bool SerializeNulls, bool IndentOutput)
{
useMinimalDataSetSchema = UseMinimalDataSetSchema;
fastguid = UseFastGuid;
useExtension = UseExtensions;
_Indent = IndentOutput;
serializeNulls = SerializeNulls;
if (useExtension == false)
_useGlobalTypes = false;
}
internal string ConvertToJSON(object obj)
{
WriteValue(obj);
string str = "";
if (_useGlobalTypes)
{
StringBuilder sb = new StringBuilder();
sb.Append("{\"$types\":{");
bool pendingSeparator = false;
foreach (var kv in _globalTypes)
{
if (pendingSeparator) sb.Append(',');
pendingSeparator = true;
sb.Append("\"");
sb.Append(kv.Key);
sb.Append("\":\"");
sb.Append(kv.Value);
sb.Append("\"");
}
sb.Append("},");
str = sb + _output.ToString();
}
else
str = _output.ToString();
return str;
}
private void WriteValue(object obj)
{
if (obj == null || obj is DBNull)
_output.Append("null");
else if (obj is string || obj is char)
WriteString((string)obj);
else if (obj is Guid)
WriteGuid((Guid)obj);
else if (obj is bool)
_output.Append(((bool)obj) ? "true" : "false"); // conform to standard
else if (
obj is int || obj is long || obj is double ||
obj is decimal || obj is float ||
obj is byte || obj is short ||
obj is sbyte || obj is ushort ||
obj is uint || obj is ulong
)
_output.Append(((IConvertible)obj).ToString(NumberFormatInfo.InvariantInfo));
else if (obj is DateTime)
WriteDateTime((DateTime)obj);
else if (obj is IDictionary && obj.GetType().IsGenericType && obj.GetType().GetGenericArguments()[0] == typeof(string))
WriteStringDictionary((IDictionary)obj);
else if (obj is IDictionary)
WriteDictionary((IDictionary)obj);
#if !SILVERLIGHT
else if (obj is DataSet)
WriteDataset((DataSet)obj);
else if (obj is DataTable)
WriteDataTable((DataTable)obj);
#endif
else if (obj is byte[])
WriteBytes((byte[])obj);
else if (obj is Array || obj is IList || obj is ICollection)
WriteArray((IEnumerable)obj);
else if (obj is Enum)
WriteEnum((Enum)obj);
#if CUSTOMTYPE
else if (JSON.Instance.IsTypeRegistered(obj.GetType()))
WriteCustom(obj);
#endif
else
WriteObject(obj);
}
#if CUSTOMTYPE
private void WriteCustom(object obj)
{
Serialize s;
JSON.Instance._customSerializer.TryGetValue(obj.GetType(), out s);
WriteStringFast(s(obj));
}
#endif
private void WriteEnum(Enum e)
{
// TODO : optimize enum write
WriteStringFast(e.ToString());
}
private void WriteGuid(Guid g)
{
if (fastguid == false)
WriteStringFast(g.ToString());
else
WriteBytes(g.ToByteArray());
}
private void WriteBytes(byte[] bytes)
{
#if !SILVERLIGHT
WriteStringFast(Convert.ToBase64String(bytes, 0, bytes.Length, Base64FormattingOptions.None));
#else
WriteStringFast(Convert.ToBase64String(bytes, 0, bytes.Length));
#endif
}
private void WriteDateTime(DateTime dateTime)
{
// datetime format standard : yyyy-MM-dd HH:mm:ss
DateTime dt = dateTime;
if (JSON.Instance.UseUTCDateTime)
dt = dateTime.ToUniversalTime();
_output.Append("\"");
_output.Append(dt.Year.ToString("0000", NumberFormatInfo.InvariantInfo));
_output.Append("-");
_output.Append(dt.Month.ToString("00", NumberFormatInfo.InvariantInfo));
_output.Append("-");
_output.Append(dt.Day.ToString("00", NumberFormatInfo.InvariantInfo));
_output.Append(" ");
_output.Append(dt.Hour.ToString("00", NumberFormatInfo.InvariantInfo));
_output.Append(":");
_output.Append(dt.Minute.ToString("00", NumberFormatInfo.InvariantInfo));
_output.Append(":");
_output.Append(dt.Second.ToString("00", NumberFormatInfo.InvariantInfo));
if (JSON.Instance.UseUTCDateTime)
_output.Append("Z");
_output.Append("\"");
}
#if !SILVERLIGHT
private DatasetSchema GetSchema(DataTable ds)
{
if (ds == null) return null;
DatasetSchema m = new DatasetSchema();
m.Info = new List<string>();
m.Name = ds.TableName;
foreach (DataColumn c in ds.Columns)
{
m.Info.Add(ds.TableName);
m.Info.Add(c.ColumnName);
m.Info.Add(c.DataType.ToString());
}
// TODO : serialize relations and constraints here
return m;
}
private DatasetSchema GetSchema(DataSet ds)
{
if (ds == null) return null;
DatasetSchema m = new DatasetSchema();
m.Info = new List<string>();
m.Name = ds.DataSetName;
foreach (DataTable t in ds.Tables)
{
foreach (DataColumn c in t.Columns)
{
m.Info.Add(t.TableName);
m.Info.Add(c.ColumnName);
m.Info.Add(c.DataType.ToString());
}
}
// TODO : serialize relations and constraints here
return m;
}
private string GetXmlSchema(DataTable dt)
{
using (var writer = new StringWriter())
{
dt.WriteXmlSchema(writer);
return dt.ToString();
}
}
private void WriteDataset(DataSet ds)
{
_output.Append('{');
if (useExtension)
{
WritePair("$schema", useMinimalDataSetSchema ? (object)GetSchema(ds) : ds.GetXmlSchema());
_output.Append(',');
}
bool tablesep = false;
foreach (DataTable table in ds.Tables)
{
if (tablesep) _output.Append(",");
tablesep = true;
WriteDataTableData(table);
}
// end dataset
_output.Append('}');
}
private void WriteDataTableData(DataTable table)
{
_output.Append('\"');
_output.Append(table.TableName);
_output.Append("\":[");
DataColumnCollection cols = table.Columns;
bool rowseparator = false;
foreach (DataRow row in table.Rows)
{
if (rowseparator) _output.Append(",");
rowseparator = true;
_output.Append('[');
bool pendingSeperator = false;
foreach (DataColumn column in cols)
{
if (pendingSeperator) _output.Append(',');
WriteValue(row[column]);
pendingSeperator = true;
}
_output.Append(']');
}
_output.Append(']');
}
void WriteDataTable(DataTable dt)
{
_output.Append('{');
if (useExtension)
{
WritePair("$schema", useMinimalDataSetSchema ? (object)GetSchema(dt) : GetXmlSchema(dt));
_output.Append(',');
}
WriteDataTableData(dt);
// end datatable
_output.Append('}');
}
#endif
bool _firstWritten;
private void WriteObject(object obj)
{
Indent();
if (_useGlobalTypes == false)
_output.Append('{');
else
{
if (_firstWritten)
_output.Append("{");
}
_firstWritten = true;
_current_depth++;
if (_current_depth > _MAX_DEPTH)
throw new Exception("Serializer encountered maximum depth of " + _MAX_DEPTH);
var map = new Dictionary<string, string>();
Type t = obj.GetType();
bool append = false;
if (useExtension)
{
if (_useGlobalTypes == false)
WritePairFast("$type", JSON.Instance.GetTypeAssemblyName(t));
else
{
int dt = 0;
string ct = JSON.Instance.GetTypeAssemblyName(t);
if (_globalTypes.TryGetValue(ct, out dt) == false)
{
dt = _globalTypes.Count + 1;
_globalTypes.Add(ct, dt);
}
WritePairFast("$type", dt.ToString());
}
append = true;
}
var g = JSON.Instance.GetGetters(t);
foreach (var p in g)
{
if (append)
_output.Append(',');
object o = p.Getter(obj);
if ((o == null || o is DBNull) && serializeNulls == false)
append = false;
else
{
WritePair(p.Name, o);
if (o != null && useExtension)
{
Type tt = o.GetType();
if (tt == typeof(object))
map.Add(p.Name, tt.ToString());
}
append = true;
}
}
if (map.Count > 0 && useExtension)
{
_output.Append(",\"$map\":");
WriteStringDictionary(map);
}
_current_depth--;
Indent();
_output.Append('}');
_current_depth--;
}
private void Indent()
{
Indent(false);
}
private void Indent(bool dec)
{
if (_Indent)
{
_output.Append("\r\n");
for (int i = 0; i < _current_depth - (dec ? 1 : 0); i++)
_output.Append("\t");
}
}
private void WritePairFast(string name, string value)
{
if ((value == null) && serializeNulls == false)
return;
Indent();
WriteStringFast(name);
_output.Append(':');
WriteStringFast(value);
}
private void WritePair(string name, object value)
{
if ((value == null || value is DBNull) && serializeNulls == false)
return;
Indent();
WriteStringFast(name);
_output.Append(':');
WriteValue(value);
}
private void WriteArray(IEnumerable array)
{
Indent();
_output.Append('[');
bool pendingSeperator = false;
foreach (var obj in array)
{
Indent();
if (pendingSeperator) _output.Append(',');
WriteValue(obj);
pendingSeperator = true;
}
Indent();
_output.Append(']');
}
private void WriteStringDictionary(IDictionary dic)
{
Indent();
_output.Append('{');
bool pendingSeparator = false;
foreach (DictionaryEntry entry in dic)
{
if (pendingSeparator) _output.Append(',');
WritePair((string)entry.Key, entry.Value);
pendingSeparator = true;
}
Indent();
_output.Append('}');
}
private void WriteDictionary(IDictionary dic)
{
Indent();
_output.Append('[');
bool pendingSeparator = false;
foreach (DictionaryEntry entry in dic)
{
if (pendingSeparator) _output.Append(',');
Indent();
_output.Append('{');
WritePair("k", entry.Key);
_output.Append(",");
WritePair("v", entry.Value);
Indent();
_output.Append('}');
pendingSeparator = true;
}
Indent();
_output.Append(']');
}
private void WriteStringFast(string s)
{
//Indent();
_output.Append('\"');
_output.Append(s);
_output.Append('\"');
}
private void WriteString(string s)
{
//Indent();
_output.Append('\"');
int runIndex = -1;
for (var index = 0; index < s.Length; ++index)
{
var c = s[index];
if (c >= ' ' && c < 128 && c != '\"' && c != '\\')
{
if (runIndex == -1)
{
runIndex = index;
}
continue;
}
if (runIndex != -1)
{
_output.Append(s, runIndex, index - runIndex);
runIndex = -1;
}
switch (c)
{
case '\t': _output.Append("\\t"); break;
case '\r': _output.Append("\\r"); break;
case '\n': _output.Append("\\n"); break;
case '"':
case '\\': _output.Append('\\'); _output.Append(c); break;
default:
_output.Append("\\u");
_output.Append(((int)c).ToString("X4", NumberFormatInfo.InvariantInfo));
break;
}
}
if (runIndex != -1)
{
_output.Append(s, runIndex, s.Length - runIndex);
}
_output.Append('\"');
}
}
}

View File

@@ -1,30 +0,0 @@
//http://fastjson.codeplex.com/
//http://fastjson.codeplex.com/license
using System.Collections.Generic;
namespace NzbDrone.Common.Exceptron.fastJSON
{
internal class SafeDictionary<TKey, TValue>
{
private readonly object _Padlock = new object();
private readonly Dictionary<TKey, TValue> _Dictionary = new Dictionary<TKey, TValue>();
internal bool TryGetValue(TKey key, out TValue value)
{
return _Dictionary.TryGetValue(key, out value);
}
internal TValue this[TKey key] => _Dictionary[key];
internal void Add(TKey key, TValue value)
{
lock (_Padlock)
{
if (_Dictionary.ContainsKey(key) == false)
_Dictionary.Add(key, value);
}
}
}
}

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