1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-04-19 21:46:50 -04:00

Compare commits

...

334 Commits

Author SHA1 Message Date
Taloth Saldono e5278a0243 Added advanced torznab option to disable rageid lookups for trackers only supporting title queries. 2015-05-19 21:29:57 +02:00
Mark McDowall 7c246abc88 Fix torrent blacklisting when InfoHash is available 2015-05-17 16:20:51 -07:00
Mark McDowall 475f4244c4 Recent folders for add series now show clickable cursor 2015-05-17 16:10:10 -07:00
Mark McDowall f57dea7f1f New: Store last 5 used folders from manual import 2015-05-17 16:07:27 -07:00
Taloth Saldono f1a5261e0a Properly dispose filestream after getting mediainfo. 2015-05-17 18:48:00 +02:00
Taloth Saldono fe5cb9503c Updated kickass url... again 2015-05-16 01:11:39 +02:00
Taloth 944a775625 Merge pull request #540 from larsjohnsen/source-misc-xbuild-support
Compilation: Misc changes to support XBuild
2015-05-15 19:34:46 +02:00
Lars Johnsen c8c17bce7e Compilation: Misc changes to support XBuild 2015-05-15 19:07:52 +02:00
Mark McDowall 366e3ed0be Merge pull request #539 from larsjohnsen/source-case-inconistancies
Compilation: Fix case inconsistencies
2015-05-15 09:10:05 -07:00
Taloth cef6eb7509 Merge pull request #544 from Sonarr/mediainfo-unicode
Integrated MediaInfo wrapper to be able to properly handle Unicode
2015-05-15 10:40:45 +02:00
Mark McDowall f88e2e2b79 Fix tests 2015-05-14 18:35:13 -07:00
Mark McDowall 57bcc9f4c1 Don't filter excluded files twice 2015-05-14 18:33:44 -07:00
Mark McDowall 69dd1c6ec4 Test to make sure we scan files in root of series folder (no season folders) 2015-05-14 18:23:57 -07:00
Mark McDowall 761a106fa9 Use HTTPS for piwik when loading via HTTPS 2015-05-14 13:24:24 -07:00
Taloth Saldono 5cd2d71e6f Integrated MediaInfo wrapper to be able to properly handle Unicode on Linux. 2015-05-14 13:31:05 +02:00
Mark McDowall 96578ca59b Stricter rejection of series subfolders
Fixed: Exclude .@__thumb folders from series disk scans

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

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

The existence of this priority is buried in their API docs[1] and in forum posts[2] but has been supported for a while (as of r1000). On older clients, the NZBGet UI will just show "Priority: 900" instead of the friendly label. They will be prioritised above other items in the queue but won't bypass the paused state. In other words, NZBGet models priorities as numeric values so this is compatible with old versions and new versions will treat this value specially when encountered.

