1
0
mirror of https://github.com/Radarr/Radarr.git synced 2026-03-26 17:44:24 -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
.gitignore vendored
View File

@@ -122,3 +122,4 @@ setup/Output/
#VS outout folders
bin
obj
output/*

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
Logo/Sonarr.svg Normal file
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

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
commonjsCleanup.linq Normal file
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

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']);
});

View File

@@ -5,19 +5,23 @@ 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));
});
gulp.task('copyContent', function () {
return gulp.src([paths.src.content + '**/*.*', '!**/*.less'])
.pipe(gulp.dest(paths.dest.content));
});
});

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');

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));
});

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));
});

View File

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

View File

@@ -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'],

View File

@@ -1,4 +0,0 @@
var replace = require('gulp-replace');
module.exports = function() {
return replace(/^\uFEFF/, '');
};

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();
});
});

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));
};

View File

@@ -8,23 +8,26 @@ 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
gulp/webpack.js Normal file
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(''));
});

View File

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

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

View File

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

View File

@@ -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
schemas/torznab.xsd Normal file
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>

View File

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

View File

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

View File

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

View File

@@ -122,7 +122,7 @@ namespace Microsoft.AspNet.SignalR
{
if (user == null)
{
throw new ArgumentNullException("user");
return false;
}
if (!user.Identity.IsAuthenticated)

View File

@@ -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);
}
}
}

View File

@@ -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>

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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)
{

View File

@@ -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);
}
}
}

View File

@@ -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;

View File

@@ -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.
}
}
}
}

View File

@@ -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;
}

View File

@@ -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;
}
}
}
}

View File

@@ -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)
{

View File

@@ -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)
{

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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; }

View File

@@ -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")]

View File

@@ -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);

View File

@@ -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" />
@@ -280,4 +282,4 @@
<Target Name="BeforeBuild">
</Target>
-->
</Project>
</Project>

View File

@@ -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);

View File

@@ -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";

View File

@@ -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);
}

View File

@@ -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();

View File

@@ -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;

View File

@@ -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);

View File

@@ -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()

View File

@@ -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;
}

View File

@@ -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
{

View File

@@ -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;
}

View File

@@ -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"); }

View File

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

View File

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

View File

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

View File

@@ -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
});
}
}
}

View File

@@ -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 + "/");
}
}
}

View File

@@ -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;
private static AuthenticationType AUTH_METHOD;
public AuthenticationService(IConfigFileProvider configFileProvider)
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;
}

View File

@@ -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);
}
}
}
}
}
}

View File

@@ -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; }
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,10 +4,9 @@ using System.Linq;
using NzbDrone.Api.Extensions;
using NzbDrone.Api.Mapping;
using NzbDrone.Api.Validation;
using NzbDrone.Common.Composition;
using NzbDrone.Common;
using NzbDrone.Core.Datastore.Events;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Messaging.Commands.Tracking;
using NzbDrone.Core.Messaging.Events;
using NzbDrone.Core.ProgressMessaging;
using NzbDrone.SignalR;
@@ -15,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);
}

View File

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

View File

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

View File

@@ -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);
}
}
}
}

View File

@@ -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; }

View File

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

View File

@@ -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);
}
}
}

View File

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

View File

@@ -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);
}
}
}

View File

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

View File

@@ -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)

View File

@@ -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);
}

View File

@@ -17,7 +17,7 @@ namespace NzbDrone.Api.Frontend.Mappers
private readonly IAnalyticsService _analyticsService;
private readonly Func<ICacheBreakerProvider> _cacheBreakProviderFactory;
private readonly string _indexPath;
private static readonly Regex ReplaceRegex = new Regex("(?<=(?:href|src|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)

View File

@@ -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;
}
}
}

View File

@@ -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)

View File

@@ -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");

View File

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

View File

@@ -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);
}
}
}

View File

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

View File

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

View File

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

View File

@@ -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.

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);
}
}
}

View File

@@ -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);
}
}
}

View File

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

View File

@@ -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">
@@ -262,4 +272,4 @@
<Target Name="AfterBuild">
</Target>
-->
</Project>
</Project>

View File

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

View File

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

View File

@@ -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);
}
}
}
}

View File

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

View File

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

View File

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

View File

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

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