[1]: http://nzbget.net/RPC_API_reference#Method_.22append.22
[2]: http://nzbget.net/forum/viewtopic.php?f=3&t=1177
2015-03-12 17:50:24 +11:00
Taloth Saldono dc2c1f7928 Fixed: Import of single-file anime torrents. 2015-03-10 22:43:24 +01:00
Taloth Saldono 2b6c915e32 Fixed: Added tooltips to Blackhole Watch/Torrent/Nzb Folder fields. 2015-03-10 22:00:47 +01:00
Taloth Saldono 2a12aca66a Fixed: Selecting range with shift-key in Series Editor should now work as intended. 2015-03-10 20:31:23 +01:00
Taloth Saldono f5f050f80b Fixed: Validation of dot prefix in Transmission category. 2015-03-10 13:38:30 +01:00
Mark McDowall d8852d840b New: Parse releases that have a 5 digit episode number 2015-03-09 17:15:56 -07:00
Mark McDowall a45f822fdb Fixed: piwik loading when accessing Sonarr via HTTPS 2015-03-09 15:56:34 -07:00
Taloth Saldono bf5b645416 Test fixed 2015-03-09 21:28:49 +01:00
Taloth aa7aa3ce61 Merge pull request #218 from Sonarr/fanzub-url
Fanzub url can now be modified [migration 82]
2015-03-09 20:54:41 +01:00
Taloth 69edfba237 Merge pull request #217 from Sonarr/transmission-category
Removed hardcoded dot prefix from the transmission category [migration 81]
2015-03-09 20:54:30 +01:00
Taloth Saldono 6393f66448 Fixed: Added symbols and tooltips to Manual Search last two sort columns. 2015-03-09 19:40:25 +01:00
Taloth Saldono f39e99bd3c Fixed: Column sort direction will not toggle unless the same column is clicked again. 2015-03-09 19:21:18 +01:00
Taloth Saldono 112cde1cee New: Added UrlBase to Deluge Settings to facilitate seedbox setups. 2015-03-08 15:37:33 +01:00
Taloth Saldono ebbaa403f6 Removed deprecated code. 2015-03-08 14:30:13 +01:00
Taloth Saldono dcc988da06 Removed duplicate tests. 2015-03-08 14:30:12 +01:00
Mark McDowall dd6dc38672 Series editor cleanup 2015-03-07 16:44:35 -08:00
Mark McDowall 1d70c97983 Fixed: Sorting on path in series editor 2015-03-06 22:14:33 -08:00
Taloth Saldono 799f6034c7 Fixed: Removed hardcoded dot prefix from the transmission category, making it configurable via the settings instead. 2015-03-05 20:44:41 +01:00
Mark McDowall dc80377a4c Merge pull request #214 from larsjohnsen/ui-connect-changes
Separate setting groups & clarify tooltips
2015-03-05 10:54:27 -08:00
Lars Johnsen ca9c39984e UI: Separate setting groups & clarify tooltips 2015-03-05 13:07:47 +01:00
Mark McDowall f021f9b146 Fixed: Updating Kodi won't fail if a series has an IMDB ID instead of a TVDB ID 2015-03-05 00:22:46 -08:00
Taloth Saldono 4036654f3f New: Fanzub url can now be modified (to be used only with alternative sites implementing the same api) 2015-03-04 22:41:22 +01:00
Taloth Saldono 8b1e0f68dd Added tooltip to Episode Delete button. 2015-03-04 21:20:08 +01:00
Taloth Saldono ee02e6a31c Disabled eztv test entirely. 2015-03-04 21:20:07 +01:00
Taloth Saldono 6803e46782 Fixed: Empty Sabnzbd category is now properly handled. But added UI validation to recommend adding a category. 2015-03-04 21:14:40 +01:00
Mark McDowall a8e805fd5d Merge pull request #205 from bdegier/develop
New: NZBFinder.ws as Newznab preset
2015-03-04 07:21:21 -08:00
Taloth Saldono d8662321be Fixed parsing specials with Scene.Title.S0x.Episode.Title format. 2015-03-03 01:08:53 +01:00
Taloth Saldono 46da14f3bf And the same fix for the actual import. 2015-03-01 23:19:49 +01:00
Taloth Saldono 8438ee0a52 Fixed regex incompatible with mono. 2015-03-01 23:04:20 +01:00
Taloth Saldono 6ba78f6aed No longer marks download as imported if no episodes were found. 2015-03-01 22:44:13 +01:00
Taloth Saldono 7b0bc4334d Fixed: Activity->Queue didn't show manually downloaded specials for which the parser couldn't find an episode number. 2015-03-01 22:42:31 +01:00
Taloth Saldono 2e81e278e1 Fixed a couple of logging errors. 2015-03-01 22:34:39 +01:00
Taloth Saldono 8d16b8b9d6 Fixed: Hashes being parsed as 0e00 numbering. 2015-03-01 15:01:06 +01:00
Mark McDowall a7a6a08807 Fixed: Force import won't trigger icon change on multiple items 2015-02-27 16:58:10 -08:00
Mark McDowall f4573545cd Fixed: Improved parsing for anime episodes with leading release group 2015-02-26 22:30:31 -08:00
Keivan Beigi a0ac27f9fe Merge pull request #213 from larsjohnsen/patch-4
Delete obsolete file: handlebars.runtime.min.js
2015-02-26 19:19:28 -08:00
larsjohnsen 3b737d4b66 Delete obsolete file: handlebars.run.min.js 2015-02-27 00:37:02 +01:00
Mark McDowall 72993f3c3c Fixed: Cutoff will be respected when release is still in queue 2015-02-26 08:13:15 -08:00
Mark McDowall 03335b2a71 Added SVG logo 2015-02-24 18:41:03 -08:00
Taloth Saldono aa52a83395 Fixed: Preferring season packs over single episodes before comparing relative sizes. 2015-02-24 20:13:17 +01:00
Taloth Saldono 14a99a28cc New: Manual single episode searches on BTN will now also search for season packs to simplify manually grabbing a season pack. 2015-02-24 20:13:16 +01:00
Mark McDowall be338a651e Fixed: Menu button on mobile views 2015-02-23 18:16:36 -08:00
Taloth Saldono 402d6b5411 New: Added rudimentary Anime search by tvdb episodenumber to BTN. 2015-02-23 20:27:11 +01:00
Taloth Saldono e143b18df3 Fixed: Episode/Season searches on BTN are now performed by tvdb numbering instead of scene numbering. (let us know if you run into problems with series with scene numbering) 2015-02-23 20:16:55 +01:00
Taloth Saldono 4d837a46af Fixed: Now searching BTN by tvdbid instead of tvrageid to get results for certain series. 2015-02-23 20:16:54 +01:00
Mark McDowall ca05c15340 Removed extra 's' in file 2015-02-22 21:11:42 -08:00
Taloth Saldono 2da23ae230 Fixed tooltip for pending queue items. 2015-02-22 22:10:45 +01:00
Mark McDowall a3d649452f Fixed sorting in episode file editor 2015-02-22 12:40:34 -08:00
Taloth Saldono 42f9992af0 Fixed: CDH can now remove items after import from NzbGet it didn't grab itself. 2015-02-22 19:55:00 +01:00
Taloth Saldono 6095855102 Fixed: Failed download handling should now only report a download wasn't grabbed by sonarr if the download actually failed. 2015-02-22 13:43:14 +01:00
Mark McDowall 30849e6356 Less wordy tooltip for season rename 2015-02-21 21:33:31 -08:00
Mark McDowall e466c77487 Fixed: Ignore .AppleDouble subfolders of season folder 2015-02-21 11:49:56 -08:00
Mark McDowall 53029fe928 Merge pull request #208 from Sonarr/episode-file-editor
Episode file editor
2015-02-21 11:14:10 -08:00
Mark McDowall 8ab0b26773 Episode file editor
New: Ability to delete all episode files in a series or season
New: Ability to change quality for all episode files in a series or season
2015-02-21 11:13:24 -08:00
Mark McDowall 47a0f55255 Fixed search icons 2015-02-21 11:12:57 -08:00
Mark McDowall 132c876ca9 Merge pull request #209 from Sonarr/font-awesome-4
Upgraded to FontAwesome 4.3.0
2015-02-21 10:54:44 -08:00
Mark McDowall 015deacd7b Upgraded to FontAwesome 4.3.0 2015-02-21 10:53:46 -08:00
Mark McDowall c4ce64d98d Update CONTRIBUTING.md 2015-02-20 21:14:18 -08:00
Mark McDowall 4108bbac7a Clear log files 2015-02-19 11:30:13 -08:00
Mark McDowall e58576bcfa Don't try to set console logging when its not enabled 2015-02-19 09:19:09 -08:00
Taloth Saldono dea58ed663 Fixed: Changed sqlite to use full fsync on osx to reduce the chance of corruption at the cost of some performance. 2015-02-18 19:28:10 +01:00
Taloth Saldono 1837ba94cc Added info to explain Generic providers such as Newznab. 2015-02-18 19:27:46 +01:00
Mark McDowall 33ed76556f Deduping tags only updates affected models 2015-02-17 22:02:24 -08:00
Mark McDowall 59f487392e Fixed: UI notification after Sonarr updates 2015-02-17 21:52:06 -08:00
bdegier 1f8f52ac9b Added NZBFinder.ws as optional Indexer
Established indexer. Been around for 2+ years.
2015-02-17 15:01:28 +01:00
Mark McDowall bf65807ef3 Series details styling fixes
Fixed: Empty network won't be shown on series details
Fixed: Poster and series title line up on series details
2015-02-16 17:58:30 -08:00
Taloth Saldono 4e6466e10c Updated stripbom. 2015-02-14 19:14:58 +01:00
Taloth Saldono 32fc68b9df UI Cleanup - Updated System, Tags and Wanted subtrees. 2015-02-14 19:14:57 +01:00
Taloth Saldono d6079a701c UI Cleanup - Updated Shared and Shims subtrees. 2015-02-14 19:12:33 +01:00
Taloth Saldono 019525dd9d UI Cleanup - Updated Settings subtree. 2015-02-14 19:12:32 +01:00
Taloth Saldono b69ea349ce UI Cleanup - Updated Series subtree. 2015-02-14 19:12:31 +01:00
Taloth Saldono 1bf433872a UI Cleanup - Updated Rename and SeasonPass subtrees. 2015-02-14 19:12:30 +01:00
Taloth Saldono 860f55996c UI Cleanup - Updated Navbar, Profile, Quality and Release subtrees. 2015-02-14 19:12:29 +01:00
Taloth Saldono 29d9e3dadf UI Cleanup - Updated root tree. 2015-02-14 19:12:28 +01:00
Taloth Saldono 70bfad4e6a UI Cleanup - Updated Instrumentation, jQuery and Mixins subtrees. 2015-02-14 19:12:28 +01:00
Taloth Saldono 44928c8f64 UI Cleanup - Updated Health subtree. 2015-02-14 19:12:27 +01:00
Taloth Saldono a5fd28326e UI Cleanup - Updated Form and Handlebars subtree. 2015-02-14 19:12:26 +01:00
Taloth Saldono fb7988edb8 UI Cleanup - Updated Episode subtree. 2015-02-14 19:12:25 +01:00
Taloth Saldono 7b7f199587 UI Cleanup - Updated Commands subtree. 2015-02-14 19:12:24 +01:00
Taloth Saldono 7b5c0a952b UI Cleanup - Updated Cells subtree. 2015-02-14 19:12:23 +01:00
Taloth Saldono 83b8ab8fe9 UI Cleanup - Updated Calendar subtree. 2015-02-14 19:12:22 +01:00
Taloth Saldono f5118fc430 UI Cleanup - Updated AddSeries subtree. 2015-02-14 19:12:21 +01:00
Taloth Saldono b556eda4a0 UI Cleanup - Updated Activity subtree. 2015-02-14 19:12:20 +01:00
Mark McDowall b308f06af3 Remove unused parameter 2015-02-13 17:22:24 -08:00
Mark McDowall 6959f6e13a Merge pull request #196 from Sonarr/dedupe-tags
Dedupe tags
2015-02-13 17:06:56 -08:00
Mark McDowall b7e609a7d5 De-dupe Tags
Fixed: Remove duplicate tags and prevent new ones from being created
2015-02-13 17:04:07 -08:00
Mark McDowall 3ed8f0ea84 Fixed: Not properly getting the parent of a folder with a trailing slash
GetParentFolder will trim trailing slashes
2015-02-13 16:53:01 -08:00
Taloth Saldono 1b3993bf6a Tooltips should now be attached to a container close to the target element while avoiding button/input groups. 2015-02-13 21:45:26 +01:00
Mark McDowall d1df5ed7cd New: Logging level in settings will be used for Console logging 2015-02-11 17:05:27 -08:00
Mark McDowall 539de9e124 Fixed: Daily episodes that have date and season/episode numbers in the release name 2015-02-11 08:04:01 -08:00
Mark McDowall 37959fd753 Search improvements
New: Series/Season search will not grab unmonitored episodes
Fixed: Missing episode search won't grab missing unmonitored episodes by mistake
2015-02-10 17:19:15 -08:00
Mark McDowall 32dd545ef9 commonjsed SearchResultView 2015-02-10 17:02:52 -08:00
Mark McDowall 408a4e0a81 Option to monitor no episodes on add 2015-02-10 16:46:25 -08:00
Mark McDowall cc21d83e69 Help text for tags on notifications 2015-02-10 16:41:15 -08:00
Taloth Saldono 29fe7b2acd Fixed and added tests. 2015-02-11 00:22:08 +01:00
Taloth Saldono 00dddfeaf3 Updated zero.clipboard to fix copy to clipboard function for requirejs.
Removed wrapper function.
2015-02-11 00:06:33 +01:00
Taloth Saldono 58f0e713fa Moved naming pattern in Rename preview dialog to top. 2015-02-10 23:31:19 +01:00
Taloth Saldono e00ec8b01b Manual search no longer permits downloading releases for which we can't find an episode until we can fix the association logic. 2015-02-10 23:30:47 +01:00
Taloth Saldono 77edf53c6a New: Added poster to Series Details overview in the large screen width category. 2015-02-10 21:13:10 +01:00
Mark McDowall 8bf1d512c2 Update UI will still load if no updates are available 2015-02-09 17:11:56 -08:00
Mark McDowall db4eadac40 Metadata file improvements
Fixed: Duplicate metadata files won't cause metadata file processing to fail
Fixed: Duplicate Metadata files will be removed from disk during rescan
2015-02-09 17:07:48 -08:00
Mark McDowall 573c2b8f60 Root folder improvements
Fixed: Prevent non-writeable root folder from being added
Fixed: Errors getting free space/subfolders for root folder won't prevent the UI from loading
2015-02-09 16:42:26 -08:00
Mark McDowall d3c1deb203 Fixed default KAT url 2015-02-09 13:04:55 -08:00
Mark McDowall b5cfa72c31 Fixed error on load for poster item view 2015-02-09 13:02:09 -08:00
Mark McDowall 394b93628c Fixed: Monitor from first season 2015-02-09 13:01:50 -08:00
Mark McDowall 6807f92f55 AsOsAgnostic? 2015-02-08 21:59:20 -08:00
Mark McDowall ec88c2c2ca Parsing improvements
Fixed: Don't use full path when parsing files
New: Support for iTunes library file structure
2015-02-08 21:49:44 -08:00
Taloth 5750f012cb Merge pull request #180 from Sonarr/updatecheck-fixes
Updatecheck fixes
2015-02-08 21:21:24 +01:00
Mark McDowall b4d3a41213 Merge pull request #184 from Sonarr/import-using-folder-name
Import using folder name
2015-02-08 00:07:52 -08:00
Mark McDowall 20782bbbc1 Episode import improvements
Fixed: Use folder name when file name is not parsable on import
2015-02-08 00:07:08 -08:00
Keivan Beigi 5b54b02d7e updated download url in UpdateServiceFixture 2015-02-07 22:40:20 -08:00
Keivan Beigi 811ce8fa22 separated vendor.js from main.js 2015-02-07 17:01:15 -08:00
Keivan Beigi 3fab46a740 updated html doctype 2015-02-07 12:07:54 -08:00
Taloth Saldono 40987cc335 Include version in services Changes api call so the server knows how to redirect. 2015-02-07 19:52:01 +01:00
Keivan Beigi 3ddc01e3f4 Fix: only add cache breaker to css/js files (exclude calendar, apple icons etc) 2015-02-07 10:41:34 -08:00
Keivan Beigi 6867319c8d added alias for vent 2015-02-07 10:30:02 -08:00
Keivan Beigi 8d03850de7 Added back version check on ajaxSuccess 2015-02-07 10:25:38 -08:00
Keivan Beigi 8f8fe99a16 Reverting SignalR.Core/Infrastructure/CancellationTokenExtensions.cs to old version
http://pastie.org/pastes/9895879/text?key=ifs697ru97thfxedyj91oq
2015-02-07 10:13:41 -08:00
Keivan Beigi 0829bb6e41 different favicon for debug mode 2015-02-07 09:58:22 -08:00
Keivan Beigi fcb4f8fd58 Maybe? 2015-02-07 09:51:06 -08:00
Keivan Beigi e0dd72328c more shim cleanup 2015-02-07 08:37:57 -08:00
Keivan Beigi 672e1bd9ed cleaned up validation shims/modules 2015-02-07 08:37:57 -08:00
Keivan Beigi e4a93ded28 Merge pull request #191 from Sonarr/signalr-1-2-2
Upgraded SignalR to 1.2.2
2015-02-07 08:20:11 -08:00
Keivan Beigi 77fdd724f4 fixed ToTheTop 2015-02-07 07:31:50 -08:00
Taloth Saldono 955029ec43 Fixed: Updated installation HealthCheck warning link to wiki. 2015-02-07 16:24:30 +01:00
Taloth Saldono 52a71a4e96 Fixed some mono specific tests. 2015-02-07 16:24:29 +01:00
Taloth Saldono f7bdf635b3 Reordered and renamed tabs in System. 2015-02-07 16:24:28 +01:00
Taloth Saldono 071839fa86 Removed InstallUpdate, instead manually triggering ApplicationUpdate. 2015-02-07 16:24:27 +01:00
Taloth Saldono 2f06cc6ffa Fixed: Branch redirects will now occur during install of the a new update instead of during an update check. 2015-02-07 16:24:26 +01:00
Taloth Saldono 7ce9f416d1 InstallUpdate pre-check failures should now show a nice error on the UI. 2015-02-07 16:24:26 +01:00
Taloth Saldono 8833f1ad31 Allow failing a Command using a specific message. 2015-02-07 16:24:25 +01:00
Taloth Saldono a058333f65 Fixed: Manually triggering Check Health will now also run health checks that normally only run on startup. 2015-02-07 16:24:24 +01:00
Taloth Saldono 6e179839d9 Added test to check Config behavior. 2015-02-07 16:24:23 +01:00
Taloth Saldono 3a938e18fa Fixed: Install Update UI should now report an error if the application folder is not writable instead of failing silently. 2015-02-07 16:24:22 +01:00
Taloth Saldono 11803afc39 Added FolderWritable to DiskProvider to centralize the check. 2015-02-07 16:24:22 +01:00
Taloth Saldono 104d35299b Checks for update regardless of settings, but won't install it. 2015-02-07 16:24:21 +01:00
Keivan Beigi 127e38feb7 Upgraded SignalR to 1.2.2 2015-02-07 07:04:22 -08:00
Taloth Saldono 0934900cab Updated exception handler to ignore certain types of exceptions. 2015-02-07 15:22:25 +01:00
Keivan Beigi 15b0bc0333 update jquery to 1.11.2 2015-02-07 05:50:19 -08:00
Mark McDowall ec413c13bd RSS Sync interval cannot be set to a 1-9 minutes (0 or 10+ only) 2015-02-06 17:42:30 -08:00
Taloth Saldono f5ddb36ebd Fixed: All issues regarding Media Covers should be fixed now after apply this update. Refresh browser cache if still missing and report issues on forum. 2015-02-06 21:41:36 +01:00
Taloth Saldono 6c22a5ffdb Fixed: Health Checks on mono now shows correct wiki links. 2015-02-06 21:41:35 +01:00
Mark McDowall 9ac3a1e094 Set RSS Sync to minimum 10 minutes 2015-02-05 23:32:28 -08:00
Mark McDowall d5cc261985 Spawn new mono processes with --debug 2015-02-05 23:31:15 -08:00
Mark McDowall fd02f0ae55 Fixed: Italians in title will not treat the episode as Italian language 2015-02-04 23:24:30 -08:00
Mark McDowall 2a4d7b82ea Comment out parsing test 2015-02-04 22:54:16 -08:00
Mark McDowall c9d21c7863 Don't search for episodes in series that haven't aired yet 2015-02-04 22:52:07 -08:00
Keivan Beigi d4a4a4095e updated npm packages 2015-02-04 14:45:53 -08:00
Keivan Beigi 9ca97d0135 Handlebars 2.0 2015-02-04 11:40:31 -08:00
Keivan Beigi c1467d0ecd enable named views for smoke tests 2015-02-04 11:09:34 -08:00
Mark McDowall 327802fae4 Integration tests as well 2015-02-04 07:54:25 -08:00
Mark McDowall 145fa76614 Run nunit console on *nix with runtime 4.0 (for proper stacktraces) 2015-02-04 07:50:29 -08:00
Mark McDowall 341daf69d1 Fixed: Wrong user name won't result in error message being generated 2015-02-04 07:15:37 -08:00
Mark McDowall 33bb64984a More parsing test cases 2015-02-04 07:04:49 -08:00
Mark McDowall de49a5b8f6 Fixed: Add series will update UI properly 2015-02-03 21:33:39 -08:00
Keivan Beigi 199c6c84b2 disabled webpack jshit 2015-02-03 17:02:13 -08:00
Keivan Beigi 095e92c7dd Moved jshint config to .jshintrc 2015-02-03 16:56:18 -08:00
Keivan Beigi a7001ab322 updated to webpack 1.5.3 2015-02-03 15:33:56 -08:00
Keivan Beigi bc037e7319 don't add named views in production 2015-02-03 15:33:55 -08:00
Taloth Saldono b3f11564a7 Fixed: No longer leaves a corrupt file if MediaCover resize failed. 2015-02-03 21:15:40 +01:00
Mark McDowall 754c1ea331 Logout button for forms Auth and fix UrlBase redirects 2015-02-03 08:37:42 -08:00
Mark McDowall aa9df49ea2 HeaderCell is a standard mixin 2015-02-03 00:01:05 -08:00
Mark McDowall 85f6d90f40 Fixed table header cell 2015-02-02 23:46:37 -08:00
Mark McDowall ed4bf349db Fixed webpack issues adding indexers/download clients/notifications 2015-02-02 23:01:45 -08:00
Mark McDowall e6566b26d8 Spinner on add series buttons 2015-02-02 22:16:26 -08:00
Mark McDowall 66a9cddf01 Fixed: Remove from pending 2015-02-02 21:17:18 -08:00
Keivan Beigi 428a1439e5 rjs -> webpack 2015-02-02 17:43:14 -08:00
Mark McDowall 344f3d66ef Reloading the page before restarting won't break the UI when changing authentication method 2015-02-02 11:55:04 -08:00
Mark McDowall 27d3ecf6b2 Merge pull request #156 from Sonarr/add-series-options
Add Series Options
2015-02-02 11:53:47 -08:00
Mark McDowall 05ee57a972 New: options when adding series, including the ability to search for all missing episodes 2015-02-02 09:03:25 -08:00
Mark McDowall 7b7f7ac56b Fixed spacing for labels when series path is abnormally long 2015-02-02 07:37:20 -08:00
Mark McDowall 53f4966e97 Merge pull request #183 from Sonarr/forms-auth
Forms authentication
2015-02-01 22:40:52 -08:00
Mark McDowall 3c756348eb New: Forms authentication 2015-02-01 22:33:53 -08:00
1152 changed files with 42046 additions and 36941 deletions
+1
View File
@@ -122,3 +122,4 @@ setup/Output/
#VS outout folders
bin
obj
output/*
+6 -1
View File
@@ -33,8 +33,13 @@ Setup guides, FAQ, the more information we have on the wiki the better.
- Use 4 spaces instead of tabs, this is the default for VS 2012 and WebStorm (to my knowledge)
### Pull Requesting ###
- Only make pull requests to develop, never master, if you make a PR to master we'll comment on it and close it
- You're probably going to get some comments or questions from us, they will be to ensure consistency and maintainability
- We'll try to respond to pull requests as soon as possible, if its been a day or two, please reach out to us, we may have missed it
- Each PR comes from its own [feature branch](http://martinfowler.com/bliki/FeatureBranch.html) not develop in your fork
- Each PR should come from its own [feature branch](http://martinfowler.com/bliki/FeatureBranch.html) not develop in your fork, it should have a meaningful branch name (what is being added/fixed)
- new-feature (Good)
- fix-bug (Good)
- patch (Bad)
- develop (Bad)
If you have any questions about any of this, please let us know.
+240
View File
@@ -0,0 +1,240 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="218px"
height="218px" viewBox="0 0 218 218" enable-background="new 0 0 218 218" xml:space="preserve">
<symbol id="hex_grid" viewBox="-114.25 -98.617 228.55 197.233">
<path fill-rule="evenodd" clip-rule="evenodd" fill="none" stroke="#989898" stroke-width="0.5" stroke-linecap="square" stroke-miterlimit="1" d="
M72.15,90.3l4.7-2.7l4.65,2.7v5.4l-4.65,2.7l-4.7-2.7V90.3z M62.85,95.7l-4.65,2.7l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7V95.7l4.65,2.7
l4.65-2.7 M62.85,90.3l4.65-2.7l4.65,2.7 M62.85,79.55v-5.4l4.65-2.7l4.65,2.7v5.4L67.5,82.2L62.85,79.55L58.2,82.2l-4.65-2.65
M72.15,74.15l4.7-2.7l4.65,2.7v5.4l-4.65,2.65l-4.7-2.65 M76.85,87.6v-5.4 M67.5,87.6v-5.4 M81.5,95.7l4.65,2.7l4.65-2.7l4.65,2.7
l4.65-2.7l4.65,2.7l4.65-2.7v-5.4l4.65-2.7v-5.4l-4.65-2.65v-5.4l4.65-2.7v-5.4l-4.65-2.7v-5.4l4.65-2.7v-5.4l-4.65-2.65v-5.4
l4.65-2.7v-5.4L109.4,31v-5.4l4.65-2.7v-5.4l-4.65-2.65v-5.4l4.65-2.7v-5.4l-4.65-2.7v-5.4l4.65-2.7v-5.4l-4.65-2.65v-5.4l4.65-2.7
V-31l-4.65-2.7v-5.4l4.65-2.7v-5.4l-4.65-2.65v-5.4l4.65-2.7v-5.4l-4.65-2.7v-5.4l4.65-2.7v-5.4l-4.65-2.65v-5.4l4.65-2.7v-5.4
l-4.65-2.7l-4.65,2.7l-4.65-2.7l-4.65,2.7l-4.65-2.7l-4.65,2.7l-4.65-2.7l-4.65,2.7l-4.7-2.7l-4.65,2.7l-4.65-2.7l-4.65,2.7
l-4.65-2.7l-4.65,2.7l-4.65-2.7l-4.65,2.7l-4.65-2.7l-4.65,2.7l-4.7-2.7l-4.65,2.7l-4.65-2.7l-4.65,2.7L7-98.4l-4.65,2.7l-4.65-2.7
l-4.65,2.7l-4.65-2.7l-4.65,2.7l-4.65-2.7l-4.65,2.7l-4.65-2.7l-4.65,2.7l-4.7-2.7l-4.65,2.7l-4.65-2.7l-4.65,2.7l-4.65-2.7
l-4.65,2.7l-4.65-2.7l-4.65,2.7l-4.65-2.7l-4.65,2.7l-4.7-2.7l-4.65,2.7l-4.65-2.7l-4.65,2.7l-4.65-2.7l-4.65,2.7v5.4l-4.65,2.7
v5.4l4.65,2.65v5.4l-4.65,2.7v5.4l4.65,2.7v5.4l-4.65,2.7v5.4l4.65,2.65v5.4l-4.65,2.7v5.4l4.65,2.7v5.4l-4.65,2.7v5.4l4.65,2.65
v5.4l-4.65,2.7v5.4l4.65,2.7v5.4l-4.65,2.7v5.4l4.65,2.65v5.4l-4.65,2.7V31l4.65,2.7v5.4l-4.65,2.7v5.4l4.65,2.65v5.4l-4.65,2.7
v5.4l4.65,2.7v5.4l-4.65,2.7v5.4l4.65,2.65v5.4l-4.65,2.7v5.4l4.65,2.7l4.65-2.7l4.65,2.7l4.65-2.7l4.65,2.7l4.65-2.7l4.7,2.7
l4.65-2.7l4.65,2.7l4.65-2.7l4.65,2.7l4.65-2.7l4.65,2.7l4.65-2.7l4.65,2.7l4.65-2.7l4.7,2.7l4.65-2.7l4.65,2.7l4.65-2.7l4.65,2.7
l4.65-2.7l4.65,2.7l4.65-2.7l4.65,2.7L7,95.7l4.65,2.7l4.65-2.7l4.65,2.7l4.65-2.7l4.7,2.7l4.65-2.7l4.65,2.7l4.65-2.7l4.65,2.7
l4.65-2.7 M44.25,95.7v-5.4l4.65-2.7l4.65,2.7 M44.25,79.55v-5.4l4.65-2.7l4.65,2.7v5.4L48.9,82.2L44.25,79.55L39.6,82.2
l-4.65-2.65 M58.2,87.6v-5.4 M48.9,87.6v-5.4 M53.55,63.35v-5.4l4.65-2.7l4.65,2.7v5.4l-4.65,2.7L53.55,63.35l-4.65,2.7l-4.65-2.7
v-5.4l4.65-2.7l4.65,2.7 M62.85,74.15l-4.65-2.7v-5.4 M53.55,74.15l4.65-2.7 M48.9,71.45v-5.4 M48.9,55.25v-5.4l4.65-2.65
l4.65,2.65v5.4 M67.5,71.45v-5.4l4.65-2.7l4.7,2.7v5.4 M67.5,66.05l-4.65-2.7 M58.2,49.85l4.65-2.65l4.65,2.65v5.4l-4.65,2.7
M72.15,63.35v-5.4l4.7-2.7l4.65,2.7v5.4l-4.65,2.7 M76.85,55.25v-5.4l4.65-2.65l4.65,2.65v5.4l-4.65,2.7 M67.5,55.25l4.65,2.7
M34.95,95.7v-5.4l4.65-2.7l4.65,2.7 M16.3,95.7v-5.4l4.65-2.7l4.65,2.7v5.4 M25.6,90.3l4.7-2.7l4.65,2.7 M25.6,79.55v-5.4l4.7-2.7
l4.65,2.7v5.4L30.3,82.2L25.6,79.55l-4.65,2.65l-4.65-2.65v-5.4l4.65-2.7l4.65,2.7 M20.95,87.6v-5.4 M39.6,87.6v-5.4 M30.3,87.6
v-5.4 M7,95.7v-5.4l4.65-2.7l4.65,2.7 M-2.3,95.7v-5.4l4.65-2.7L7,90.3 M2.35,82.2l-4.65-2.65v-5.4l4.65-2.7L7,74.15v5.4L2.35,82.2
z M16.3,79.55l-4.65,2.65L7,79.55 M2.35,87.6v-5.4 M11.65,87.6v-5.4 M16.3,74.15l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7v5.4 M2.35,71.45
v-5.4L7,63.35l4.65,2.7 M2.35,49.85L7,47.2l4.65,2.65v5.4L7,57.95l-4.65-2.7V49.85L-2.3,47.2v-5.4l4.65-2.7L7,41.8v5.4
M11.65,55.25l4.65,2.7v5.4 M7,74.15l4.65-2.7 M7,57.95v5.4 M30.3,71.45v-5.4l4.65-2.7l4.65,2.7v5.4l-4.65,2.7 M16.3,57.95
l4.65-2.7l4.65,2.7v5.4l-4.65,2.7 M30.3,66.05l-4.7-2.7 M30.3,49.85l4.65-2.65l4.65,2.65v5.4l-4.65,2.7l-4.65-2.7V49.85l-4.7-2.65
v-5.4l-4.65-2.7v-5.4L25.6,31l4.7,2.7v5.4l-4.7,2.7 M11.65,49.85l4.65-2.65l4.65,2.65v5.4 M25.6,57.95l4.7-2.7 M34.95,57.95v5.4
M34.95,47.2v-5.4l4.65-2.7l4.65,2.7v5.4l-4.65,2.65 M25.6,31v-5.4l4.7-2.7l4.65,2.7V31l-4.65,2.7 M34.95,25.6l4.65-2.7l4.65,2.7
V31l-4.65,2.7L34.95,31 M30.3,39.1l4.65,2.7 M39.6,39.1v-5.4 M20.95,39.1l-4.65,2.7l-4.65-2.7v-5.4L16.3,31l4.65,2.7 M16.3,41.8
v5.4 M2.35,33.7L-2.3,31v-5.4l4.65-2.7L7,25.6V31L2.35,33.7z M7,25.6l4.65-2.7l4.65,2.7V31 M2.35,39.1v-5.4 M11.65,33.7L7,31
M7,41.8l4.65-2.7 M11.65,17.5L7,14.85v-5.4l4.65-2.7l4.65,2.7v5.4L11.65,17.5z M11.65,22.9v-5.4 M2.35,22.9v-5.4L7,14.85 M7,9.45
l-4.65-2.7v-5.4L7-1.35l4.65,2.7v5.4 M34.95,14.85v-5.4l4.65-2.7l4.65,2.7v5.4L39.6,17.5L34.95,14.85z M39.6,22.9v-5.4 M25.6,25.6
l-4.65-2.7v-5.4l4.65-2.65l4.7,2.65v5.4 M16.3,9.45l4.65-2.7l4.65,2.7v5.4 M34.95,9.45l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7v5.4
M11.65,1.35l4.65-2.7l4.65,2.7v5.4 M25.6,9.45l4.7-2.7 M30.3,17.5l4.65-2.65 M16.3,25.6l4.65-2.7 M20.95,17.5l-4.65-2.65
M76.85,49.85l-4.7-2.65v-5.4l4.7-2.7l4.65,2.7v5.4 M62.85,47.2v-5.4l4.65-2.7l4.65,2.7 M62.85,25.6l4.65-2.7l4.65,2.7V31
l-4.65,2.7L62.85,31V25.6l-4.65-2.7v-5.4 M72.15,25.6l4.7-2.7l4.65,2.7V31l-4.65,2.7l-4.7-2.7 M76.85,39.1v-5.4 M67.5,39.1v-5.4
M53.55,47.2v-5.4l4.65-2.7l4.65,2.7 M44.25,41.8l4.65-2.7l4.65,2.7 M44.25,25.6l4.65-2.7l4.65,2.7V31l-4.65,2.7L44.25,31
M62.85,31l-4.65,2.7L53.55,31 M58.2,39.1v-5.4 M48.9,39.1v-5.4 M53.55,14.85v-5.4l4.65-2.7l4.65,2.7v5.4L58.2,17.5L53.55,14.85
L48.9,17.5l-4.65-2.65 M48.9,22.9v-5.4 M53.55,9.45l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7v5.4 M44.25,9.45l4.65-2.7 M76.85,22.9v-5.4
l4.65-2.65l4.65,2.65v5.4l-4.65,2.7 M76.85,17.5l-4.7-2.65v-5.4l4.7-2.7l4.65,2.7v5.4 M67.5,22.9v-5.4l4.65-2.65 M67.5,17.5
l-4.65-2.65 M72.15,9.45l-4.65-2.7v-5.4l4.65-2.7l4.7,2.7v5.4 M62.85,9.45l4.65-2.7 M53.55,25.6l4.65-2.7 M44.25,63.35l-4.65,2.7
M39.6,55.25l4.65,2.7 M39.6,71.45l4.65,2.7 M67.5,49.85l4.65-2.65 M48.9,49.85l-4.65-2.65 M25.6,47.2l-4.65,2.65 M104.75,87.6
l-4.65,2.7l-4.65-2.7v-5.4l4.65-2.65l4.65,2.65V87.6l4.65,2.7 M109.4,79.55l-4.65,2.65 M100.1,95.7v-5.4 M81.5,90.3l4.65-2.7
l4.65,2.7v5.4 M81.5,74.15l4.65-2.7l4.65,2.7v5.4l-4.65,2.65l-4.65-2.65 M90.8,79.55l4.65,2.65 M86.15,87.6v-5.4 M90.8,90.3
l4.65-2.7 M95.45,71.45v-5.4l4.65-2.7l4.65,2.7v5.4l-4.65,2.7L95.45,71.45l-4.65,2.7 M86.15,55.25l4.65,2.7v5.4l-4.65,2.7
l-4.65-2.7 M86.15,71.45v-5.4 M95.45,55.25v-5.4l4.65-2.65l4.65,2.65v5.4l-4.65,2.7L95.45,55.25l-4.65,2.7 M100.1,63.35v-5.4
M90.8,63.35l4.65,2.7 M109.4,63.35l-4.65,2.7 M104.75,71.45l4.65,2.7 M104.75,55.25l4.65,2.7 M100.1,79.55v-5.4 M100.1,47.2v-5.4
l4.65-2.7l4.65,2.7 M100.1,41.8l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7v5.4 M109.4,31l-4.65,2.7 M81.5,41.8l4.65-2.7l4.65,2.7v5.4
l-4.65,2.65 M86.15,22.9l4.65,2.7V31l-4.65,2.7L81.5,31 M100.1,31v-5.4l4.65-2.7l4.65,2.7 M90.8,31l4.65,2.7 M86.15,39.1v-5.4
M90.8,41.8l4.65-2.7 M86.15,17.5l4.65-2.65l4.65,2.65v5.4l-4.65,2.7 M90.8,14.85v-5.4l4.65-2.7l4.65,2.7v5.4l-4.65,2.65
M81.5,9.45l4.65-2.7l4.65,2.7 M86.15,6.75v-5.4l4.65-2.7l4.65,2.7v5.4 M100.1,14.85l4.65,2.65v5.4 M109.4,14.85l-4.65,2.65
M100.1,9.45l4.65-2.7l4.65,2.7 M95.45,1.35l4.65-2.7l4.65,2.7v5.4 M95.45,22.9l4.65,2.7 M109.4,47.2l-4.65,2.65 M90.8,47.2
l4.65,2.65 M104.75-9.45l-4.65,2.7l-4.65-2.7v-5.4l4.65-2.65l4.65,2.65V-9.45l4.65,2.7 M109.4-17.5l-4.65,2.65 M100.1-1.35v-5.4
M90.8-1.35v-5.4l4.65-2.7 M81.5-6.75l-4.65-2.7v-5.4l4.65-2.65l4.65,2.65v5.4L81.5-6.75v5.4l-4.65,2.7 M90.8-6.75l-4.65-2.7
M95.45-14.85L90.8-17.5v-5.4l4.65-2.7l4.65,2.7v5.4 M76.85-14.85l-4.7-2.65v-5.4l4.7-2.7l4.65,2.7v5.4 M86.15-14.85l4.65-2.65
M95.45-31l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7v5.4L95.45-31z M95.45-25.6V-31 M90.8-22.9l-4.65-2.7V-31l4.65-2.7 M86.15-31
l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7 M86.15-41.8v-5.4l4.65-2.65l4.65,2.65v5.4 M100.1-33.7l4.65,2.7v5.4l-4.65,2.7 M109.4-33.7
l-4.65,2.7 M95.45-47.2l4.65-2.65l4.65,2.65v5.4l-4.65,2.7 M104.75-41.8l4.65,2.7 M81.5-22.9l4.65-2.7 M104.75-25.6l4.65,2.7
M100.1-49.85v-5.4l4.65-2.7l4.65,2.7 M109.4-66.05l-4.65,2.7l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7 M104.75-63.35v5.4 M100.1-55.25
l-4.65-2.7v-5.4l4.65-2.7 M86.15-47.2l-4.65-2.65v-5.4l4.65-2.7l4.65,2.7v5.4 M86.15-74.15l4.65,2.7v5.4l-4.65,2.7l-4.65-2.7v-5.4
L86.15-74.15v-5.4l4.65-2.65l4.65,2.65 M90.8-71.45l4.65-2.7l4.65,2.7 M95.45-63.35l-4.65-2.7 M90.8-55.25l4.65-2.7 M86.15-57.95
v-5.4 M95.45-74.15v-5.4l4.65-2.65l4.65,2.65v5.4 M81.5-71.45l-4.65-2.7v-5.4l4.65-2.65l4.65,2.65 M81.5-82.2v-5.4l4.65-2.7
l4.65,2.7v5.4 M86.15-90.3v-5.4 M104.75-95.7v5.4l-4.65,2.7l-4.65-2.7v-5.4 M100.1-82.2v-5.4 M95.45-90.3l-4.65,2.7 M109.4-82.2
l-4.65,2.65 M104.75-90.3l4.65,2.7 M109.4-49.85l-4.65,2.65 M72.15-1.35v-5.4l4.7-2.7 M67.5,1.35l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7
M72.15-17.5l-4.65,2.65l-4.65-2.65v-5.4l4.65-2.7l4.65,2.7 M67.5-14.85v5.4 M53.55-1.35v-5.4l4.65-2.7l4.65,2.7 M48.9,1.35
l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7 M44.25-22.9l4.65-2.7l4.65,2.7v5.4l-4.65,2.65l-4.65-2.65V-22.9l-4.65-2.7V-31 M53.55-17.5
l4.65,2.65v5.4 M48.9-9.45v-5.4 M53.55-33.7v-5.4l4.65-2.7l4.65,2.7v5.4L58.2-31L53.55-33.7z M62.85-22.9l-4.65-2.7V-31 M48.9-25.6
V-31l4.65-2.7 M48.9-31l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7 M48.9-41.8v-5.4l4.65-2.65l4.65,2.65v5.4 M76.85-25.6V-31l4.65-2.7
M76.85-31l-4.7-2.7v-5.4l4.7-2.7l4.65,2.7 M62.85-33.7L67.5-31v5.4 M72.15-39.1l-4.65-2.7v-5.4l4.65-2.65l4.7,2.65v5.4
M62.85-39.1l4.65-2.7 M72.15-33.7L67.5-31 M53.55-22.9l4.65-2.7 M58.2-14.85l4.65-2.65 M30.3,1.35l-4.7-2.7v-5.4l4.7-2.7l4.65,2.7
v5.4 M30.3-9.45v-5.4l4.65-2.65l4.65,2.65v5.4l-4.65,2.7 M16.3-1.35v-5.4l4.65-2.7l4.65,2.7 M30.3-14.85l-4.7-2.65v-5.4l4.7-2.7
l4.65,2.7v5.4 M25.6-17.5l-4.65,2.65L16.3-17.5v-5.4l4.65-2.7l4.65,2.7 M20.95-14.85v5.4 M16.3-6.75l-4.65-2.7v-5.4l4.65-2.65
M2.35,1.35l-4.65-2.7v-5.4l4.65-2.7L7-6.75v5.4 M7-17.5l-4.65,2.65L-2.3-17.5v-5.4l4.65-2.7L7-22.9V-17.5l4.65,2.65 M11.65-9.45
L7-6.75 M2.35-9.45v-5.4 M11.65-31L7-33.7v-5.4l4.65-2.7l4.65,2.7v5.4L11.65-31z M16.3-22.9l-4.65-2.7V-31 M2.35-25.6V-31L7-33.7
M7-39.1l-4.65-2.7v-5.4L7-49.85l4.65,2.65v5.4 M44.25-33.7L39.6-31l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7 M20.95-25.6V-31l4.65-2.7
l4.7,2.7v5.4 M20.95-47.2l4.65-2.65l4.7,2.65v5.4l-4.7,2.7l-4.65-2.7V-47.2l-4.65-2.65v-5.4l4.65-2.7l4.65,2.7 M30.3-47.2
l4.65-2.65l4.65,2.65v5.4 M30.3-41.8l4.65,2.7 M25.6-33.7v-5.4 M34.95-33.7L30.3-31 M34.95-22.9l4.65-2.7 M20.95-31l-4.65-2.7
M16.3-39.1l4.65-2.7 M7-22.9l4.65-2.7 M34.95-55.25l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7v5.4L34.95-55.25v5.4 M25.6-49.85v-5.4
l4.7-2.7 M30.3-63.35l-4.7-2.7v-5.4l4.7-2.7l4.65,2.7v5.4 M25.6-66.05l-4.65,2.7l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7 M30.3-74.15
v-5.4l4.65-2.65l4.65,2.65v5.4l-4.65,2.7 M20.95-63.35v5.4 M7-49.85v-5.4l4.65-2.7l4.65,2.7 M2.35-47.2l-4.65-2.65v-5.4l4.65-2.7
L7-55.25 M2.35-63.35l-4.65-2.7v-5.4l4.65-2.7L7-71.45v5.4L2.35-63.35z M16.3-71.45l-4.65-2.7v-5.4l4.65-2.65l4.65,2.65v5.4
M11.65-57.95v-5.4l4.65-2.7 M2.35-57.95v-5.4 M7-66.05l4.65,2.7 M11.65-74.15L7-71.45 M2.35-74.15v-5.4L7-82.2l4.65,2.65
M11.65-95.7v5.4L7-87.6l-4.65-2.7v-5.4 M20.95-95.7v5.4l-4.65,2.7l-4.65-2.7 M7-87.6v5.4 M16.3-87.6v5.4 M20.95-90.3l4.65,2.7v5.4
l-4.65,2.65 M30.3-79.55l-4.7-2.65 M30.3-95.7v5.4l-4.7,2.7 M48.9-95.7v5.4l-4.65,2.7l-4.65-2.7v-5.4 M34.95-82.2v-5.4l4.65-2.7
M30.3-90.3l4.65,2.7 M72.15-49.85v-5.4l4.7-2.7l4.65,2.7 M67.5-47.2l-4.65-2.65v-5.4l4.65-2.7l4.65,2.7 M67.5-74.15l4.65,2.7v5.4
l-4.65,2.7l-4.65-2.7v-5.4L67.5-74.15v-5.4l4.65-2.65l4.7,2.65 M81.5-66.05l-4.65,2.7l-4.7-2.7 M72.15-71.45l4.7-2.7 M76.85-63.35
v5.4 M67.5-57.95v-5.4 M53.55-49.85v-5.4l4.65-2.7l4.65,2.7 M48.9-47.2l-4.65-2.65v-5.4l4.65-2.7l4.65,2.7 M44.25-66.05v-5.4
l4.65-2.7l4.65,2.7v5.4l-4.65,2.7L44.25-66.05l-4.65,2.7 M62.85-66.05l-4.65,2.7l-4.65-2.7 M53.55-71.45l4.65-2.7l4.65,2.7
M58.2-57.95v-5.4 M48.9-57.95v-5.4 M48.9-74.15v-5.4l4.65-2.65l4.65,2.65v5.4 M48.9-79.55l-4.65-2.65v-5.4 M58.2-95.7v5.4
l-4.65,2.7l-4.65-2.7 M53.55-87.6v5.4 M58.2-79.55l4.65-2.65l4.65,2.65 M62.85-82.2v-5.4l4.65-2.7l4.65,2.7v5.4 M67.5-90.3v-5.4
M76.85-95.7v5.4l-4.7,2.7 M58.2-90.3l4.65,2.7 M44.25-17.5l-4.65,2.65 M39.6-9.45l4.65,2.7 M39.6-74.15l4.65,2.7 M39.6-57.95
l4.65,2.7 M44.25-49.85L39.6-47.2 M62.85-49.85L58.2-47.2 M16.3-49.85l-4.65,2.65 M44.25-82.2l-4.65,2.65 M76.85-90.3l4.65,2.7
M81.5-49.85l-4.65,2.65 M86.15,1.35l-4.65-2.7 M44.25-1.35l-4.65,2.7 M62.85-1.35l-4.65,2.7 M109.4-1.35l-4.65,2.7 M25.6-1.35
l-4.65,2.7 M-95.4,95.7v-5.4l4.65-2.7l4.65,2.7v5.4 M-86.1,90.3l4.7-2.7l4.65,2.7v5.4 M-104.7,95.7v-5.4l4.65-2.7l4.65,2.7
M-100.05,82.2l-4.65-2.65v-5.4l4.65-2.7l4.65,2.7v5.4L-100.05,82.2z M-95.4,74.15l4.65-2.7l4.65,2.7v5.4l-4.65,2.65l-4.65-2.65
M-86.1,74.15l4.7-2.7l4.65,2.7v5.4l-4.65,2.65l-4.7-2.65 M-100.05,87.6v-5.4 M-90.75,87.6v-5.4 M-81.4,87.6v-5.4 M-109.35,87.6
l4.65,2.7 M-104.7,79.55l-4.65,2.65 M-109.35,55.25l4.65,2.7v5.4l-4.65,2.7 M-109.35,71.45l4.65,2.7 M-90.75,66.05l-4.65-2.7v-5.4
l4.65-2.7l4.65,2.7v5.4L-90.75,66.05v5.4 M-86.1,57.95l4.7-2.7l4.65,2.7v5.4l-4.65,2.7l-4.7-2.7 M-81.4,71.45v-5.4 M-100.05,71.45
v-5.4l4.65-2.7 M-95.4,57.95l-4.65-2.7v-5.4l4.65-2.65l4.65,2.65v5.4 M-81.4,55.25v-5.4l4.65-2.65l4.65,2.65v5.4l-4.65,2.7
M-100.05,66.05l-4.65-2.7 M-104.7,57.95l4.65-2.7 M-86.1,41.8l-4.65-2.7v-5.4l4.65-2.7l4.7,2.7v5.4L-86.1,41.8z M-81.4,49.85
l-4.7-2.65v-5.4 M-100.05,49.85l-4.65-2.65v-5.4l4.65-2.7l4.65,2.7v5.4 M-95.4,31l-4.65,2.7l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7V31
l4.65,2.7 M-86.1,31v-5.4l4.7-2.7l4.65,2.7V31l-4.65,2.7 M-100.05,39.1v-5.4 M-90.75,39.1l-4.65,2.7 M-109.35,39.1l4.65,2.7
M-104.7,31l-4.65,2.7 M-109.35,6.75l4.65,2.7v5.4l-4.65,2.65 M-86.1,14.85v-5.4l4.7-2.7l4.65,2.7v5.4l-4.65,2.65L-86.1,14.85z
M-81.4,22.9v-5.4 M-86.1,25.6l-4.65-2.7v-5.4l4.65-2.65 M-100.05,22.9v-5.4l4.65-2.65l4.65,2.65 M-100.05,6.75v-5.4l4.65-2.7
l4.65,2.7v5.4l-4.65,2.7L-100.05,6.75z M-81.4,6.75v-5.4l4.65-2.7l4.65,2.7v5.4l-4.65,2.7 M-90.75,6.75l4.65,2.7 M-95.4,14.85v-5.4
M-95.4,25.6l4.65-2.7 M-100.05,17.5l-4.65-2.65 M-104.7,9.45l4.65-2.7 M-109.35,22.9l4.65,2.7 M-86.1,47.2l-4.65,2.65
M-104.7,47.2l-4.65,2.65 M-11.6,95.7v-5.4l4.65-2.7l4.65,2.7 M-20.9,95.7v-5.4l4.65-2.7l4.65,2.7 M-16.25,82.2l-4.65-2.65v-5.4
l4.65-2.7l4.65,2.7v5.4L-16.25,82.2v5.4 M-2.3,79.55l-4.65,2.65l-4.65-2.65 M-6.95,82.2v5.4 M-30.2,95.7v-5.4l4.65-2.7l4.65,2.7
M-39.55,95.7v-5.4l4.7-2.7l4.65,2.7 M-39.55,79.55v-5.4l4.7-2.7l4.65,2.7v5.4l-4.65,2.65L-39.55,79.55l-4.65,2.65l-4.65-2.65
M-20.9,79.55l-4.65,2.65l-4.65-2.65 M-34.85,87.6v-5.4 M-25.55,87.6v-5.4 M-20.9,74.15l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7v5.4
M-30.2,74.15l4.65-2.7 M-25.55,66.05l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7v5.4 M-39.55,74.15l-4.65-2.7v-5.4l4.65-2.7l4.7,2.7v5.4
M-30.2,57.95l-4.65-2.7v-5.4l4.65-2.65l4.65,2.65v5.4 M-39.55,63.35v-5.4l4.7-2.7 M-30.2,63.35l-4.65,2.7 M-11.6,74.15l4.65-2.7
l4.65,2.7 M-6.95,66.05l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7v5.4L-6.95,66.05v5.4 M-16.25,66.05l4.65-2.7 M-11.6,57.95l-4.65-2.7v-5.4
l4.65-2.65l4.65,2.65v5.4 M-16.25,55.25l-4.65,2.7 M-48.85,95.7v-5.4l4.65-2.7l4.65,2.7 M-58.15,95.7v-5.4l4.65-2.7l4.65,2.7
M-58.15,79.55v-5.4l4.65-2.7l4.65,2.7v5.4l-4.65,2.65L-58.15,79.55l-4.65,2.65l-4.65-2.65 M-44.2,87.6v-5.4 M-53.5,87.6v-5.4
M-67.45,95.7v-5.4l4.65-2.7l4.65,2.7 M-76.75,90.3l4.65-2.7l4.65,2.7 M-76.75,74.15l4.65-2.7l4.65,2.7v5.4l-4.65,2.65l-4.65-2.65
M-62.8,87.6v-5.4 M-72.1,87.6v-5.4 M-67.45,74.15l4.65-2.7l4.65,2.7 M-62.8,71.45v-5.4l4.65-2.7l4.65,2.7v5.4 M-62.8,66.05
l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7v5.4 M-76.75,63.35l4.65,2.7v5.4 M-62.8,55.25v-5.4l4.65-2.65l4.65,2.65v5.4l-4.65,2.7
M-67.45,63.35l-4.65,2.7 M-72.1,55.25l4.65,2.7 M-48.85,74.15l4.65-2.7 M-44.2,66.05l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7
M-53.5,49.85l4.65-2.65l4.65,2.65v5.4 M-53.5,66.05l4.65-2.7 M-53.5,55.25l4.65,2.7 M-48.85,47.2v-5.4l4.65-2.7l4.65,2.7v5.4
l-4.65,2.65 M-58.15,47.2v-5.4l4.65-2.7l4.65,2.7 M-58.15,25.6l4.65-2.7l4.65,2.7V31l-4.65,2.7l-4.65-2.7V25.6l-4.65-2.7v-5.4
M-48.85,25.6l4.65-2.7l4.65,2.7V31l-4.65,2.7l-4.65-2.7 M-44.2,39.1v-5.4 M-53.5,39.1v-5.4 M-62.8,49.85l-4.65-2.65v-5.4l4.65-2.7
l4.65,2.7 M-76.75,47.2v-5.4l4.65-2.7l4.65,2.7 M-76.75,25.6l4.65-2.7l4.65,2.7V31l-4.65,2.7l-4.65-2.7 M-58.15,31l-4.65,2.7
l-4.65-2.7 M-72.1,39.1v-5.4 M-62.8,39.1v-5.4 M-67.45,14.85v-5.4l4.65-2.7l4.65,2.7v5.4l-4.65,2.65L-67.45,14.85l-4.65,2.65
l-4.65-2.65 M-72.1,22.9v-5.4 M-62.8,6.75v-5.4l4.65-2.7l4.65,2.7v5.4l-4.65,2.7 M-72.1,6.75l4.65,2.7 M-44.2,22.9v-5.4l4.65-2.65
l4.7,2.65v5.4l-4.7,2.7 M-44.2,17.5l-4.65-2.65v-5.4l4.65-2.7l4.65,2.7v5.4 M-48.85,14.85l-4.65,2.65l-4.65-2.65 M-53.5,22.9v-5.4
M-44.2,6.75v-5.4l4.65-2.7l4.7,2.7v5.4l-4.7,2.7 M-53.5,6.75l4.65,2.7 M-67.45,25.6l4.65-2.7 M-11.6,47.2v-5.4l4.65-2.7l4.65,2.7
M-11.6,41.8l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7v5.4 M-16.25,33.7L-20.9,31v-5.4l4.65-2.7l4.65,2.7V31 M-2.3,31l-4.65,2.7
M-30.2,47.2v-5.4l4.65-2.7l4.65,2.7v5.4l-4.65,2.65 M-39.55,41.8l4.7-2.7l4.65,2.7 M-34.85,22.9l4.65,2.7V31l-4.65,2.7l-4.7-2.7
M-20.9,31l-4.65,2.7L-30.2,31 M-34.85,39.1v-5.4 M-25.55,39.1v-5.4 M-20.9,25.6l-4.65-2.7v-5.4l4.65-2.65l4.65,2.65v5.4
M-25.55,17.5l-4.65-2.65v-5.4l4.65-2.7l4.65,2.7v5.4 M-25.55,6.75v-5.4l4.65-2.7l4.65,2.7v5.4l-4.65,2.7 M-30.2,14.85l-4.65,2.65
M-34.85,6.75l4.65,2.7 M-6.95,17.5l-4.65-2.65v-5.4l4.65-2.7l4.65,2.7v5.4L-6.95,17.5v5.4l-4.65,2.7 M-11.6,14.85l-4.65,2.65
M-16.25,6.75l4.65,2.7 M-16.25,1.35l4.65-2.7l4.65,2.7v5.4 M-6.95,22.9l4.65,2.7 M-30.2,25.6l4.65-2.7 M-16.25,39.1l-4.65,2.7
M-67.45,47.2l-4.65,2.65 M-2.3,47.2l-4.65,2.65 M-34.85,49.85l-4.7-2.65 M-16.25,49.85l-4.65-2.65 M-81.4,39.1l4.65,2.7
M-11.6-1.35v-5.4l4.65-2.7l4.65,2.7 M-11.6-6.75l-4.65-2.7v-5.4l4.65-2.65l4.65,2.65v5.4 M-16.25-14.85l-4.65-2.65v-5.4l4.65-2.7
l4.65,2.7v5.4 M-2.3-17.5l-4.65,2.65 M-20.9-1.35v-5.4l4.65-2.7 M-20.9-6.75l-4.65-2.7v-5.4l4.65-2.65 M-25.55-9.45l-4.65,2.7
l-4.65-2.7v-5.4l4.65-2.65l4.65,2.65 M-30.2-6.75v5.4l-4.65,2.7 M-39.55-1.35v-5.4l4.7-2.7 M-34.85-14.85l-4.7-2.65v-5.4l4.7-2.7
l4.65,2.7v5.4 M-30.2-33.7v-5.4l4.65-2.7l4.65,2.7v5.4l-4.65,2.7L-30.2-33.7l-4.65,2.7l-4.7-2.7v-5.4l4.7-2.7l4.65,2.7 M-20.9-22.9
l-4.65-2.7V-31 M-34.85-25.6V-31 M-39.55-39.1l-4.65-2.7v-5.4l4.65-2.65l4.7,2.65v5.4 M-25.55-41.8v-5.4l4.65-2.65l4.65,2.65v5.4
l-4.65,2.7 M-6.95-31l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7v5.4L-6.95-31v5.4l-4.65,2.7 M-16.25-25.6V-31l4.65-2.7 M-16.25-41.8
l4.65,2.7 M-16.25-47.2l4.65-2.65l4.65,2.65v5.4 M-30.2-22.9l4.65-2.7 M-16.25-31l-4.65-2.7 M-6.95-25.6l4.65,2.7 M-44.2,1.35
l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7 M-58.15-1.35v-5.4l4.65-2.7l4.65,2.7 M-58.15-6.75l-4.65-2.7v-5.4l4.65-2.65l4.65,2.65v5.4
M-62.8-14.85l-4.65-2.65v-5.4l4.65-2.7l4.65,2.7v5.4 M-39.55-17.5l-4.65,2.65l-4.65-2.65v-5.4l4.65-2.7l4.65,2.7 M-44.2-9.45v-5.4
M-53.5-14.85l4.65-2.65 M-62.8,1.35l-4.65-2.7v-5.4l4.65-2.7 M-76.75-6.75l-4.65-2.7v-5.4l4.65-2.65l4.65,2.65v5.4L-76.75-6.75
v5.4 M-76.75-17.5v-5.4l4.65-2.7l4.65,2.7 M-67.45-17.5l-4.65,2.65 M-72.1-9.45l4.65,2.7 M-67.45-33.7v-5.4l4.65-2.7l4.65,2.7v5.4
L-62.8-31L-67.45-33.7z M-62.8-25.6V-31 M-72.1-25.6V-31l4.65-2.7 M-72.1-31l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7 M-76.75-39.1
l-4.65-2.7v-5.4l4.65-2.65l4.65,2.65v5.4 M-62.8-41.8v-5.4l4.65-2.65l4.65,2.65v5.4l-4.65,2.7 M-44.2-25.6V-31l4.65-2.7 M-44.2-31
l-4.65-2.7v-5.4l4.65-2.7 M-48.85-33.7L-53.5-31l-4.65-2.7 M-48.85-22.9l-4.65-2.7V-31 M-53.5-41.8l4.65,2.7 M-58.15-22.9l4.65-2.7
M-58.15-49.85v-5.4l4.65-2.7l4.65,2.7v5.4l-4.65,2.65 M-48.85-55.25l4.65-2.7l4.65,2.7v5.4 M-58.15-71.45l4.65-2.7l4.65,2.7v5.4
l-4.65,2.7l-4.65-2.7V-71.45z M-48.85-71.45l4.65-2.7l4.65,2.7v5.4l-4.65,2.7l-4.65-2.7 M-44.2-57.95v-5.4 M-53.5-57.95v-5.4
M-76.75-49.85v-5.4l4.65-2.7l4.65,2.7v5.4l-4.65,2.65 M-67.45-55.25l4.65-2.7l4.65,2.7 M-76.75-55.25l-4.65-2.7v-5.4l4.65-2.7
l4.65,2.7v5.4 M-81.4-63.35l-4.7-2.7v-5.4l4.7-2.7l4.65,2.7v5.4 M-81.4-74.15v-5.4l4.65-2.65l4.65,2.65v5.4l-4.65,2.7
M-58.15-66.05l-4.65,2.7l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7 M-62.8-57.95v-5.4 M-72.1-74.15l4.65,2.7 M-67.45-66.05l-4.65,2.7
M-67.45-82.2v-5.4l4.65-2.7l4.65,2.7v5.4l-4.65,2.65L-67.45-82.2l-4.65,2.65 M-76.75-82.2v-5.4l4.65-2.7l4.65,2.7 M-72.1-90.3
v-5.4 M-62.8-90.3v-5.4 M-53.5-74.15v-5.4l4.65-2.65l4.65,2.65v5.4 M-58.15-82.2l4.65,2.65 M-58.15-87.6l4.65-2.7l4.65,2.7v5.4
M-53.5-90.3v-5.4 M-44.2-95.7v5.4l-4.65,2.7 M-62.8-74.15v-5.4 M-11.6-49.85v-5.4l4.65-2.7l4.65,2.7 M-20.9-49.85v-5.4l4.65-2.7
l4.65,2.7 M-16.25-63.35l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7v5.4L-16.25-63.35v5.4 M-2.3-66.05l-4.65,2.7l-4.65-2.7 M-11.6-71.45
l4.65-2.7l4.65,2.7 M-6.95-63.35v5.4 M-25.55-47.2l-4.65-2.65v-5.4l4.65-2.7l4.65,2.7 M-39.55-55.25l4.7-2.7l4.65,2.7
M-39.55-71.45l4.7-2.7l4.65,2.7v5.4l-4.65,2.7l-4.7-2.7 M-20.9-71.45l-4.65-2.7v-5.4l4.65-2.65l4.65,2.65v5.4 M-20.9-66.05
l-4.65,2.7l-4.65-2.7 M-25.55-74.15l-4.65,2.7 M-34.85-63.35v5.4 M-25.55-57.95v-5.4 M-34.85-74.15v-5.4l4.65-2.65l4.65,2.65
M-44.2-79.55l4.65-2.65l4.7,2.65 M-39.55-82.2v-5.4l4.7-2.7l4.65,2.7v5.4 M-34.85-90.3v-5.4 M-16.25-95.7v5.4l-4.65,2.7l-4.65-2.7
v-5.4 M-25.55-90.3l-4.65,2.7 M-20.9-82.2v-5.4 M-6.95-79.55l-4.65-2.65v-5.4l4.65-2.7l4.65,2.7v5.4L-6.95-79.55v5.4 M-16.25-79.55
l4.65-2.65 M-11.6-87.6l-4.65-2.7 M-6.95-95.7v5.4 M-44.2-90.3l4.65,2.7 M-62.8-47.2l-4.65-2.65 M-44.2-47.2l-4.65-2.65
M-30.2-49.85l-4.65,2.65 M-2.3-49.85l-4.65,2.65 M-95.4-1.35v-5.4l4.65-2.7l4.65,2.7v5.4l-4.65,2.7 M-86.1-6.75l4.7-2.7
M-100.05,1.35l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7 M-95.4-17.5l-4.65,2.65l-4.65-2.65v-5.4l4.65-2.7l4.65,2.7V-17.5z M-95.4-22.9
l4.65-2.7l4.65,2.7v5.4l-4.65,2.65l-4.65-2.65 M-81.4-14.85l-4.7-2.65 M-100.05-9.45v-5.4 M-90.75-14.85v5.4 M-109.35-9.45
l4.65,2.7 M-104.7-17.5l-4.65,2.65 M-109.35-41.8l4.65,2.7v5.4l-4.65,2.7 M-90.75-25.6V-31l4.65-2.7l4.7,2.7v5.4l-4.7,2.7
M-100.05-25.6V-31l4.65-2.7l4.65,2.7 M-100.05-47.2l4.65-2.65l4.65,2.65v5.4l-4.65,2.7l-4.65-2.7V-47.2l-4.65-2.65 M-90.75-41.8
l4.65,2.7v5.4 M-86.1-39.1l4.7-2.7 M-95.4-33.7v-5.4 M-109.35-25.6l4.65,2.7 M-100.05-31l-4.65-2.7 M-104.7-39.1l4.65-2.7
M-95.4-49.85v-5.4l4.65-2.7l4.65,2.7v5.4l-4.65,2.65 M-86.1-55.25l4.7-2.7 M-95.4-55.25l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7v5.4
M-100.05-63.35l-4.65-2.7v-5.4l4.65-2.7l4.65,2.7v5.4 M-95.4-71.45l4.65-2.7l4.65,2.7 M-86.1-66.05l-4.65,2.7 M-109.35-57.95
l4.65,2.7v5.4l-4.65,2.65 M-109.35-74.15l4.65,2.7 M-104.7-66.05l-4.65,2.7 M-109.35-90.3l4.65,2.7v5.4l-4.65,2.65 M-81.4-79.55
l-4.7-2.65v-5.4l4.7-2.7l4.65,2.7 M-86.1-82.2l-4.65,2.65l-4.65-2.65v-5.4l4.65-2.7l4.65,2.7 M-100.05-74.15v-5.4l4.65-2.65
M-95.4-87.6l-4.65-2.7v-5.4 M-81.4-95.7v5.4 M-90.75-95.7v5.4 M-90.75-79.55v5.4 M-104.7-55.25l4.65-2.7 M-100.05-79.55
l-4.65-2.65 M-104.7-87.6l4.65-2.7 M-81.4-47.2l-4.7-2.65 M-76.75-33.7L-81.4-31 M-81.4-25.6l4.65,2.7 M-67.45-1.35l-4.65,2.7
M-104.7-1.35l-4.65,2.7 M-81.4,1.35l-4.7-2.7 M-25.55,1.35l-4.65-2.7 M-53.5,1.35l4.65-2.7 M-2.3-1.35l-4.65,2.7 M-2.3,57.95
l4.65-2.7 M2.35,66.05l-4.65-2.7 M-2.3-39.1l4.65-2.7 M2.35-31l-4.65-2.7 M2.35-79.55L-2.3-82.2 M-2.3-87.6l4.65-2.7 M-2.3,9.45
l4.65-2.7 M2.35,17.5l-4.65-2.65"/>
</symbol>
<g id="Layer_1">
</g>
<g id="Layer_6">
<path fill-rule="evenodd" clip-rule="evenodd" fill="#EFEEEE" d="M217.5,108.95c0,29.833-10.533,55.399-31.6,76.7
c-0.7,0.833-1.484,1.6-2.351,2.3c-3.466,3.399-7.134,6.483-11,9.25c-18.267,13.467-39.366,20.2-63.3,20.2
c-23.967,0-45.033-6.733-63.2-20.2c-4.8-3.4-9.3-7.25-13.5-11.55c-16.367-16.267-26.417-35.167-30.15-56.7
c-0.733-4.2-1.217-8.467-1.45-12.8c-0.1-2.4-0.15-4.801-0.15-7.2c0-2.534,0.05-4.95,0.15-7.25c0-0.233,0.066-0.467,0.2-0.7
c1.567-26.6,12.033-49.583,31.4-68.95C53.85,11.017,79.417,0.5,109.25,0.5c29.934,0,55.483,10.517,76.65,31.55
C206.967,53.483,217.5,79.117,217.5,108.95z"/>
</g>
<g id="Layer_5">
<path fill-rule="evenodd" clip-rule="evenodd" fill="#010101" d="M195.45,43l-22.4,22.4c-8.833,13-13.25,27.867-13.25,44.6
c0,17.934,5.067,33.833,15.2,47.7l19,18.95c-2.5,3.066-5.2,6.066-8.1,9c-0.7,0.833-1.484,1.6-2.351,2.3
c-2.533,2.5-5.167,4.816-7.899,6.95L158.1,177.35c-13.934-10.733-30.133-16.1-48.6-16.1c-17.933,0-33.833,5.1-47.7,15.3
L43.25,195.15c-3.767-2.867-7.333-6.034-10.7-9.5c-2.8-2.801-5.417-5.667-7.85-8.601l19.15-19.2
c10.066-13.966,15.1-29.916,15.1-47.85c0-17.5-4.867-33.017-14.6-46.55l-21.05-21c2.833-3.6,5.917-7.067,9.25-10.4
c2.934-2.867,5.934-5.55,9-8.05L61.9,44.35C75.7,54.583,91.567,59.7,109.5,59.7c18.467,0,34.666-5.367,48.6-16.1L177.4,24.35
c2.899,2.367,5.732,4.933,8.5,7.7C189.367,35.583,192.55,39.233,195.45,43z"/>
</g>
<g id="Layer_4">
<defs>
<path id="SVGID_1_" d="M159.8,110c0-16.733,4.417-31.6,13.25-44.6l22.4-22.4c-2.9-3.767-6.083-7.417-9.55-10.95
c-2.768-2.767-5.601-5.333-8.5-7.7L158.1,43.6c-13.934,10.733-30.133,16.1-48.6,16.1c-17.933,0-33.8-5.117-47.6-15.35L41.55,24
c-3.066,2.5-6.066,5.183-9,8.05c-3.333,3.333-6.417,6.8-9.25,10.4l21.05,21c9.733,13.533,14.6,29.05,14.6,46.55
c0,17.934-5.034,33.884-15.1,47.85l-19.15,19.2c2.433,2.934,5.05,5.8,7.85,8.601c3.367,3.466,6.934,6.633,10.7,9.5L61.8,176.55
c13.867-10.2,29.767-15.3,47.7-15.3c18.467,0,34.666,5.366,48.6,16.1L175.65,194.9c2.732-2.134,5.366-4.45,7.899-6.95
c0.866-0.7,1.65-1.467,2.351-2.3c2.899-2.934,5.6-5.934,8.1-9l-19-18.95C164.867,143.833,159.8,127.934,159.8,110z"/>
</defs>
<clipPath id="SVGID_2_">
<use xlink:href="#SVGID_1_" overflow="visible"/>
</clipPath>
<g clip-path="url(#SVGID_2_)">
<use xlink:href="#hex_grid" width="228.55" height="197.233" x="-114.25" y="-98.617" transform="matrix(1.1415 0 0 -1.1415 105.5 107.75)" overflow="visible"/>
</g>
</g>
<g id="Layer_2">
<g>
<path fill-rule="evenodd" clip-rule="evenodd" fill="#35C6F4" d="M79.1,110.95c-0.033-0.667-0.05-1.333-0.05-2
c0-0.7,0.017-1.366,0.05-2c0-0.067,0.017-0.134,0.05-0.2c0.434-7.367,3.333-13.733,8.7-19.1c5.9-5.833,12.983-8.75,21.25-8.75
c8.301,0,15.384,2.917,21.25,8.75c5.834,5.934,8.75,13.033,8.75,21.3c0,8.267-2.916,15.35-8.75,21.25
c-0.199,0.233-0.416,0.45-0.649,0.649c-0.967,0.934-1.983,1.784-3.05,2.551c-5.066,3.733-10.917,5.6-17.551,5.6
c-6.633,0-12.466-1.866-17.5-5.6c-1.333-0.934-2.583-2-3.75-3.2c-4.533-4.5-7.317-9.733-8.35-15.7
C79.3,113.334,79.167,112.15,79.1,110.95z M126.1,127.25l3.601,3.6L126.1,127.25z"/>
<path fill-rule="evenodd" clip-rule="evenodd" fill="none" stroke="#35C6F4" stroke-width="2" stroke-miterlimit="1" d="
M158.6,60.25l-15,14.65 M31.7,33.1l40.75,40.65 M126.1,127.25l3.601,3.6 M157.05,158l27.65,28.6 M153.05,153.95l-10.75-11.2
M186.6,33l-28,27.25 M33.15,186.25l27.35-27.4"/>
<path fill-rule="evenodd" clip-rule="evenodd" fill="none" stroke="#35C6F4" stroke-width="7" stroke-miterlimit="1" d="
M158.6,60.25l-16.949,17.2 M59.4,61.35L76.6,78.5 M60.5,158.85l16.75-17.399 M153.05,153.95l4,4.05 M139.45,140.4l13.6,13.55"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 26 KiB

+5 -8
View File
@@ -114,8 +114,8 @@ Function PackageMono()
get-childitem $outputFolderMono -File -Filter sqlite3.* -Recurse | foreach ($_) {remove-item $_.fullname}
get-childitem $outputFolderMono -File -Filter MediaInfo.* -Recurse | foreach ($_) {remove-item $_.fullname}
Write-Host "Adding MediaInfoDotNet.dll.config (for dllmap)"
Copy-Item "$sourceFolder\MediaInfoDotNet.dll.config" $outputFolderMono
Write-Host "Adding NzbDrone.Core.dll.config (for dllmap)"
Copy-Item "$sourceFolder\NzbDrone.Core\NzbDrone.Core.dll.config" $outputFolderMono
Write-Host Renaming NzbDrone.Console.exe to NzbDrone.exe
Get-ChildItem $outputFolderMono -File -Filter "NzbDrone.exe*" -Recurse | foreach ($_) {remove-item $_.fullname}
@@ -212,8 +212,8 @@ Function PackageTests()
CleanFolder $testPackageFolder $true
Write-Host "Adding MediaInfoDotNet.dll.config (for dllmap)"
Copy-Item "$sourceFolder\MediaInfoDotNet.dll.config" -Destination $testPackageFolder -Force
Write-Host "Adding NzbDrone.Core.dll.config (for dllmap)"
Copy-Item "$sourceFolder\NzbDrone.Core\NzbDrone.Core.dll.config" -Destination $testPackageFolder -Force
Write-Host "##teamcity[progressFinish 'Creating Test Package']"
}
@@ -221,15 +221,12 @@ Function PackageTests()
Function RunGulp()
{
Write-Host "##teamcity[progressStart 'Running Gulp']"
$gulpPath = '.\node_modules\gulp\bin\gulp'
Invoke-Expression 'npm install'
CheckExitCode
Invoke-Expression ('node ' + $gulpPath + ' build') -ErrorAction Continue -Verbose
Invoke-Expression 'gulp build' -ErrorAction Continue -Verbose
CheckExitCode
Remove-Item $outputFolder\UI\build.txt -ErrorAction Continue
Write-Host "##teamcity[progressFinish 'Running Gulp']"
}
+50
View File
@@ -0,0 +1,50 @@
<Query Kind="Program" />
void Main()
{
var files = Directory.GetFiles("c:\\git\\sonarr\\src\\UI","*.js", SearchOption.AllDirectories);
var moduleRegex = new Regex(@"module.exports\s*=\s*\(function\s*\(\)\s*{\n\s*return\s*(\w|\W)*\)\.call\(this\);$");
var functionHead = new Regex(@"\s*\(function\s*\(\)\s*{\n\s*return\s*");
var functionTail = new Regex(@"\}\).call\(this\);$");
var multiVar = new Regex(@"^(?<d>var\s*\w*\s*=\s*require\(.*\)),");
var seperateDeclearatuin = new Regex(@"^((\w|\$|_)*\s=\srequire\(.*\))(,|;)", RegexOptions.Multiline);
foreach (var filePath in files)
{
var text = File.ReadAllText(filePath);
var newContent = text.Replace("// Generated by uRequire v0.7.0-beta.14 template: 'nodejs'","");
newContent = newContent.Replace("var __isAMD = !!(typeof define === 'function' && define.amd),","");
newContent = newContent.Replace("__isNode = (typeof exports === 'object'),","");
newContent = newContent.Replace("__isWeb = !__isNode;","");
newContent = newContent.Replace("\"use strict\";","'use strict';");
newContent = newContent.Trim();
if(moduleRegex.IsMatch(newContent))
{
filePath.Dump();
newContent = functionHead.Replace(newContent," ");
newContent = functionTail.Replace(newContent,"");
}
if(multiVar.IsMatch(newContent))
{
newContent = multiVar.Replace(newContent,"$1;"); //first one
}
newContent = seperateDeclearatuin.Replace(newContent,"var $1;"); //ones after
newContent.Replace("var $ = require('jquery'), var","var $ = require('jquery');");
File.WriteAllText(filePath,newContent.Trim());
}
}
// Define other methods and classes here
+1 -2
View File
@@ -2,12 +2,11 @@ var gulp = require('gulp');
var runSequence = require('run-sequence');
require('./clean');
require('./requirejs');
require('./less');
require('./handlebars');
require('./copy');
gulp.task('build', function () {
return runSequence('clean',
['requireJs', 'less', 'handlebars', 'copyIndex', 'copyContent']);
['webpack', 'less', 'handlebars', 'copyHtml', 'copyContent', 'copyJs']);
});
+8 -4
View File
@@ -5,15 +5,19 @@ var cache = require('gulp-cached');
var paths = require('./paths.js');
gulp.task('copyJs', function () {
return gulp.src(paths.src.scripts)
return gulp.src(
[
paths.src.root + "polyfills.js",
paths.src.root + "JsLibraries/handlebars.runtime.js",
])
.pipe(cache('copyJs'))
.pipe(print())
.pipe(gulp.dest(paths.dest.root));
});
gulp.task('copyIndex', function () {
return gulp.src(paths.src.index)
.pipe(cache('copyIndex'))
gulp.task('copyHtml', function () {
return gulp.src(paths.src.html)
.pipe(cache('copyHtml'))
.pipe(gulp.dest(paths.dest.root));
});
+1 -1
View File
@@ -1,12 +1,12 @@
require('./watch.js');
require('./build.js');
require('./clean.js');
require('./requirejs.js');
require('./jshint.js');
require('./handlebars.js');
require('./copy.js');
require('./less.js');
require('./stripBom.js');
require('./imageMin.js');
require('./webpack.js');
+4 -9
View File
@@ -2,18 +2,17 @@ var gulp = require('gulp');
var handlebars = require('gulp-handlebars');
var declare = require('gulp-declare');
var concat = require('gulp-concat');
var wrapAmd = require('gulp-wrap-amd');
var wrap = require("gulp-wrap");
var path = require('path');
var streamqueue = require('streamqueue');
var stripbom = require('gulp-stripbom');
var paths = require('./paths.js');
var bom = require('./pipelines/gulp-bom.js');
gulp.task('handlebars', function () {
var coreStream = gulp.src([paths.src.templates, '!*/**/*Partial.*'])
.pipe(bom())
.pipe(stripbom({ showLog: false }))
.pipe(handlebars())
.pipe(declare({
namespace: 'T',
@@ -30,7 +29,7 @@ gulp.task('handlebars', function () {
}));
var partialStream = gulp.src([paths.src.partials])
.pipe(bom())
.pipe(stripbom({ showLog: false }))
.pipe(handlebars())
.pipe(wrap('Handlebars.template(<%= contents %>)'))
.pipe(wrap('Handlebars.registerPartial(<%= processPartialName(file.relative) %>, <%= contents %>)', {}, {
@@ -48,10 +47,6 @@ gulp.task('handlebars', function () {
partialStream,
coreStream
).pipe(concat('templates.js'))
.pipe(wrapAmd({
deps: ['handlebars'],
params: ['Handlebars'],
exports: 'this["T"]'
}))
.pipe(gulp.dest(paths.dest.root));
});
+1 -14
View File
@@ -8,19 +8,6 @@ var paths = require('./paths.js');
gulp.task('jshint', function () {
return gulp.src([paths.src.scripts, paths.src.exclude.libs])
.pipe(cache('jshint'))
.pipe(jshint({
'-W030': false,
'-W064': false,
'-W097': false, //Use the function form of “use strict”
'-W100': false, //Silently deleted characters (in locales)
'undef': true,
'globals': {
'require': true,
'define': true,
'window': true,
'document': true,
'console': true
}
}))
.pipe(jshint())
.pipe(jshint.reporter(stylish));
});
+1
View File
@@ -15,6 +15,7 @@ gulp.task('less', function () {
paths.src.root + 'AddSeries/addSeries.less',
paths.src.root + 'Calendar/calendar.less',
paths.src.root + 'Cells/cells.less',
paths.src.root + 'ManualImport/manualimport.less',
paths.src.root + 'Settings/settings.less',
paths.src.root + 'System/Logs/logs.less',
paths.src.root + 'System/Update/update.less',
+1 -1
View File
@@ -2,7 +2,7 @@ module.exports = {
src: {
root: './src/UI/',
templates: './src/UI/**/*.hbs',
index: './src/UI/index.html',
html: './src/UI/*.html',
partials: './src/UI/**/*Partial.hbs',
scripts: './src/UI/**/*.js',
less: ['./src/UI/**/*.less'],
-4
View File
@@ -1,4 +0,0 @@
var replace = require('gulp-replace');
module.exports = function() {
return replace(/^\uFEFF/, '');
};
-32
View File
@@ -1,32 +0,0 @@
var gulp = require('gulp');
var requirejs = require('requirejs');
var paths = require('./paths');
require('./handlebars.js');
require('./jshint.js');
gulp.task('requireJs', ['jshint'], function (cb) {
var config = {
mainConfigFile: 'src/UI/app.js',
fileExclusionRegExp: /^.*\.(?!js$)[^.]+$/,
preserveLicenseComments: false,
dir: paths.dest.root,
optimize: 'none',
removeCombined: true,
inlineText: false,
keepBuildDir: true,
modules: [
{
name: 'app',
exclude: ['templates.js']
}
]};
requirejs.optimize(config, function (buildResponse) {
console.log(buildResponse);
cb();
});
});
+3 -11
View File
@@ -1,22 +1,14 @@
var gulp = require('gulp');
var paths = require('./paths.js');
var bom = require('./pipelines/gulp-bom.js');
var gulpPrint = require('gulp-print');
var stripbom = require('gulp-stripbom');
var stripBom = function (dest) {
gulp.src([paths.src.scripts, paths.src.exclude.libs])
.pipe(bom())
.pipe(gulpPrint(function (filepath) {
return "booming: " + filepath;
}))
.pipe(stripbom({ showLog: false }))
.pipe(gulp.dest(dest));
gulp.src(paths.src.templates)
.pipe(bom())
.pipe(gulpPrint(function (filepath) {
return "booming: " + filepath;
}))
.pipe(stripbom({ showLog: false }))
.pipe(gulp.dest(dest));
};
+8 -5
View File
@@ -8,22 +8,25 @@ require('./jshint.js');
require('./handlebars.js');
require('./less.js');
require('./copy.js');
require('./webpack.js');
gulp.task('watch', ['jshint', 'handlebars', 'less', 'copyJs','copyIndex', 'copyContent'], function () {
gulp.watch([paths.src.scripts, paths.src.exclude.libs], ['jshint', 'copyJs']);
gulp.task('watch', ['jshint', 'handlebars', 'less','copyHtml', 'copyContent','copyJs'], function () {
gulp.start('webpackWatch');
gulp.watch([paths.src.scripts, paths.src.exclude.libs], ['jshint','copyJs']);
gulp.watch(paths.src.templates, ['handlebars']);
gulp.watch([paths.src.less, paths.src.exclude.libs], ['less']);
gulp.watch([paths.src.index], ['copyIndex']);
gulp.watch([paths.src.html], ['copyHtml']);
gulp.watch([paths.src.content + '**/*.*', '!**/*.less'], ['copyContent']);
});
gulp.task('liveReload', ['jshint', 'handlebars', 'less', 'copyJs'], function () {
gulp.task('liveReload', ['jshint', 'handlebars', 'less', 'webPack'], function () {
var server = livereload();
gulp.watch([
'app/**/*.js',
'app/**/*.css',
'app/index.html'
'app/index.html',
'app/login.html'
]).on('change', function (file) {
server.changed(file.path);
});
+20
View File
@@ -0,0 +1,20 @@
var gulp = require('gulp');
var gulpWebpack = require('gulp-webpack');
var webpack = require('webpack');
var webpackConfig = require('../webpack.config');
webpackConfig.devtool = "#source-map";
gulp.task('webpack', function() {
return gulp.src('main.js')
.pipe(gulpWebpack(webpackConfig, webpack))
.pipe(gulp.dest(''));
});
gulp.task('webpackWatch', function() {
webpackConfig.watch = true;
return gulp.src('main.js')
.pipe(gulpWebpack(webpackConfig, webpack))
.pipe(gulp.dest(''));
});
+1 -1
View File
@@ -1 +1 @@
require('./gulp/gulpfile.js');
require('./gulp/gulpFile.js');
+3 -3
View File
@@ -2,6 +2,6 @@ EXCLUDE="-exclude:Windows -include:IntegrationTest"
TESTDIR="."
NUNIT="$TESTDIR/NUnit.Runners.2.6.1/tools/nunit-console-x86.exe"
mono --debug $NUNIT $EXCLUDE -xml:NzbDrone.Api.Result.xml $TESTDIR/NzbDrone.Api.Test.dll
mono --debug $NUNIT $EXCLUDE -xml:NzbDrone.Core.Result.xml $TESTDIR/NzbDrone.Core.Test.dll
mono --debug $NUNIT $EXCLUDE -xml:NzbDrone.Integration.Result.xml $TESTDIR/NzbDrone.Integration.Test.dll
mono --debug --runtime=v4.0 $NUNIT $EXCLUDE -xml:NzbDrone.Api.Result.xml $TESTDIR/NzbDrone.Api.Test.dll
mono --debug --runtime=v4.0 $NUNIT $EXCLUDE -xml:NzbDrone.Core.Result.xml $TESTDIR/NzbDrone.Core.Test.dll
mono --debug --runtime=v4.0 $NUNIT $EXCLUDE -xml:NzbDrone.Integration.Result.xml $TESTDIR/NzbDrone.Integration.Test.dll
+1 -1
View File
@@ -4,7 +4,7 @@
DIR=$(cd "$(dirname "$0")"; pwd)
#change these values to match your app
EXE_PATH="$DIR/nzbdrone.exe"
EXE_PATH="$DIR/NzbDrone.exe"
APPNAME="Sonarr"
#set up environment
+17 -14
View File
@@ -1,8 +1,8 @@
{
"name": "Sonarr",
"version": "0.0.0",
"version": "2.0.0",
"description": "Sonarr",
"main": "index.js",
"main": "main.js",
"scripts": {
"preinstall": ""
},
@@ -15,22 +15,25 @@
"gitHead": "9ff7aa1bf7fe38c4c5bdb92f56c8ad556916ed67",
"readmeFilename": "readme.md",
"dependencies": {
"fs-extra": "0.12.0",
"del": "1.1.1",
"gulp": "3.8.10",
"gulp-cached": "1.0.1",
"del": "0.1.3",
"gulp-concat": "2.4.2",
"gulp-cached": "1.0.2",
"gulp-concat": "2.4.3",
"gulp-declare": "0.3.0",
"gulp-handlebars": "2.2.0",
"gulp-jshint": "1.9.0",
"gulp-less": "1.3.6",
"gulp-handlebars": "3.0.1",
"gulp-jshint": "1.9.2",
"gulp-less": "2.0.1",
"gulp-print": "1.1.0",
"gulp-replace": "0.5.0",
"gulp-wrap": "0.5.0",
"gulp-wrap-amd": "0.3.1",
"gulp-replace": "0.5.2",
"gulp-stripbom": "1.0.4",
"gulp-run": "1.6.6",
"gulp-webpack": "1.2.0",
"gulp-wrap": "0.10.1",
"handlebars": "2.0.0",
"jshint-loader": "0.8.1",
"jshint-stylish": "1.0.0",
"requirejs": "2.1.15",
"run-sequence": "1.0.2",
"streamqueue": "0.1.1"
"streamqueue": "0.1.1",
"webpack": "1.5.3"
}
}
+86
View File
@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="utf-8" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://torznab.com/schemas/2015/feed"
xmlns:torznab="http://torznab.com/schemas/2015/feed">
<xs:simpleType name="attrNames">
<xs:restriction base="xs:string">
<!-- https://github.com/nZEDb/nZEDb/blob/master/docs/newznab_api_specification.txt -->
<!-- http://newznab.readthedocs.org/en/latest/misc/api/ -->
<!-- Original newznab attributes -->
<!-- All -->
<xs:enumeration value="size" />
<xs:enumeration value="category" />
<xs:enumeration value="guid" />
<xs:enumeration value="poster" />
<xs:enumeration value="team" />
<xs:enumeration value="grabs" />
<xs:enumeration value="comments" />
<xs:enumeration value="year" />
<!-- TV -->
<xs:enumeration value="season" />
<xs:enumeration value="episode" />
<xs:enumeration value="rageid" />
<xs:enumeration value="tvtitle" />
<xs:enumeration value="tvairdate" />
<!-- TV, Movies, Audio -->
<xs:enumeration value="video" />
<xs:enumeration value="audio" />
<xs:enumeration value="resolution" />
<xs:enumeration value="framerate" />
<xs:enumeration value="language" />
<xs:enumeration value="subs" />
<!-- Movies -->
<xs:enumeration value="imdb" />
<xs:enumeration value="imdbscore" />
<xs:enumeration value="imdbtitle" />
<xs:enumeration value="imdbtagline" />
<xs:enumeration value="imdbscore" />
<xs:enumeration value="imdbtitle" />
<xs:enumeration value="imdbtagline" />
<xs:enumeration value="imdbplot" />
<xs:enumeration value="imdbyear" />
<xs:enumeration value="imdbdirector" />
<xs:enumeration value="imdbactors" />
<!-- TV, Movies -->
<xs:enumeration value="genre" />
<!-- Music -->
<xs:enumeration value="artist" />
<xs:enumeration value="album" />
<xs:enumeration value="publisher" />
<xs:enumeration value="tracks" />
<!-- Mixed -->
<xs:enumeration value="coverurl" />
<xs:enumeration value="backdropcoverurl" />
<xs:enumeration value="review" />
<!-- Book -->
<xs:enumeration value="booktitle" />
<xs:enumeration value="publishdate" />
<xs:enumeration value="author" />
<xs:enumeration value="pages" />
<!-- Generic extensions -->
<xs:enumeration value="type" /> <!-- series|movie|music|book if unknown just omit -->
<xs:enumeration value="tvdbid" />
<xs:enumeration value="bannerurl" />
<!-- Nzb extensions -->
<xs:enumeration value="nzbhash" /> <!-- TBD, hash of sorted article headers of relevant content (relevant excludes stuff like par,nfo,nzb etc) -->
<!-- Torrent extensions -->
<xs:enumeration value="infohash" />
<xs:enumeration value="magneturl" />
<xs:enumeration value="seeders" />
<xs:enumeration value="leechers" />
<xs:enumeration value="peers" /> <!-- seeders + leechers -->
<xs:enumeration value="seedtype" /> <!-- TBD, which criteria must be met. was going for 'ratio,seedtime,both' but afaik it's always 'either' -->
<xs:enumeration value="minimumratio" />
<xs:enumeration value="minimumseedtime" />
</xs:restriction>
</xs:simpleType>
<xs:element name="attr">
<xs:complexType>
<xs:attribute name="name" type="torznab:attrNames" />
<xs:attribute name="value" type="xs:string" />
</xs:complexType>
</xs:element>
</xs:schema>
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="NLog" version="2.1.0" targetFramework="net40" />
</packages>
+1 -1
View File
@@ -144,7 +144,7 @@
<PreBuildEvent>
</PreBuildEvent>
</PropertyGroup>
<Import Project="$(SolutionDir)\.nuget\nuget.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
-7
View File
@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<dllmap os="osx" dll="MediaInfo.dll" target="libmediainfo.0.dylib"/>
<dllmap os="linux" dll="MediaInfo.dll" target="libmediainfo.so.0" />
<dllmap os="freebsd" dll="MediaInfo.dll" target="libmediainfo.so.0" />
<dllmap os="solaris" dll="MediaInfo.dll" target="libmediainfo.so.0.0.0" />
</configuration>
@@ -122,7 +122,7 @@ namespace Microsoft.AspNet.SignalR
{
if (user == null)
{
throw new ArgumentNullException("user");
return false;
}
if (!user.Identity.IsAuthenticated)
@@ -20,8 +20,12 @@ namespace Microsoft.AspNet.SignalR.Hosting
throw new ArgumentNullException("instanceName");
}
// Initialize the performance counters
resolver.InitializePerformanceCounters(instanceName, hostShutdownToken);
// Performance counters are broken on mono so just skip this step
if (!MonoUtility.IsRunningMono)
{
// Initialize the performance counters
resolver.InitializePerformanceCounters(instanceName, hostShutdownToken);
}
// Dispose the dependency resolver on host shut down (cleanly)
resolver.InitializeResolverDispose(hostShutdownToken);
@@ -41,12 +45,11 @@ namespace Microsoft.AspNet.SignalR.Hosting
// TODO: Guard against multiple calls to this
// When the host triggers the shutdown token, dispose the resolver
hostShutdownToken.Register(state =>
hostShutdownToken.SafeRegister(state =>
{
((IDependencyResolver)state).Dispose();
},
resolver,
useSynchronizationContext: false);
resolver);
}
}
}
@@ -17,6 +17,11 @@ namespace Microsoft.AspNet.SignalR.Hosting
/// </summary>
CancellationToken CancellationToken { get; }
/// <summary>
/// Gets or sets the status code of the response.
/// </summary>
int StatusCode { get; set; }
/// <summary>
/// Gets or sets the content type of the response.
/// </summary>
@@ -16,9 +16,9 @@ namespace Microsoft.AspNet.SignalR.Hosting
Action<string> OnMessage { get; set; }
/// <summary>
/// Invoked when the websocket gracefully closes
/// Invoked when the websocket closes
/// </summary>
Action<bool> OnClose { get; set; }
Action OnClose { get; set; }
/// <summary>
/// Invoked when there is an error
@@ -11,6 +11,7 @@ namespace Microsoft.AspNet.SignalR.Hosting
/// Accepts an websocket request using the specified user function.
/// </summary>
/// <param name="callback">The callback that fires when the websocket is ready.</param>
Task AcceptWebSocketRequest(Func<IWebSocket, Task> callback);
/// <param name="initTask">The task that completes when the websocket transport is ready.</param>
Task AcceptWebSocketRequest(Func<IWebSocket, Task> callback, Task initTask);
}
}
@@ -358,7 +358,7 @@ namespace Microsoft.AspNet.SignalR.Hubs
private Task ExecuteHubEvent(IRequest request, string connectionId, Func<IHub, Task> action)
{
var hubs = GetHubs(request, connectionId).ToList();
var operations = hubs.Select(instance => action(instance).Catch().OrEmpty()).ToArray();
var operations = hubs.Select(instance => action(instance).OrEmpty().Catch()).ToArray();
if (operations.Length == 0)
{
@@ -0,0 +1,34 @@
using System;
using Microsoft.AspNet.SignalR.Hosting;
namespace Microsoft.AspNet.SignalR.Infrastructure
{
/// <summary>
/// A buffering text writer that supports writing binary directly as well
/// </summary>
internal unsafe class BinaryTextWriter : BufferTextWriter, IBinaryWriter
{
public BinaryTextWriter(IResponse response) :
base((data, state) => ((IResponse)state).Write(data), response, reuseBuffers: true, bufferSize: 128)
{
}
public BinaryTextWriter(IWebSocket socket) :
base((data, state) => ((IWebSocket)state).SendChunk(data), socket, reuseBuffers: false, bufferSize: 1024)
{
}
public BinaryTextWriter(Action<ArraySegment<byte>, object> write, object state, bool reuseBuffers, int bufferSize) :
base(write, state, reuseBuffers, bufferSize)
{
}
public void Write(ArraySegment<byte> data)
{
Writer.Write(data);
}
}
}
@@ -13,7 +13,7 @@ namespace Microsoft.AspNet.SignalR.Infrastructure
/// we don't need to write to a long lived buffer. This saves massive amounts of memory
/// as the number of connections grows.
/// </summary>
internal unsafe class BufferTextWriter : TextWriter, IBinaryWriter
internal abstract unsafe class BufferTextWriter : TextWriter
{
private readonly Encoding _encoding;
@@ -31,13 +31,13 @@ namespace Microsoft.AspNet.SignalR.Infrastructure
}
public BufferTextWriter(IWebSocket socket) :
this((data, state) => ((IWebSocket)state).SendChunk(data), socket, reuseBuffers: false, bufferSize: 128)
this((data, state) => ((IWebSocket)state).SendChunk(data), socket, reuseBuffers: false, bufferSize: 1024 * 4)
{
}
[SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.IO.TextWriter.#ctor", Justification = "It won't be used")]
public BufferTextWriter(Action<ArraySegment<byte>, object> write, object state, bool reuseBuffers, int bufferSize)
protected BufferTextWriter(Action<ArraySegment<byte>, object> write, object state, bool reuseBuffers, int bufferSize)
{
_write = write;
_writeState = state;
@@ -46,7 +46,7 @@ namespace Microsoft.AspNet.SignalR.Infrastructure
_bufferSize = bufferSize;
}
private ChunkedWriter Writer
protected internal ChunkedWriter Writer
{
get
{
@@ -79,17 +79,12 @@ namespace Microsoft.AspNet.SignalR.Infrastructure
Writer.Write(value);
}
public void Write(ArraySegment<byte> data)
{
Writer.Write(data);
}
public override void Flush()
{
Writer.Flush();
}
private class ChunkedWriter
internal class ChunkedWriter
{
private int _charPos;
private int _charLen;
@@ -1,6 +1,9 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;
namespace Microsoft.AspNet.SignalR.Infrastructure
@@ -48,8 +51,14 @@ namespace Microsoft.AspNet.SignalR.Infrastructure
// This normally waits until the callback is finished invoked but we don't care
if (_callbackWrapper.TrySetInvoked())
{
// Bug #1549, .NET 4.0 has a bug where this throws if the CTS
_registration.Dispose();
try
{
_registration.Dispose();
}
catch (ObjectDisposedException)
{
// Bug #1549, .NET 4.0 has a bug where this throws if the CTS is disposed.
}
}
}
}
@@ -144,7 +144,7 @@ namespace Microsoft.AspNet.SignalR.Infrastructure
{
using (var stream = new MemoryStream(128))
{
var bufferWriter = new BufferTextWriter((buffer, state) =>
var bufferWriter = new BinaryTextWriter((buffer, state) =>
{
((MemoryStream)state).Write(buffer.Array, buffer.Offset, buffer.Count);
},
@@ -236,8 +236,7 @@ namespace Microsoft.AspNet.SignalR.Infrastructure
if (command == null)
{
var platform = (int)Environment.OSVersion.Platform;
if (platform == 4 || platform == 6 || platform == 128)
if (MonoUtility.IsRunningMono)
{
return;
}
@@ -0,0 +1,31 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace Microsoft.AspNet.SignalR.Infrastructure
{
internal static class MonoUtility
{
private static readonly Lazy<bool> _isRunningMono = new Lazy<bool>(() => CheckRunningOnMono());
internal static bool IsRunningMono
{
get
{
return _isRunningMono.Value;
}
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "This should never fail")]
private static bool CheckRunningOnMono()
{
try
{
return Type.GetType("Mono.Runtime") != null;
}
catch
{
return false;
}
}
}
}
@@ -35,7 +35,7 @@ namespace Microsoft.AspNet.SignalR.Infrastructure
_maxSize = maxSize;
}
#if !CLIENT_NET45
#if !CLIENT_NET45 && !CLIENT_NET4 && !NETFX_CORE && !SILVERLIGHT
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This is shared code.")]
public IPerformanceCounter QueueSizeCounter { get; set; }
#endif
@@ -62,19 +62,16 @@ namespace Microsoft.AspNet.SignalR.Infrastructure
if (_maxSize != null)
{
if (Interlocked.Read(ref _size) == _maxSize)
// Increment the size if the queue
if (Interlocked.Increment(ref _size) > _maxSize)
{
// REVIEW: Do we need to make the contract more clear between the
// queue full case and the queue drained case? Should we throw an exeception instead?
Interlocked.Decrement(ref _size);
// We failed to enqueue because the size limit was reached
return null;
}
// Increment the size if the queue
Interlocked.Increment(ref _size);
#if !CLIENT_NET45
#if !CLIENT_NET45 && !CLIENT_NET4 && !NETFX_CORE && !SILVERLIGHT
var counter = QueueSizeCounter;
if (counter != null)
{
@@ -93,7 +90,7 @@ namespace Microsoft.AspNet.SignalR.Infrastructure
// Decrement the number of items left in the queue
Interlocked.Decrement(ref queue._size);
#if !CLIENT_NET45
#if !CLIENT_NET45 && !CLIENT_NET4 && !NETFX_CORE && !SILVERLIGHT
var counter = QueueSizeCounter;
if (counter != null)
{
@@ -33,8 +33,10 @@ namespace Microsoft.AspNet.SignalR.Messaging
_escapedKey = minifiedKey;
}
public static void WriteCursors(TextWriter textWriter, IList<Cursor> cursors)
public static void WriteCursors(TextWriter textWriter, IList<Cursor> cursors, string prefix)
{
textWriter.Write(prefix);
for (int i = 0; i < cursors.Count; i++)
{
if (i > 0)
@@ -48,7 +50,7 @@ namespace Microsoft.AspNet.SignalR.Messaging
}
}
private static void WriteUlongAsHexToBuffer(ulong value, TextWriter textWriter)
internal static void WriteUlongAsHexToBuffer(ulong value, TextWriter textWriter)
{
// This tracks the length of the output and serves as the index for the next character to be written into the pBuffer.
// The length could reach up to 16 characters, so at least that much space should remain in the pBuffer.
@@ -114,17 +116,17 @@ namespace Microsoft.AspNet.SignalR.Messaging
return sb.ToString();
}
public static List<Cursor> GetCursors(string cursor)
public static List<Cursor> GetCursors(string cursor, string prefix)
{
return GetCursors(cursor, s => s);
return GetCursors(cursor, prefix, s => s);
}
public static List<Cursor> GetCursors(string cursor, Func<string, string> keyMaximizer)
public static List<Cursor> GetCursors(string cursor, string prefix, Func<string, string> keyMaximizer)
{
return GetCursors(cursor, (key, state) => ((Func<string, string>)state).Invoke(key), keyMaximizer);
return GetCursors(cursor, prefix, (key, state) => ((Func<string, string>)state).Invoke(key), keyMaximizer);
}
public static List<Cursor> GetCursors(string cursor, Func<string, object, string> keyMaximizer, object state)
public static List<Cursor> GetCursors(string cursor, string prefix, Func<string, object, string> keyMaximizer, object state)
{
// Technically GetCursors should never be called with a null value, so this is extra cautious
if (String.IsNullOrEmpty(cursor))
@@ -132,6 +134,14 @@ namespace Microsoft.AspNet.SignalR.Messaging
throw new FormatException(Resources.Error_InvalidCursorFormat);
}
// If the cursor does not begin with the prefix stream, it isn't necessarily a formatting problem.
// The cursor with a different prefix might have had different, but also valid, formatting.
// Null should be returned so new cursors will be generated
if (!cursor.StartsWith(prefix, StringComparison.Ordinal))
{
return null;
}
var signals = new HashSet<string>();
var cursors = new List<Cursor>();
string currentKey = null;
@@ -143,8 +153,10 @@ namespace Microsoft.AspNet.SignalR.Messaging
var sbEscaped = new StringBuilder();
Cursor parsedCursor;
foreach (var ch in cursor)
for (int i = prefix.Length; i < cursor.Length; i++)
{
var ch = cursor[i];
// escape can only be true if we are consuming the key
if (escape)
{
@@ -3,7 +3,9 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Security.Cryptography;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Infrastructure;
@@ -11,6 +13,8 @@ namespace Microsoft.AspNet.SignalR.Messaging
{
internal class DefaultSubscription : Subscription
{
internal static string _defaultCursorPrefix = GetCursorPrefix();
private List<Cursor> _cursors;
private List<Topic> _cursorTopics;
@@ -36,7 +40,7 @@ namespace Microsoft.AspNet.SignalR.Messaging
else
{
// Ensure delegate continues to use the C# Compiler static delegate caching optimization.
_cursors = Cursor.GetCursors(cursor, (k, s) => UnminifyCursor(k, s), stringMinifier) ?? GetCursorsFromEventKeys(EventKeys, topics);
_cursors = Cursor.GetCursors(cursor, _defaultCursorPrefix, (k, s) => UnminifyCursor(k, s), stringMinifier) ?? GetCursorsFromEventKeys(EventKeys, topics);
}
_cursorTopics = new List<Topic>();
@@ -126,7 +130,7 @@ namespace Microsoft.AspNet.SignalR.Messaging
{
lock (_cursors)
{
Cursor.WriteCursors(textWriter, _cursors);
Cursor.WriteCursors(textWriter, _cursors, _defaultCursorPrefix);
}
}
@@ -196,6 +200,22 @@ namespace Microsoft.AspNet.SignalR.Messaging
return list;
}
private static string GetCursorPrefix()
{
using (var rng = new RNGCryptoServiceProvider())
{
var data = new byte[4];
rng.GetBytes(data);
using (var writer = new StringWriter(CultureInfo.InvariantCulture))
{
var randomValue = (ulong)BitConverter.ToUInt32(data, 0);
Cursor.WriteUlongAsHexToBuffer(randomValue, writer);
return "d-" + writer.ToString() + "-";
}
}
}
private static ulong GetMessageId(TopicLookup topics, string key)
{
Topic topic;
@@ -233,7 +233,7 @@ namespace Microsoft.AspNet.SignalR.Messaging
}
finally
{
if (!subscription.UnsetQueued() || workTask.IsFaulted)
if (!subscription.UnsetQueued() || workTask.IsFaulted || workTask.IsCanceled)
{
// If we don't have more work to do just make the subscription null
subscription = null;
@@ -271,7 +271,7 @@ namespace Microsoft.AspNet.SignalR.Messaging
Trace.TraceEvent(TraceEventType.Error, 0, "Work failed for " + subscription.Identity + ": " + task.Exception.GetBaseException());
}
if (moreWork && !task.IsFaulted)
if (moreWork && !task.IsFaulted && !task.IsCanceled)
{
PumpImpl(taskCompletionSource, subscription);
}
@@ -295,10 +295,7 @@ namespace Microsoft.AspNet.SignalR.Messaging
Trace.TraceEvent(TraceEventType.Verbose, 0, "Dispoing the broker");
//Check if OS is not Windows and exit
var platform = (int)Environment.OSVersion.Platform;
if ((platform == 4) || (platform == 6) || (platform == 128))
if (MonoUtility.IsRunningMono)
{
return;
}
@@ -15,6 +15,9 @@ namespace Microsoft.AspNet.SignalR.Messaging
private static readonly List<ArraySegment<Message>> _emptyList = new List<ArraySegment<Message>>();
public readonly static MessageResult TerminalMessage = new MessageResult(terminal: true);
/// <summary>
/// Gets an <see cref="T:IList{Message}"/> associated with the result.
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "This is an optimization to avoid allocations.")]
public IList<ArraySegment<Message>> Messages { get; private set; }
@@ -12,6 +12,8 @@ namespace Microsoft.AspNet.SignalR.Messaging
{
public class ScaleoutSubscription : Subscription
{
private const string _scaleoutCursorPrefix = "s-";
private readonly IList<ScaleoutMappingStore> _streams;
private readonly List<Cursor> _cursors;
@@ -40,10 +42,15 @@ namespace Microsoft.AspNet.SignalR.Messaging
}
else
{
cursors = Cursor.GetCursors(cursor);
cursors = Cursor.GetCursors(cursor, _scaleoutCursorPrefix);
// If the cursor had a default prefix, "d-", cursors might be null
if (cursors == null)
{
cursors = new List<Cursor>();
}
// If the streams don't match the cursors then throw it out
if (cursors.Count != _streams.Count)
else if (cursors.Count != _streams.Count)
{
cursors.Clear();
}
@@ -63,7 +70,7 @@ namespace Microsoft.AspNet.SignalR.Messaging
public override void WriteCursor(TextWriter textWriter)
{
Cursor.WriteCursors(textWriter, _cursors);
Cursor.WriteCursors(textWriter, _cursors, _scaleoutCursorPrefix);
}
[SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists", Justification = "The list needs to be populated")]
@@ -120,13 +120,7 @@ namespace Microsoft.AspNet.SignalR.Messaging
WorkImpl(tcs);
// Fast Path
if (tcs.Task.IsCompleted)
{
return tcs.Task;
}
return FinishAsync(tcs);
return tcs.Task;
}
public bool SetQueued()
@@ -140,19 +134,6 @@ namespace Microsoft.AspNet.SignalR.Messaging
return Interlocked.CompareExchange(ref _state, State.Idle, State.Working) != State.Working;
}
private static Task FinishAsync(TaskCompletionSource<object> tcs)
{
return tcs.Task.ContinueWith(task =>
{
if (task.IsFaulted)
{
return TaskAsyncHelper.FromError(task.Exception);
}
return TaskAsyncHelper.Empty;
}).FastUnwrap();
}
[SuppressMessage("Microsoft.Usage", "CA2202:Do not dispose objects multiple times", Justification = "We have a sync and async code path.")]
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We want to avoid user code taking the process down.")]
private void WorkImpl(TaskCompletionSource<object> taskCompletionSource)
@@ -200,7 +181,14 @@ namespace Microsoft.AspNet.SignalR.Messaging
}
catch (Exception ex)
{
taskCompletionSource.TrySetUnwrappedException(ex);
if (ex.InnerException is TaskCanceledException)
{
taskCompletionSource.TrySetCanceled();
}
else
{
taskCompletionSource.TrySetUnwrappedException(ex);
}
}
}
else
@@ -233,6 +221,10 @@ namespace Microsoft.AspNet.SignalR.Messaging
{
taskCompletionSource.TrySetUnwrappedException(task.Exception);
}
else if (task.IsCanceled)
{
taskCompletionSource.TrySetCanceled();
}
else if (task.Result)
{
WorkImpl(taskCompletionSource);
@@ -71,9 +71,11 @@
<Compile Include="Infrastructure\AckHandler.cs" />
<Compile Include="Configuration\DefaultConfigurationManager.cs" />
<Compile Include="Infrastructure\ArraySegmentTextReader.cs" />
<Compile Include="Infrastructure\BinaryTextWriter.cs" />
<Compile Include="Infrastructure\ConnectionManager.cs" />
<Compile Include="ConnectionMessage.cs" />
<Compile Include="Infrastructure\DefaultProtectedData.cs" />
<Compile Include="Infrastructure\MonoUtility.cs" />
<Compile Include="Infrastructure\DiffPair.cs" />
<Compile Include="Infrastructure\DiffSet.cs" />
<Compile Include="GlobalHost.cs" />
@@ -6,6 +6,7 @@ using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Configuration;
using Microsoft.AspNet.SignalR.Hosting;
@@ -165,7 +166,7 @@ namespace Microsoft.AspNet.SignalR
if (Transport == null)
{
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.Error_ProtocolErrorUnknownTransport));
return FailResponse(context.Response, String.Format(CultureInfo.CurrentCulture, Resources.Error_ProtocolErrorUnknownTransport));
}
string connectionToken = context.Request.QueryString["connectionToken"];
@@ -173,10 +174,17 @@ namespace Microsoft.AspNet.SignalR
// If there's no connection id then this is a bad request
if (String.IsNullOrEmpty(connectionToken))
{
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.Error_ProtocolErrorMissingConnectionToken));
return FailResponse(context.Response, String.Format(CultureInfo.CurrentCulture, Resources.Error_ProtocolErrorMissingConnectionToken));
}
string connectionId = GetConnectionId(context, connectionToken);
string connectionId;
string message;
int statusCode;
if (!TryGetConnectionId(context, connectionToken, out connectionId, out message, out statusCode))
{
return FailResponse(context.Response, message, statusCode);
}
// Set the transport's connection id to the unprotected one
Transport.ConnectionId = connectionId;
@@ -227,10 +235,21 @@ namespace Microsoft.AspNet.SignalR
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We want to catch any exception when unprotecting data.")]
internal string GetConnectionId(HostContext context, string connectionToken)
internal bool TryGetConnectionId(HostContext context,
string connectionToken,
out string connectionId,
out string message,
out int statusCode)
{
string unprotectedConnectionToken = null;
// connectionId is only valid when this method returns true
connectionId = null;
// message and statusCode are only valid when this method returns false
message = null;
statusCode = 400;
try
{
unprotectedConnectionToken = ProtectedData.Unprotect(connectionToken, Purposes.ConnectionToken);
@@ -242,21 +261,24 @@ namespace Microsoft.AspNet.SignalR
if (String.IsNullOrEmpty(unprotectedConnectionToken))
{
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.Error_ConnectionIdIncorrectFormat));
message = String.Format(CultureInfo.CurrentCulture, Resources.Error_ConnectionIdIncorrectFormat);
return false;
}
var tokens = unprotectedConnectionToken.Split(SplitChars, 2);
string connectionId = tokens[0];
connectionId = tokens[0];
string tokenUserName = tokens.Length > 1 ? tokens[1] : String.Empty;
string userName = GetUserIdentity(context);
if (!String.Equals(tokenUserName, userName, StringComparison.OrdinalIgnoreCase))
{
throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, Resources.Error_UnrecognizedUserIdentity));
message = String.Format(CultureInfo.CurrentCulture, Resources.Error_UnrecognizedUserIdentity);
statusCode = 403;
return false;
}
return connectionId;
return true;
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "We want to prevent any failures in unprotecting")]
@@ -477,6 +499,12 @@ namespace Microsoft.AspNet.SignalR
return context.Response.End(data);
}
private static Task FailResponse(IResponse response, string message, int statusCode = 400)
{
response.StatusCode = statusCode;
return response.End(message);
}
private static bool IsNegotiationRequest(IRequest request)
{
return request.Url.LocalPath.EndsWith("/negotiate", StringComparison.OrdinalIgnoreCase);
@@ -1,5 +1,5 @@
/*!
* ASP.NET SignalR JavaScript Library v1.1.3
* ASP.NET SignalR JavaScript Library v1.2.2
* http://signalr.net/
*
* Copyright Microsoft Open Technologies, Inc. All rights reserved.
@@ -10,7 +10,7 @@
/// <reference path="..\..\SignalR.Client.JS\Scripts\jquery-1.6.4.js" />
/// <reference path="jquery.signalR.js" />
(function ($, window) {
(function ($, window, undefined) {
/// <param name="$" type="jQuery" />
"use strict";
@@ -1,9 +1,13 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.md in the project root for license information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Infrastructure;
@@ -159,7 +163,7 @@ namespace Microsoft.AspNet.SignalR
{
// observe Exception
#if !WINDOWS_PHONE && !SILVERLIGHT && !NETFX_CORE
Trace.TraceError("SignalR exception thrown by Task: {0}", exception);
Trace.TraceWarning("SignalR exception thrown by Task: {0}", exception);
#endif
handler(exception, state);
}
@@ -162,7 +162,7 @@ namespace Microsoft.AspNet.SignalR.Transports
}
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exceptions are flowed to the caller.")]
private Task ProcessReceiveRequest(ITransportConnection connection)
protected Task ProcessReceiveRequest(ITransportConnection connection)
{
Func<Task> initialize = null;
@@ -273,7 +273,7 @@ namespace Microsoft.AspNet.SignalR.Transports
{
var context = (MessageContext)state;
response.TimedOut = context.Transport.IsTimedOut;
response.Reconnect = context.Transport.HostShutdownToken.IsCancellationRequested;
// If we're telling the client to disconnect then clean up the instantiated connection.
if (response.Disconnect)
@@ -282,7 +282,7 @@ namespace Microsoft.AspNet.SignalR.Transports
return context.Transport.Send(response).Then(c => OnDisconnectMessage(c), context)
.Then(() => TaskAsyncHelper.False);
}
else if (response.TimedOut || response.Aborted)
else if (context.Transport.IsTimedOut || response.Aborted)
{
context.Registration.Dispose();
@@ -3,6 +3,7 @@
using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Hosting;
using Microsoft.AspNet.SignalR.Infrastructure;
@@ -252,7 +253,7 @@ namespace Microsoft.AspNet.SignalR.Transports
{
var context = (MessageContext)state;
response.TimedOut = context.Transport.IsTimedOut;
response.Reconnect = context.Transport.HostShutdownToken.IsCancellationRequested;
Task task = TaskAsyncHelper.Empty;
@@ -20,7 +20,7 @@ namespace Microsoft.AspNet.SignalR.Transports
private readonly Action<TextWriter> _writeCursor;
public PersistentResponse()
: this(message => true, writer => { })
: this(message => false, writer => { })
{
}
@@ -61,9 +61,10 @@ namespace Microsoft.AspNet.SignalR.Transports
public bool Aborted { get; set; }
/// <summary>
/// True if the connection timed out.
/// True if the client should try reconnecting.
/// </summary>
public bool TimedOut { get; set; }
// This is set when the host is shutting down.
public bool Reconnect { get; set; }
/// <summary>
/// Signed token representing the list of groups. Updates on change.
@@ -106,7 +107,7 @@ namespace Microsoft.AspNet.SignalR.Transports
jsonWriter.WriteValue(1);
}
if (TimedOut)
if (Reconnect)
{
jsonWriter.WritePropertyName("T");
jsonWriter.WriteValue(1);
@@ -130,6 +130,14 @@ namespace Microsoft.AspNet.SignalR.Transports
}
}
protected CancellationToken HostShutdownToken
{
get
{
return _hostShutdownToken;
}
}
public bool IsTimedOut
{
get
@@ -186,7 +194,7 @@ namespace Microsoft.AspNet.SignalR.Transports
protected virtual TextWriter CreateResponseWriter()
{
return new BufferTextWriter(Context.Response);
return new BinaryTextWriter(Context.Response);
}
protected void IncrementErrors()
@@ -19,7 +19,7 @@ namespace Microsoft.AspNet.SignalR.Transports
private bool _isAlive = true;
private readonly Action<string> _message;
private readonly Action<bool> _closed;
private readonly Action _closed;
private readonly Action<Exception> _error;
public WebSocketTransport(HostContext context,
@@ -74,28 +74,39 @@ namespace Microsoft.AspNet.SignalR.Transports
public override Task ProcessRequest(ITransportConnection connection)
{
var webSocketRequest = _context.Request as IWebSocketRequest;
// Throw if the server implementation doesn't support websockets
if (webSocketRequest == null)
if (IsAbortRequest)
{
throw new InvalidOperationException(Resources.Error_WebSocketsNotSupported);
return connection.Abort(ConnectionId);
}
return webSocketRequest.AcceptWebSocketRequest(socket =>
else
{
_socket = socket;
socket.OnClose = _closed;
socket.OnMessage = _message;
socket.OnError = _error;
var webSocketRequest = _context.Request as IWebSocketRequest;
return ProcessRequestCore(connection);
});
// Throw if the server implementation doesn't support websockets
if (webSocketRequest == null)
{
throw new InvalidOperationException(Resources.Error_WebSocketsNotSupported);
}
Connection = connection;
InitializePersistentState();
return webSocketRequest.AcceptWebSocketRequest(socket =>
{
_socket = socket;
socket.OnClose = _closed;
socket.OnMessage = _message;
socket.OnError = _error;
return ProcessReceiveRequest(connection);
},
InitializeTcs.Task);
}
}
protected override TextWriter CreateResponseWriter()
{
return new BufferTextWriter(_socket);
return new BinaryTextWriter(_socket);
}
public override Task Send(object value)
@@ -113,6 +124,11 @@ namespace Microsoft.AspNet.SignalR.Transports
return Send((object)response);
}
protected internal override Task InitializeResponse(ITransportConnection connection)
{
return _socket.Send("{}");
}
private static Task PerformSend(object state)
{
var context = (WebSocketTransportContext)state;
@@ -131,18 +147,11 @@ namespace Microsoft.AspNet.SignalR.Transports
}
}
private void OnClosed(bool clean)
private void OnClosed()
{
Trace.TraceInformation("CloseSocket({0}, {1})", clean, ConnectionId);
// If we performed a clean disconnect then we go through the normal disconnect routine. However,
// If we performed an unclean disconnect we want to mark the connection as "not alive" and let the
// HeartBeat clean it up. This is to maintain consistency across the transports.
if (clean)
{
Abort();
}
Trace.TraceInformation("CloseSocket({0})", ConnectionId);
// Require a request to /abort to stop tracking the connection. #2195
_isAlive = false;
}
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.Security.Principal;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Hosting;
using Microsoft.AspNet.SignalR.Owin.Infrastructure;
@@ -65,9 +66,19 @@ namespace Microsoft.AspNet.SignalR.Owin
if (!_connection.Authorize(serverRequest))
{
// If we failed to authorize the request then return a 403 since the request
// can't do anything
return EndResponse(environment, 403, "Forbidden");
IPrincipal user = hostContext.Request.User;
if (user != null && user.Identity.IsAuthenticated)
{
// If we failed to authorize the request then return a 403 since the request
// can't do anything
return EndResponse(environment, 403, "Forbidden");
}
else
{
// If we failed to authorize the request and the user is not authenticated
// then return a 401
return EndResponse(environment, 401, "Unauthorized");
}
}
else
{
@@ -7,6 +7,7 @@ using System.Security.Principal;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.SignalR.Owin.Infrastructure;
using Microsoft.AspNet.SignalR.Hosting;
namespace Microsoft.AspNet.SignalR.Owin
{
@@ -138,15 +139,17 @@ namespace Microsoft.AspNet.SignalR.Owin
}
#if NET45
public Task AcceptWebSocketRequest(Func<IWebSocket, Task> callback)
public Task AcceptWebSocketRequest(Func<IWebSocket, Task> callback, Task initTask)
{
var accept = _environment.Get<Action<IDictionary<string, object>, WebSocketFunc>>(OwinConstants.WebSocketAccept);
if (accept == null)
{
throw new InvalidOperationException(Resources.Error_NotWebSocketRequest);
var response = new ServerResponse(_environment);
response.StatusCode = 400;
return response.End(Resources.Error_NotWebSocketRequest);
}
var handler = new OwinWebSocketHandler(callback);
var handler = new OwinWebSocketHandler(callback, initTask);
accept(null, handler.ProcessRequestAsync);
return TaskAsyncHelper.Empty;
}
@@ -27,6 +27,18 @@ namespace Microsoft.AspNet.SignalR.Owin
get { return _callCancelled; }
}
public int StatusCode
{
get
{
return _environment.Get<int>(OwinConstants.ResponseStatusCode);
}
set
{
_environment[OwinConstants.ResponseStatusCode] = value;
}
}
public string ContentType
{
get { return ResponseHeaders.GetHeader("Content-Type"); }
@@ -117,13 +117,11 @@ namespace NzbDrone.Api.Test.MappingTests
}
[Test]
public void should_map_tracked_command()
{
var profileResource = new ApplicationUpdateCommand();
profileResource.InjectTo<CommandResource>();
var commandResource = new CommandModel { Body = new ApplicationUpdateCommand() };
commandResource.InjectTo<CommandResource>();
}
}
@@ -64,7 +64,7 @@
<HintPath>..\packages\Moq.4.0.10827\lib\NET40\Moq.dll</HintPath>
</Reference>
<Reference Include="Omu.ValueInjecter">
<HintPath>..\packages\valueinjecter.2.3.3\lib\net35\Omu.ValueInjecter.dll</HintPath>
<HintPath>..\packages\ValueInjecter.2.3.3\lib\net35\Omu.ValueInjecter.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
@@ -104,7 +104,7 @@
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\nuget.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
+1 -1
View File
@@ -4,5 +4,5 @@
<package id="Moq" version="4.0.10827" />
<package id="NBuilder" version="3.0.1.1" targetFramework="net40" />
<package id="NUnit" version="2.6.3" targetFramework="net40" />
<package id="valueinjecter" version="2.3.3" targetFramework="net40" />
<package id="ValueInjecter" version="2.3.3" targetFramework="net40" />
</packages>
@@ -0,0 +1,62 @@
using Nancy;
using Nancy.Authentication.Basic;
using Nancy.Authentication.Forms;
using Nancy.Bootstrapper;
using Nancy.Cryptography;
using NzbDrone.Api.Extensions.Pipelines;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Api.Authentication
{
public class EnableAuthInNancy : IRegisterNancyPipeline
{
private readonly IAuthenticationService _authenticationService;
private readonly IConfigService _configService;
private readonly IConfigFileProvider _configFileProvider;
public EnableAuthInNancy(IAuthenticationService authenticationService,
IConfigService configService,
IConfigFileProvider configFileProvider)
{
_authenticationService = authenticationService;
_configService = configService;
_configFileProvider = configFileProvider;
}
public void Register(IPipelines pipelines)
{
RegisterFormsAuth(pipelines);
pipelines.EnableBasicAuthentication(new BasicAuthenticationConfiguration(_authenticationService, "Sonarr"));
pipelines.BeforeRequest.AddItemToEndOfPipeline(RequiresAuthentication);
}
private Response RequiresAuthentication(NancyContext context)
{
Response response = null;
if (!_authenticationService.IsAuthenticated(context))
{
response = new Response { StatusCode = HttpStatusCode.Unauthorized };
}
return response;
}
private void RegisterFormsAuth(IPipelines pipelines)
{
var cryptographyConfiguration = new CryptographyConfiguration(
new RijndaelEncryptionProvider(new PassphraseKeyGenerator(_configService.RijndaelPassphrase,
new byte[] {1, 2, 3, 4, 5, 6, 7, 8})),
new DefaultHmacProvider(new PassphraseKeyGenerator(_configService.HmacPassphrase,
new byte[] {1, 2, 3, 4, 5, 6, 7, 8}))
);
FormsAuthentication.Enable(pipelines, new FormsAuthenticationConfiguration
{
RedirectUrl = "~/login",
UserMapper = _authenticationService,
CryptographyConfiguration = cryptographyConfiguration
});
}
}
}
@@ -0,0 +1,48 @@
using System;
using Nancy;
using Nancy.Authentication.Forms;
using Nancy.Extensions;
using Nancy.ModelBinding;
using NzbDrone.Core.Authentication;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Api.Authentication
{
public class AuthenticationModule : NancyModule
{
private readonly IUserService _userService;
private readonly IConfigFileProvider _configFileProvider;
public AuthenticationModule(IUserService userService, IConfigFileProvider configFileProvider)
{
_userService = userService;
_configFileProvider = configFileProvider;
Post["/login"] = x => Login(this.Bind<LoginResource>());
Get["/logout"] = x => Logout();
}
private Response Login(LoginResource resource)
{
var user = _userService.FindUser(resource.Username, resource.Password);
if (user == null)
{
return Context.GetRedirect("~/login?returnUrl=" + (string)Request.Query.returnUrl);
}
DateTime? expiry = null;
if (resource.RememberMe)
{
expiry = DateTime.UtcNow.AddDays(7);
}
return this.LoginAndRedirect(user.Identifier, expiry);
}
private Response Logout()
{
return this.LogoutAndRedirect(_configFileProvider.UrlBase + "/");
}
}
}
@@ -2,14 +2,16 @@
using System.Linq;
using Nancy;
using Nancy.Authentication.Basic;
using Nancy.Authentication.Forms;
using Nancy.Security;
using NzbDrone.Api.Extensions;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Authentication;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Api.Authentication
{
public interface IAuthenticationService : IUserValidator
public interface IAuthenticationService : IUserValidator, IUserMapper
{
bool IsAuthenticated(NancyContext context);
}
@@ -17,37 +19,52 @@ namespace NzbDrone.Api.Authentication
public class AuthenticationService : IAuthenticationService
{
private readonly IConfigFileProvider _configFileProvider;
private readonly IUserService _userService;
private static readonly NzbDroneUser AnonymousUser = new NzbDroneUser { UserName = "Anonymous" };
private static String API_KEY;
public AuthenticationService(IConfigFileProvider configFileProvider)
private static String API_KEY;
private static AuthenticationType AUTH_METHOD;
public AuthenticationService(IConfigFileProvider configFileProvider, IUserService userService)
{
_configFileProvider = configFileProvider;
_userService = userService;
API_KEY = configFileProvider.ApiKey;
AUTH_METHOD = configFileProvider.AuthenticationMethod;
}
public IUserIdentity Validate(string username, string password)
{
if (!Enabled)
if (AUTH_METHOD == AuthenticationType.None)
{
return AnonymousUser;
}
if (_configFileProvider.Username.Equals(username) &&
_configFileProvider.Password.Equals(password))
var user = _userService.FindUser(username, password);
if (user != null)
{
return new NzbDroneUser { UserName = username };
return new NzbDroneUser { UserName = user.Username };
}
return null;
}
private bool Enabled
public IUserIdentity GetUserFromIdentifier(Guid identifier, NancyContext context)
{
get
if (AUTH_METHOD == AuthenticationType.None)
{
return _configFileProvider.AuthenticationEnabled;
return AnonymousUser;
}
var user = _userService.FindUser(identifier);
if (user != null)
{
return new NzbDroneUser { UserName = user.Username };
}
return null;
}
public bool IsAuthenticated(NancyContext context)
@@ -59,13 +76,13 @@ namespace NzbDrone.Api.Authentication
return ValidApiKey(apiKey);
}
if (AUTH_METHOD == AuthenticationType.None)
{
return true;
}
if (context.Request.IsFeedRequest())
{
if (!Enabled)
{
return true;
}
if (ValidUser(context) || ValidApiKey(apiKey))
{
return true;
@@ -74,7 +91,12 @@ namespace NzbDrone.Api.Authentication
return false;
}
if (!Enabled)
if (context.Request.IsLoginRequest())
{
return true;
}
if (context.Request.IsContentRequest())
{
return true;
}
@@ -1,23 +1,46 @@
using Nancy;
using System;
using System.Text;
using Nancy;
using Nancy.Authentication.Basic;
using Nancy.Authentication.Forms;
using Nancy.Bootstrapper;
using Nancy.Cryptography;
using NzbDrone.Api.Extensions;
using NzbDrone.Api.Extensions.Pipelines;
using NzbDrone.Core.Authentication;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Api.Authentication
{
public class EnableAuthInNancy : IRegisterNancyPipeline
{
private readonly IAuthenticationService _authenticationService;
private readonly IConfigService _configService;
private readonly IConfigFileProvider _configFileProvider;
public EnableAuthInNancy(IAuthenticationService authenticationService)
public EnableAuthInNancy(IAuthenticationService authenticationService,
IConfigService configService,
IConfigFileProvider configFileProvider)
{
_authenticationService = authenticationService;
_configService = configService;
_configFileProvider = configFileProvider;
}
public void Register(IPipelines pipelines)
{
pipelines.EnableBasicAuthentication(new BasicAuthenticationConfiguration(_authenticationService, "Sonarr"));
if (_configFileProvider.AuthenticationMethod == AuthenticationType.Forms)
{
RegisterFormsAuth(pipelines);
}
else if (_configFileProvider.AuthenticationMethod == AuthenticationType.Basic)
{
pipelines.EnableBasicAuthentication(new BasicAuthenticationConfiguration(_authenticationService, "Sonarr"));
}
pipelines.BeforeRequest.AddItemToEndOfPipeline(RequiresAuthentication);
pipelines.AfterRequest.AddItemToEndOfPipeline(RemoveLoginHooksForApiCalls);
}
private Response RequiresAuthentication(NancyContext context)
@@ -31,5 +54,33 @@ namespace NzbDrone.Api.Authentication
return response;
}
private void RegisterFormsAuth(IPipelines pipelines)
{
var cryptographyConfiguration = new CryptographyConfiguration(
new RijndaelEncryptionProvider(new PassphraseKeyGenerator(_configService.RijndaelPassphrase, Encoding.ASCII.GetBytes(_configService.RijndaelSalt))),
new DefaultHmacProvider(new PassphraseKeyGenerator(_configService.HmacPassphrase, Encoding.ASCII.GetBytes(_configService.HmacSalt)))
);
FormsAuthentication.Enable(pipelines, new FormsAuthenticationConfiguration
{
RedirectUrl = _configFileProvider.UrlBase + "/login",
UserMapper = _authenticationService,
CryptographyConfiguration = cryptographyConfiguration
});
}
private void RemoveLoginHooksForApiCalls(NancyContext context)
{
if (context.Request.IsApiRequest())
{
if ((context.Response.StatusCode == HttpStatusCode.SeeOther &&
context.Response.Headers["Location"].StartsWith("/login", StringComparison.InvariantCultureIgnoreCase)) ||
context.Response.StatusCode == HttpStatusCode.Unauthorized)
{
context.Response = new { Error = "Unauthorized" }.AsResponse(HttpStatusCode.Unauthorized);
}
}
}
}
}
@@ -0,0 +1,9 @@
namespace NzbDrone.Api.Authentication
{
public class LoginResource
{
public string Username { get; set; }
public string Password { get; set; }
public bool RememberMe { get; set; }
}
}
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using NzbDrone.Api.REST;
using NzbDrone.Core.Qualities;
using NzbDrone.Api.Series;
using NzbDrone.Core.Indexers;
namespace NzbDrone.Api.Blacklist
{
@@ -13,6 +14,9 @@ namespace NzbDrone.Api.Blacklist
public string SourceTitle { get; set; }
public QualityModel Quality { get; set; }
public DateTime Date { get; set; }
public DownloadProtocol Protocol { get; set; }
public string Indexer { get; set; }
public string Message { get; set; }
public SeriesResource Series { get; set; }
}
@@ -31,7 +31,7 @@ namespace NzbDrone.Api.Calendar
if (queryStart.HasValue) start = DateTime.Parse(queryStart.Value);
if (queryEnd.HasValue) end = DateTime.Parse(queryEnd.Value);
var episodes = _episodeService.EpisodesBetweenDates(start, end);
var episodes = _episodeService.EpisodesBetweenDates(start, end, false);
var icalCalendar = new iCalendar();
foreach (var episode in episodes.OrderBy(v => v.AirDateUtc.Value))
+4 -1
View File
@@ -23,14 +23,17 @@ namespace NzbDrone.Api.Calendar
{
var start = DateTime.Today;
var end = DateTime.Today.AddDays(2);
var includeUnmonitored = false;
var queryStart = Request.Query.Start;
var queryEnd = Request.Query.End;
var queryIncludeUnmonitored = Request.Query.Unmonitored;
if (queryStart.HasValue) start = DateTime.Parse(queryStart.Value);
if (queryEnd.HasValue) end = DateTime.Parse(queryEnd.Value);
if (queryIncludeUnmonitored.HasValue) includeUnmonitored = Convert.ToBoolean(queryIncludeUnmonitored.Value);
var resources = ToListResource(() => _episodeService.EpisodesBetweenDates(start, end));
var resources = ToListResource(() => _episodeService.EpisodesBetweenDates(start, end, includeUnmonitored));
return resources.OrderBy(e => e.AirDateUtc).ToList();
}
@@ -53,11 +53,9 @@ namespace NzbDrone.Api.ClientSchema
}
}
return result;
return result.OrderBy(r => r.Order).ToList();
}
public static object ReadFormSchema(List<Field> fields, Type targetType, object defaults = null)
{
Ensure.That(targetType, () => targetType).IsNotNull();
+18 -22
View File
@@ -4,10 +4,9 @@ using System.Linq;
using NzbDrone.Api.Extensions;
using NzbDrone.Api.Mapping;
using NzbDrone.Api.Validation;
using NzbDrone.Common.Composition;
using NzbDrone.Common;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Commands.Tracking;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.ProgressMessaging;
using NzbDrone.SignalR;
@@ -15,56 +14,53 @@ using NzbDrone.SignalR;
namespace NzbDrone.Api.Commands
{
public class CommandModule : NzbDroneRestModuleWithSignalR<CommandResource, Command>, IHandle<CommandUpdatedEvent>
public class CommandModule : NzbDroneRestModuleWithSignalR<CommandResource, CommandModel>, IHandle<CommandUpdatedEvent>
{
private readonly ICommandExecutor _commandExecutor;
private readonly IContainer _container;
private readonly ITrackCommands _trackCommands;
private readonly IManageCommandQueue _commandQueueManager;
private readonly IServiceFactory _serviceFactory;
public CommandModule(ICommandExecutor commandExecutor,
public CommandModule(IManageCommandQueue commandQueueManager,
IBroadcastSignalRMessage signalRBroadcaster,
IContainer container,
ITrackCommands trackCommands)
IServiceFactory serviceFactory)
: base(signalRBroadcaster)
{
_commandExecutor = commandExecutor;
_container = container;
_trackCommands = trackCommands;
_commandQueueManager = commandQueueManager;
_serviceFactory = serviceFactory;
GetResourceById = GetCommand;
CreateResource = StartCommand;
GetResourceAll = GetAllCommands;
GetResourceAll = GetStartedCommands;
PostValidator.RuleFor(c => c.Name).NotBlank();
}
private CommandResource GetCommand(int id)
{
return _trackCommands.GetById(id).InjectTo<CommandResource>();
return _commandQueueManager.Get(id).InjectTo<CommandResource>();
}
private int StartCommand(CommandResource commandResource)
{
var commandType =
_container.GetImplementations(typeof(Command))
.Single(c => c.Name.Replace("Command", "")
.Equals(commandResource.Name, StringComparison.InvariantCultureIgnoreCase));
_serviceFactory.GetImplementations(typeof (Command))
.Single(c => c.Name.Replace("Command", "")
.Equals(commandResource.Name, StringComparison.InvariantCultureIgnoreCase));
dynamic command = Request.Body.FromJson(commandType);
command.Manual = true;
command.Trigger = CommandTrigger.Manual;
var trackedCommand = (Command)_commandExecutor.PublishCommandAsync(command);
var trackedCommand = _commandQueueManager.Push(command, CommandPriority.Normal, CommandTrigger.Manual);
return trackedCommand.Id;
}
private List<CommandResource> GetAllCommands()
private List<CommandResource> GetStartedCommands()
{
return ToListResource(_trackCommands.RunningCommands);
return ToListResource(_commandQueueManager.GetStarted());
}
public void Handle(CommandUpdatedEvent message)
{
if (message.Command.SendUpdatesToClient)
if (message.Command.Body.SendUpdatesToClient)
{
BroadcastResourceChange(ModelAction.Updated, message.Command.Id);
}
+71 -6
View File
@@ -1,6 +1,7 @@
using System;
using Newtonsoft.Json;
using NzbDrone.Api.REST;
using NzbDrone.Core.Messaging.Commands.Tracking;
using NzbDrone.Core.Messaging.Commands;
namespace NzbDrone.Api.Commands
{
@@ -8,11 +9,75 @@ namespace NzbDrone.Api.Commands
{
public String Name { get; set; }
public String Message { get; set; }
public DateTime StartedOn { get; set; }
public DateTime StateChangeTime { get; set; }
public Boolean SendUpdatesToClient { get; set; }
public CommandStatus State { get; set; }
public Command Body { get; set; }
public CommandPriority Priority { get; set; }
public CommandStatus Status { get; set; }
public DateTime Queued { get; set; }
public DateTime? Started { get; set; }
public DateTime? Ended { get; set; }
public TimeSpan? Duration { get; set; }
public string Exception { get; set; }
public CommandTrigger Trigger { get; set; }
[JsonIgnore]
public string CompletionMessage { get; set; }
//Legacy
public CommandStatus State
{
get
{
return Status;
}
set { }
}
public Boolean Manual
{
get
{
return Trigger == CommandTrigger.Manual;
}
set { }
}
public DateTime StartedOn
{
get
{
return Queued;
}
set { }
}
public DateTime? StateChangeTime
{
get
{
if (Started.HasValue) return Started.Value;
return Ended;
}
set { }
}
public Boolean SendUpdatesToClient
{
get
{
if (Body != null) return Body.SendUpdatesToClient;
return false;
}
set { }
}
public DateTime? LastExecutionTime { get; set; }
public Boolean Manual { get; set; }
}
}
@@ -7,16 +7,19 @@ namespace NzbDrone.Api.Config
{
public class DownloadClientConfigModule : NzbDroneConfigModule<DownloadClientConfigResource>
{
public DownloadClientConfigModule(IConfigService configService, RootFolderValidator rootFolderValidator, PathExistsValidator pathExistsValidator)
public DownloadClientConfigModule(IConfigService configService,
RootFolderValidator rootFolderValidator,
PathExistsValidator pathExistsValidator,
MappedNetworkDriveValidator mappedNetworkDriveValidator)
: base(configService)
{
SharedValidator.RuleFor(c => c.DownloadedEpisodesFolder)
.Cascade(CascadeMode.StopOnFirstFailure)
.IsValidPath()
.SetValidator(rootFolderValidator)
.SetValidator(mappedNetworkDriveValidator)
.SetValidator(pathExistsValidator)
.When(c => !String.IsNullOrWhiteSpace(c.DownloadedEpisodesFolder));
}
}
}
+20 -3
View File
@@ -2,6 +2,8 @@
using System.Reflection;
using FluentValidation;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Authentication;
using NzbDrone.Core.Configuration;
using NzbDrone.Core.Update;
using NzbDrone.Core.Validation;
@@ -13,11 +15,13 @@ namespace NzbDrone.Api.Config
public class HostConfigModule : NzbDroneRestModule<HostConfigResource>
{
private readonly IConfigFileProvider _configFileProvider;
private readonly IUserService _userService;
public HostConfigModule(IConfigFileProvider configFileProvider)
public HostConfigModule(IConfigFileProvider configFileProvider, IUserService userService)
: base("/config/host")
{
_configFileProvider = configFileProvider;
_userService = userService;
GetResourceSingle = GetHostConfig;
GetResourceById = GetHostConfig;
@@ -26,8 +30,8 @@ namespace NzbDrone.Api.Config
SharedValidator.RuleFor(c => c.Branch).NotEmpty().WithMessage("Branch name is required, 'master' is the default");
SharedValidator.RuleFor(c => c.Port).ValidPort();
SharedValidator.RuleFor(c => c.Username).NotEmpty().When(c => c.AuthenticationEnabled);
SharedValidator.RuleFor(c => c.Password).NotEmpty().When(c => c.AuthenticationEnabled);
SharedValidator.RuleFor(c => c.Username).NotEmpty().When(c => c.AuthenticationMethod != AuthenticationType.None);
SharedValidator.RuleFor(c => c.Password).NotEmpty().When(c => c.AuthenticationMethod != AuthenticationType.None);
SharedValidator.RuleFor(c => c.SslPort).ValidPort().When(c => c.EnableSsl);
SharedValidator.RuleFor(c => c.SslCertHash).NotEmpty().When(c => c.EnableSsl && OsInfo.IsWindows);
@@ -46,6 +50,14 @@ namespace NzbDrone.Api.Config
resource.InjectFrom(_configFileProvider);
resource.Id = 1;
var user = _userService.FindUser();
if (user != null)
{
resource.Username = user.Username;
resource.Password = user.Password;
}
return resource;
}
@@ -61,6 +73,11 @@ namespace NzbDrone.Api.Config
.ToDictionary(prop => prop.Name, prop => prop.GetValue(resource, null));
_configFileProvider.SaveConfigDictionary(dictionary);
if (resource.Username.IsNotNullOrWhiteSpace() && resource.Password.IsNotNullOrWhiteSpace())
{
_userService.Upsert(resource.Username, resource.Password);
}
}
}
}
@@ -1,5 +1,6 @@
using System;
using NzbDrone.Api.REST;
using NzbDrone.Core.Authentication;
using NzbDrone.Core.Update;
namespace NzbDrone.Api.Config
@@ -11,7 +12,7 @@ namespace NzbDrone.Api.Config
public Int32 SslPort { get; set; }
public Boolean EnableSsl { get; set; }
public Boolean LaunchBrowser { get; set; }
public bool AuthenticationEnabled { get; set; }
public AuthenticationType AuthenticationMethod { get; set; }
public Boolean AnalyticsEnabled { get; set; }
public String Username { get; set; }
public String Password { get; set; }
@@ -14,5 +14,7 @@ namespace NzbDrone.Api.Config
public String LongDateFormat { get; set; }
public String TimeFormat { get; set; }
public Boolean ShowRelativeDates { get; set; }
public Boolean EnableColorImpairedMode { get; set; }
}
}
@@ -9,10 +9,10 @@ namespace NzbDrone.Api.DownloadClient
{
}
protected override void Validate(DownloadClientDefinition definition)
protected override void Validate(DownloadClientDefinition definition, bool includeWarnings)
{
if (!definition.Enable) return;
base.Validate(definition);
base.Validate(definition, includeWarnings);
}
}
}
@@ -24,7 +24,6 @@ namespace NzbDrone.Api.Episodes
public Nullable<Int32> SceneAbsoluteEpisodeNumber { get; set; }
public Nullable<Int32> SceneEpisodeNumber { get; set; }
public Nullable<Int32> SceneSeasonNumber { get; set; }
public Int32 TvDbEpisodeId { get; set; }
public DateTime? EndTime { get; set; }
public DateTime? GrabDate { get; set; }
public String SeriesTitle { get; set; }
@@ -26,5 +26,15 @@ namespace NzbDrone.Api.Extensions
request.UserHostAddress.Equals("127.0.0.1") ||
request.UserHostAddress.Equals("::1"));
}
public static bool IsLoginRequest(this Request request)
{
return request.Path.Equals("/login", StringComparison.InvariantCultureIgnoreCase);
}
public static bool IsContentRequest(this Request request)
{
return request.Path.StartsWith("/Content/", StringComparison.InvariantCultureIgnoreCase);
}
}
}
@@ -1,19 +1,31 @@
using System;
using System.IO;
using System.Linq;
using Nancy;
using NzbDrone.Api.Extensions;
using NzbDrone.Common.Disk;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.MediaFiles;
namespace NzbDrone.Api.FileSystem
{
public class FileSystemModule : NzbDroneApiModule
{
private readonly IFileSystemLookupService _fileSystemLookupService;
private readonly IDiskProvider _diskProvider;
private readonly IDiskScanService _diskScanService;
public FileSystemModule(IFileSystemLookupService fileSystemLookupService)
public FileSystemModule(IFileSystemLookupService fileSystemLookupService,
IDiskProvider diskProvider,
IDiskScanService diskScanService)
: base("/filesystem")
{
_fileSystemLookupService = fileSystemLookupService;
_diskProvider = diskProvider;
_diskScanService = diskScanService;
Get["/"] = x => GetContents();
Get["/type"] = x => GetEntityType();
Get["/mediafiles"] = x => GetMediaFiles();
}
private Response GetContents()
@@ -29,5 +41,36 @@ namespace NzbDrone.Api.FileSystem
return _fileSystemLookupService.LookupContents((string)pathQuery.Value, includeFiles).AsResponse();
}
private Response GetEntityType()
{
var pathQuery = Request.Query.path;
var path = (string)pathQuery.Value;
if (_diskProvider.FileExists(path))
{
return new { type = "file" }.AsResponse();
}
//Return folder even if it doesn't exist on disk to avoid leaking anything from the UI about the underlying system
return new { type = "folder" }.AsResponse();
}
private Response GetMediaFiles()
{
var pathQuery = Request.Query.path;
var path = (string)pathQuery.Value;
if (!_diskProvider.FolderExists(path))
{
return new string[0].AsResponse();
}
return _diskScanService.GetVideoFiles(path).Select(f => new {
Path = f,
RelativePath = path.GetRelativePath(f),
Name = Path.GetFileName(f)
}).AsResponse();
}
}
}
@@ -32,7 +32,7 @@ namespace NzbDrone.Api.Frontend.Mappers
var pathToFile = mapper.Map(resourceUrl);
var hash = _hashProvider.ComputeMd5(pathToFile).ToBase64();
return resourceUrl + "?h=" + hash;
return resourceUrl + "?h=" + hash.Trim('=');
}
private static bool ShouldBreakCache(string path)
@@ -1,3 +1,4 @@
using System;
using System.IO;
using NLog;
using NzbDrone.Common.Disk;
@@ -17,7 +18,14 @@ namespace NzbDrone.Api.Frontend.Mappers
public override string Map(string resourceUrl)
{
var path = Path.Combine("Content", "Images", "favicon.ico");
var fileName = "favicon.ico";
if (BuildInfo.IsDebug)
{
fileName = "favicon-debug.ico";
}
var path = Path.Combine("Content", "Images", fileName);
return Path.Combine(_appFolderInfo.StartUpFolder, "UI", path);
}
@@ -17,7 +17,7 @@ namespace NzbDrone.Api.Frontend.Mappers
private readonly IAnalyticsService _analyticsService;
private readonly Func<ICacheBreakerProvider> _cacheBreakProviderFactory;
private readonly string _indexPath;
private static readonly Regex ReplaceRegex = new Regex("(?<=(?:href|src|data-main)=\").*?(?=\")", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex ReplaceRegex = new Regex("(?<=(?:href|src)=\").*?(css|js|png|ico|ics)(?=\")", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static String API_KEY;
private static String URL_BASE;
@@ -49,7 +49,7 @@ namespace NzbDrone.Api.Frontend.Mappers
public override bool CanHandle(string resourceUrl)
{
return !resourceUrl.Contains(".");
return !resourceUrl.Contains(".") && !resourceUrl.StartsWith("/login");
}
public override Response GetResponse(string resourceUrl)
@@ -0,0 +1,88 @@
using System;
using System.IO;
using System.Text.RegularExpressions;
using Nancy;
using NLog;
using NzbDrone.Common.Disk;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Configuration;
namespace NzbDrone.Api.Frontend.Mappers
{
public class LoginHtmlMapper : StaticResourceMapperBase
{
private readonly IDiskProvider _diskProvider;
private readonly Func<ICacheBreakerProvider> _cacheBreakProviderFactory;
private readonly string _indexPath;
private static readonly Regex ReplaceRegex = new Regex("(?<=(?:href|src|data-main)=\").*?(?=\")", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static String URL_BASE;
private string _generatedContent;
public LoginHtmlMapper(IAppFolderInfo appFolderInfo,
IDiskProvider diskProvider,
IConfigFileProvider configFileProvider,
Func<ICacheBreakerProvider> cacheBreakProviderFactory,
Logger logger)
: base(diskProvider, logger)
{
_diskProvider = diskProvider;
_cacheBreakProviderFactory = cacheBreakProviderFactory;
_indexPath = Path.Combine(appFolderInfo.StartUpFolder, "UI", "login.html");
URL_BASE = configFileProvider.UrlBase;
}
public override string Map(string resourceUrl)
{
return _indexPath;
}
public override bool CanHandle(string resourceUrl)
{
return resourceUrl.StartsWith("/login");
}
public override Response GetResponse(string resourceUrl)
{
var response = base.GetResponse(resourceUrl);
response.Headers["X-UA-Compatible"] = "IE=edge";
return response;
}
protected override Stream GetContentStream(string filePath)
{
var text = GetLoginText();
var stream = new MemoryStream();
var writer = new StreamWriter(stream);
writer.Write(text);
writer.Flush();
stream.Position = 0;
return stream;
}
private string GetLoginText()
{
if (RuntimeInfoBase.IsProduction && _generatedContent != null)
{
return _generatedContent;
}
var text = _diskProvider.ReadAllText(_indexPath);
var cacheBreakProvider = _cacheBreakProviderFactory();
text = ReplaceRegex.Replace(text, match =>
{
var url = cacheBreakProvider.AddCacheBreakerToPath(match.Value);
return URL_BASE + url;
});
_generatedContent = text;
return _generatedContent;
}
}
}
@@ -13,11 +13,13 @@ namespace NzbDrone.Api.Frontend.Mappers
private static readonly Regex RegexResizedImage = new Regex(@"-\d+\.jpg($|\?)", RegexOptions.Compiled | RegexOptions.IgnoreCase);
private readonly IAppFolderInfo _appFolderInfo;
private readonly IDiskProvider _diskProvider;
public MediaCoverMapper(IAppFolderInfo appFolderInfo, IDiskProvider diskProvider, Logger logger)
: base(diskProvider, logger)
{
_appFolderInfo = appFolderInfo;
_diskProvider = diskProvider;
}
public override string Map(string resourceUrl)
@@ -25,25 +27,18 @@ namespace NzbDrone.Api.Frontend.Mappers
var path = resourceUrl.Replace('/', Path.DirectorySeparatorChar);
path = path.Trim(Path.DirectorySeparatorChar);
return Path.Combine(_appFolderInfo.GetAppDataPath(), path);
}
var resourcePath = Path.Combine(_appFolderInfo.GetAppDataPath(), path);
public override Response GetResponse(string resourceUrl)
{
var result = base.GetResponse(resourceUrl);
// Return the full sized image if someone requests a non-existing resized one.
// TODO: This code can be removed later once everyone had the update for a while.
if (result is NotFoundResponse)
if (!_diskProvider.FileExists(resourcePath) || _diskProvider.GetFileSize(resourcePath) == 0)
{
var baseResourceUrl = RegexResizedImage.Replace(resourceUrl, ".jpg$1");
if (baseResourceUrl != resourceUrl)
var baseResourcePath = RegexResizedImage.Replace(resourcePath, ".jpg$1");
if (baseResourcePath != resourcePath)
{
result = base.GetResponse(baseResourceUrl);
return baseResourcePath;
}
}
return result;
return resourcePath;
}
public override bool CanHandle(string resourceUrl)
@@ -27,6 +27,7 @@ namespace NzbDrone.Api.Frontend.Mappers
{
return resourceUrl.StartsWith("/Content") ||
resourceUrl.EndsWith(".js") ||
resourceUrl.EndsWith(".map") ||
resourceUrl.EndsWith(".css") ||
(resourceUrl.EndsWith(".ico") && !resourceUrl.Equals("/favicon.ico")) ||
resourceUrl.EndsWith(".swf");
@@ -18,7 +18,6 @@ namespace NzbDrone.Api.History
public Boolean QualityCutoffNotMet { get; set; }
public DateTime Date { get; set; }
public string Indexer { get; set; }
public string NzbInfoUrl { get; set; }
public string ReleaseGroup { get; set; }
public string DownloadId { get; set; }
+2 -2
View File
@@ -9,10 +9,10 @@ namespace NzbDrone.Api.Indexers
{
}
protected override void Validate(IndexerDefinition definition)
protected override void Validate(IndexerDefinition definition, bool includeWarnings)
{
if (!definition.Enable) return;
base.Validate(definition);
base.Validate(definition, includeWarnings);
}
}
}
@@ -43,10 +43,12 @@ namespace NzbDrone.Api.Indexers
_prioritizeDownloadDecision = prioritizeDownloadDecision;
_downloadService = downloadService;
_logger = logger;
GetResourceAll = GetReleases;
Post["/"] = x => DownloadRelease(this.Bind<ReleaseResource>());
PostValidator.RuleFor(s => s.DownloadAllowed).Equal(true);
PostValidator.RuleFor(s => s.Guid).NotEmpty();
_remoteEpisodeCache = cacheManager.GetCache<RemoteEpisode>(GetType(), "remoteEpisodes");
}
@@ -0,0 +1,40 @@
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Core.MediaFiles.EpisodeImport.Manual;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Api.ManualImport
{
public class ManualImportModule : NzbDroneRestModule<ManualImportResource>
{
private readonly IManualImportService _manualImportService;
public ManualImportModule(IManualImportService manualImportService)
: base("/manualimport")
{
_manualImportService = manualImportService;
GetResourceAll = GetMediaFiles;
}
private List<ManualImportResource> GetMediaFiles()
{
var folderQuery = Request.Query.folder;
var folder = (string)folderQuery.Value;
var downloadIdQuery = Request.Query.downloadId;
var downloadId = (string)downloadIdQuery.Value;
return ToListResource(_manualImportService.GetMediaFiles(folder, downloadId)).Select(AddQualityWeight).ToList();
}
private ManualImportResource AddQualityWeight(ManualImportResource item)
{
item.QualityWeight = Quality.DefaultQualityDefinitions.Single(q => q.Quality == item.Quality.Quality).Weight;
item.QualityWeight += item.Quality.Revision.Real * 10;
item.QualityWeight += item.Quality.Revision.Version;
return item;
}
}
}
@@ -0,0 +1,32 @@
using System.Collections.Generic;
using NzbDrone.Api.Episodes;
using NzbDrone.Api.REST;
using NzbDrone.Api.Series;
using NzbDrone.Core.DecisionEngine;
using NzbDrone.Core.Qualities;
namespace NzbDrone.Api.ManualImport
{
public class ManualImportResource : RestResource
{
public string Path { get; set; }
public string RelativePath { get; set; }
public string Name { get; set; }
public long Size { get; set; }
public SeriesResource Series { get; set; }
public int? SeasonNumber { get; set; }
public List<EpisodeResource> Episodes { get; set; }
public QualityModel Quality { get; set; }
public int QualityWeight { get; set; }
public string DownloadId { get; set; }
public IEnumerable<Rejection> Rejections { get; set; }
public int Id
{
get
{
return Path.GetHashCode();
}
}
}
}
@@ -29,7 +29,7 @@ namespace NzbDrone.Api.MediaCovers
{
var filePath = Path.Combine(_appFolderInfo.GetAppDataPath(), "MediaCover", seriesId.ToString(), filename);
if (!_diskProvider.FileExists(filePath))
if (!_diskProvider.FileExists(filePath) || _diskProvider.GetFileSize(filePath) == 0)
{
// Return the full sized image if someone requests a non-existing resized one.
// TODO: This code can be removed later once everyone had the update for a while.
+2 -2
View File
@@ -9,10 +9,10 @@ namespace NzbDrone.Api.Metadata
{
}
protected override void Validate(MetadataDefinition definition)
protected override void Validate(MetadataDefinition definition, bool includeWarnings)
{
if (!definition.Enable) return;
base.Validate(definition);
base.Validate(definition, includeWarnings);
}
}
}
@@ -9,10 +9,10 @@ namespace NzbDrone.Api.Notifications
{
}
protected override void Validate(NotificationDefinition definition)
protected override void Validate(NotificationDefinition definition, bool includeWarnings)
{
if (!definition.OnGrab && !definition.OnDownload) return;
base.Validate(definition);
base.Validate(definition, includeWarnings);
}
}
}
@@ -5,11 +5,11 @@ namespace NzbDrone.Api.Notifications
{
public class NotificationResource : ProviderResource
{
public String Link { get; set; }
public Boolean OnGrab { get; set; }
public Boolean OnDownload { get; set; }
public Boolean OnUpgrade { get; set; }
public String TestCommand { get; set; }
public HashSet<Int32> Tags { get; set; }
public string Link { get; set; }
public bool OnGrab { get; set; }
public bool OnDownload { get; set; }
public bool OnUpgrade { get; set; }
public string TestCommand { get; set; }
public HashSet<int> Tags { get; set; }
}
}
+11 -1
View File
@@ -52,6 +52,9 @@
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\Nancy.Authentication.Basic.0.23.2\lib\net40\Nancy.Authentication.Basic.dll</HintPath>
</Reference>
<Reference Include="Nancy.Authentication.Forms">
<HintPath>..\packages\Nancy.Authentication.Forms.0.23.2\lib\net40\Nancy.Authentication.Forms.dll</HintPath>
</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>
@@ -80,6 +83,8 @@
</Compile>
<Compile Include="Authentication\AuthenticationService.cs" />
<Compile Include="Authentication\EnableAuthInNancy.cs" />
<Compile Include="Authentication\AuthenticationModule.cs" />
<Compile Include="Authentication\LoginResource.cs" />
<Compile Include="Authentication\NzbDroneUser.cs" />
<Compile Include="Blacklist\BlacklistModule.cs" />
<Compile Include="Blacklist\BlacklistResource.cs" />
@@ -94,6 +99,11 @@
<Compile Include="Commands\CommandResource.cs" />
<Compile Include="Extensions\AccessControlHeaders.cs" />
<Compile Include="Extensions\Pipelines\CorsPipeline.cs" />
<Compile Include="Frontend\Mappers\LoginHtmlMapper.cs" />
<Compile Include="Parse\ParseModule.cs" />
<Compile Include="Parse\ParseResource.cs" />
<Compile Include="ManualImport\ManualImportModule.cs" />
<Compile Include="ManualImport\ManualImportResource.cs" />
<Compile Include="Profiles\Delay\DelayProfileModule.cs" />
<Compile Include="Profiles\Delay\DelayProfileResource.cs" />
<Compile Include="Profiles\Delay\DelayProfileValidator.cs" />
@@ -254,7 +264,7 @@
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\nuget.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
+49
View File
@@ -0,0 +1,49 @@
using NzbDrone.Core.Parser;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Api.Parse
{
public class ParseModule : NzbDroneRestModule<ParseResource>
{
private readonly IParsingService _parsingService;
public ParseModule(IParsingService parsingService)
{
_parsingService = parsingService;
GetResourceSingle = Parse;
}
private ParseResource Parse()
{
var title = Request.Query.Title.Value;
var parsedEpisodeInfo = Parser.ParseTitle(title);
if (parsedEpisodeInfo == null)
{
return null;
}
var remoteEpisode = _parsingService.Map(parsedEpisodeInfo);
if (remoteEpisode == null)
{
remoteEpisode = new RemoteEpisode
{
ParsedEpisodeInfo = parsedEpisodeInfo
};
return new ParseResource
{
Title = title,
ParsedEpisodeInfo = parsedEpisodeInfo
};
}
var resource = ToResource(remoteEpisode);
resource.Title = title;
return resource;
}
}
}
+17
View File
@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using NzbDrone.Api.Episodes;
using NzbDrone.Api.REST;
using NzbDrone.Api.Series;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Api.Parse
{
public class ParseResource : RestResource
{
public string Title { get; set; }
public ParsedEpisodeInfo ParsedEpisodeInfo { get; set; }
public SeriesResource Series { get; set; }
public List<EpisodeResource> Episodes { get; set; }
}
}
+39 -23
View File
@@ -2,12 +2,14 @@
using System.Collections.Generic;
using System.Linq;
using FluentValidation;
using FluentValidation.Results;
using Nancy;
using NzbDrone.Api.ClientSchema;
using NzbDrone.Api.Extensions;
using NzbDrone.Api.Mapping;
using NzbDrone.Common.Reflection;
using NzbDrone.Core.ThingiProvider;
using NzbDrone.Core.Validation;
using Omu.ValueInjecter;
namespace NzbDrone.Api
@@ -53,9 +55,9 @@ namespace NzbDrone.Api
private List<TProviderResource> GetAll()
{
var providerDefinitions = _providerFactory.All();
var providerDefinitions = _providerFactory.All().OrderBy(p => p.ImplementationName);
var result = new List<TProviderResource>(providerDefinitions.Count);
var result = new List<TProviderResource>(providerDefinitions.Count());
foreach (var definition in providerDefinitions)
{
@@ -67,16 +69,16 @@ namespace NzbDrone.Api
result.Add(providerResource);
}
return result;
return result.OrderBy(p => p.Name).ToList();
}
private int CreateProvider(TProviderResource providerResource)
{
var providerDefinition = GetDefinition(providerResource);
var providerDefinition = GetDefinition(providerResource, false);
if (providerDefinition.Enable)
{
Test(providerDefinition);
Test(providerDefinition, false);
}
providerDefinition = _providerFactory.Create(providerDefinition);
@@ -86,12 +88,17 @@ namespace NzbDrone.Api
private void UpdateProvider(TProviderResource providerResource)
{
var providerDefinition = GetDefinition(providerResource);
var providerDefinition = GetDefinition(providerResource, false);
if (providerDefinition.Enable)
{
Test(providerDefinition, false);
}
_providerFactory.Update(providerDefinition);
}
private TProviderDefinition GetDefinition(TProviderResource providerResource)
private TProviderDefinition GetDefinition(TProviderResource providerResource, bool includeWarnings = false)
{
var definition = new TProviderDefinition();
@@ -105,7 +112,7 @@ namespace NzbDrone.Api
var configContract = ReflectionExtensions.CoreAssembly.FindTypeByName(definition.ConfigContract);
definition.Settings = (IProviderConfig)SchemaBuilder.ReadFormSchema(providerResource.Fields, configContract, preset);
Validate(definition);
Validate(definition, includeWarnings);
return definition;
}
@@ -117,7 +124,7 @@ namespace NzbDrone.Api
private Response GetTemplates()
{
var defaultDefinitions = _providerFactory.GetDefaultDefinitions().ToList();
var defaultDefinitions = _providerFactory.GetDefaultDefinitions().OrderBy(p => p.ImplementationName).ToList();
var result = new List<TProviderResource>(defaultDefinitions.Count());
@@ -149,31 +156,40 @@ namespace NzbDrone.Api
private Response Test(TProviderResource providerResource)
{
var providerDefinition = GetDefinition(providerResource);
var providerDefinition = GetDefinition(providerResource, true);
Test(providerDefinition);
Test(providerDefinition, true);
return "{}";
}
private void Test(TProviderDefinition providerDefinition)
protected virtual void Validate(TProviderDefinition definition, bool includeWarnings)
{
var result = _providerFactory.Test(providerDefinition);
var validationResult = definition.Settings.Validate();
VerifyValidationResult(validationResult, includeWarnings);
}
protected virtual void Test(TProviderDefinition definition, bool includeWarnings)
{
var validationResult = _providerFactory.Test(definition);
VerifyValidationResult(validationResult, includeWarnings);
}
protected void VerifyValidationResult(ValidationResult validationResult, bool includeWarnings)
{
var result = new NzbDroneValidationResult(validationResult.Errors);
if (includeWarnings && (!result.IsValid || result.HasWarnings))
{
throw new ValidationException(result.Failures);
}
if (!result.IsValid)
{
throw new ValidationException(result.Errors);
}
}
protected virtual void Validate(TProviderDefinition definition)
{
var validationResult = definition.Settings.Validate();
if (!validationResult.IsValid)
{
throw new ValidationException(validationResult.Errors);
}
}
}
}
+6 -6
View File
@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using NzbDrone.Api.ClientSchema;
using NzbDrone.Api.REST;
@@ -7,11 +6,12 @@ namespace NzbDrone.Api
{
public class ProviderResource : RestResource
{
public String Name { get; set; }
public string Name { get; set; }
public List<Field> Fields { get; set; }
public String Implementation { get; set; }
public String ConfigContract { get; set; }
public String InfoLink { get; set; }
public string ImplementationName { get; set; }
public string Implementation { get; set; }
public string ConfigContract { get; set; }
public string InfoLink { get; set; }
public List<ProviderResource> Presets { get; set; }
}
@@ -11,7 +11,9 @@ namespace NzbDrone.Api.RemotePathMappings
{
private readonly IRemotePathMappingService _remotePathMappingService;
public RemotePathMappingModule(IRemotePathMappingService remotePathMappingService, PathExistsValidator pathExistsValidator)
public RemotePathMappingModule(IRemotePathMappingService remotePathMappingService,
PathExistsValidator pathExistsValidator,
MappedNetworkDriveValidator mappedNetworkDriveValidator)
{
_remotePathMappingService = remotePathMappingService;
@@ -31,6 +33,7 @@ namespace NzbDrone.Api.RemotePathMappings
SharedValidator.RuleFor(c => c.LocalPath)
.Cascade(CascadeMode.StopOnFirstFailure)
.IsValidPath()
.SetValidator(mappedNetworkDriveValidator)
.SetValidator(pathExistsValidator);
}
@@ -15,7 +15,8 @@ namespace NzbDrone.Api.RootFolders
IBroadcastSignalRMessage signalRBroadcaster,
RootFolderValidator rootFolderValidator,
PathExistsValidator pathExistsValidator,
DroneFactoryValidator droneFactoryValidator)
DroneFactoryValidator droneFactoryValidator,
MappedNetworkDriveValidator mappedNetworkDriveValidator)
: base(signalRBroadcaster)
{
_rootFolderService = rootFolderService;
@@ -29,8 +30,9 @@ namespace NzbDrone.Api.RootFolders
.Cascade(CascadeMode.StopOnFirstFailure)
.IsValidPath()
.SetValidator(rootFolderValidator)
.SetValidator(pathExistsValidator)
.SetValidator(droneFactoryValidator);
.SetValidator(droneFactoryValidator)
.SetValidator(mappedNetworkDriveValidator)
.SetValidator(pathExistsValidator);
}
private RootFolderResource GetRootFolder(int id)
@@ -13,7 +13,7 @@ namespace NzbDrone.Api.Series
private readonly ISearchForNewSeries _searchProxy;
public SeriesLookupModule(ISearchForNewSeries searchProxy)
: base("/Series/lookup")
: base("/series/lookup")
{
_searchProxy = searchProxy;
Get["/"] = x => Search();

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