1
0
mirror of https://github.com/Sonarr/Sonarr.git synced 2026-04-18 21:35:27 -04:00

Compare commits

...

138 Commits

Author SHA1 Message Date
Taloth Saldono 40b34dbb5e Do custom gzip compression on mono when returning api responses to avoid a memory leak. 2019-01-08 20:34:35 +01:00
Taloth Saldono 567719e6c4 Only do the custom decompression on mono. 2019-01-08 20:33:50 +01:00
Taloth Saldono 76212ef908 Do our own decompression to workaround mono bug. 2019-01-04 23:29:32 +01:00
Taloth Saldono c14b339d80 Fixed typo in postinst preventing it from properly upgrading v3 alpha installs. 2019-01-02 22:06:58 +01:00
Taloth Saldono 8f4067adbb Also deal with migrations where the user installed using a different user. 2018-12-30 15:27:41 +01:00
Taloth Saldono 54160850b2 Increased debconf priority for username to high. 2018-12-29 13:51:34 +01:00
Taloth Saldono 524048c4b4 Also force branch if an external updater is used. 2018-12-29 13:51:34 +01:00
Taloth Saldono 565e625e21 Updated selection in UpdateSettings for external updaters. 2018-12-29 13:51:34 +01:00
Taloth Saldono 1ccae530d9 Added missing interval unit to Backup Settings. 2018-12-29 13:51:34 +01:00
Taloth Saldono e8161aa9f4 Added specific external updater messages for Apt and Docker. 2018-12-29 13:51:34 +01:00
Mark McDowall 5a24b8d294 Add PackageUpdateMechanism to system/status response 2018-12-29 13:49:42 +01:00
Mark McDowall aafa9cb6be Update UI
Squash into v3 UI
2018-12-29 13:49:42 +01:00
Taloth Saldono dbdb8e4436 Added System.Native to the ignored list since afaik it's not used by SharpRaven. 2018-12-29 13:49:42 +01:00
Taloth Saldono bfe2976463 Fixed failing tests. 2018-12-29 13:49:42 +01:00
Taloth Saldono 080bf6787a Fixed wrong sed wildcard. 2018-12-29 13:49:42 +01:00
Taloth Saldono 066a9de7ae Parse the package_info file to determine whether the UpdateMechanism is configured properly. 2018-12-29 13:49:42 +01:00
Taloth Saldono f87e2dd717 Copy unix-specific System.Runtime.InteropServices.RuntimeInformation to linux output. 2018-12-29 13:49:42 +01:00
Taloth Saldono 1cf80c3ba0 Added release_info to build output instead of only in the debian package. 2018-12-29 13:49:42 +01:00
Taloth Saldono 6345d0e73b Replace Major.Minor.Revision in the TC provided BuildNumber with the packageVersion. 2018-12-29 13:49:42 +01:00
Taloth Saldono 7f5c1b0d5f Added support to migrate existing nzbdrone+systemd installs. 2018-12-29 13:49:42 +01:00
Taloth Saldono b7259e3ebb Added support to handle the BuiltIn Updater for 3.0.1 packages. 2018-12-29 13:49:42 +01:00
Taloth Saldono fb6e4c0130 Various fixes 2018-12-29 13:49:42 +01:00
Taloth Saldono 241c26e099 Purge should use the data folder as specified by debconf 2018-12-29 13:49:42 +01:00
Taloth Saldono e2ccb4c049 Deal with wrong permissions for folders. 2018-12-29 13:49:42 +01:00
Taloth Saldono 975fe98cbf Moved from /opt back to /usr/lib based on debian policy. 2018-12-29 13:49:42 +01:00
Taloth Saldono 40a7cf32c5 Added debconf to allow user to set user, group and config dir. Only the group is high prio and thus asked for by default. 2018-12-29 13:49:42 +01:00
Taloth Saldono 9e909cafdd Added check in preinst to cancel install if NzbDrone is still running. 2018-12-29 13:49:42 +01:00
Taloth Saldono db47308762 Tell dpkg that sonarr replaces nzbdrone instead of a pure conflict. 2018-12-29 13:49:42 +01:00
Taloth Saldono 6d41f7895f Move binaries to subdir for easier builtin updater changes. 2018-12-29 13:49:42 +01:00
Taloth Saldono 7cf4c6f655 Use clideps to determine mono dependencies instead of the monolithic libmono-cil-dev. 2018-12-29 13:49:42 +01:00
Taloth Saldono c63186a798 Use dh_systemd to configure systemd unit installation. 2018-12-29 13:49:42 +01:00
Mark McDowall a0ce2d7cdf New: Parse release in square brackets at end of file name
Closes #966
2018-12-29 13:08:57 +01:00
Taloth Saldono de61ecbfd2 Show unknown items in queue
New: Download items from unknown series now appear on the queue and can be imported with Manual Import.
2018-12-29 13:08:57 +01:00
Mark McDowall 5f6f4915a1 Delay import when absolute episode number is missing
New: Delay import is absolute episode number is required and is missing for EpisodeFormat
2018-12-29 13:08:56 +01:00
Mark McDowall f345977e3f Add upgrade allowed to language and profiles 2018-12-29 13:08:56 +01:00
Mark McDowall 853f25468c Preferred words
New: Ability to prefer releases based on terms in release title
2018-12-29 13:08:56 +01:00
Mark McDowall ac709c39ab New: Default size limits 2018-12-29 13:08:55 +01:00
Mark McDowall 4939b77a6c Upgrade signalR to 2.4.0 2018-12-29 13:08:55 +01:00
Mark McDowall ded0caee39 New: Series title first character renaming token 2018-12-29 13:08:55 +01:00
Mark McDowall 3b9ed49d0f New: Option to disable rescanning of series folder after RefreshSeriesInfo
Closes #1592
2018-12-29 13:08:54 +01:00
Mark McDowall 7e79ccd710 Fixed: Finding files with unexpected quality in the filename 2018-12-29 13:08:54 +01:00
Mark McDowall 575c5af1f8 Upgrade SharpRaven to 2.4.0 2018-12-29 13:08:54 +01:00
Mark McDowall ca079a77ce New: Renaming tokens for IMDB ID, TVDB ID and TV Maze ID
Closes #2659
2018-12-29 13:08:54 +01:00
Mark McDowall a550186337 New: Choose extension for magnet links in Torrent Blackhole
Closes #2165
2018-12-29 13:08:53 +01:00
Mark McDowall 8594a93cf5 New: Pushover device setting 2018-12-29 13:08:53 +01:00
Mark McDowall a86397f4bd New: Choose PushBullet device from the UI 2018-12-29 13:08:53 +01:00
Mark McDowall d52b63e5e9 New: Add tags to Kodi metadata 2018-12-29 13:08:53 +01:00
Mark McDowall 400838c79a New: Series Title with Year renaming token 2018-12-29 13:08:52 +01:00
Mark McDowall 62b03adb53 Removed NotifyMyAndroid and Pushalot
Fixed: Removed NotifyMyAndroid notifications
Fixed: Remove Pushalot notifications
2018-12-29 13:08:52 +01:00
Mark McDowall ffc49c9049 Fixed: Removed old Media Browser metadata 2018-12-29 13:08:52 +01:00
Mark McDowall 3b042707f5 Interactive Season Search
New: Interactive Season Search
Fixed: BTN will no longer include season packs for individual episode searches
2018-12-29 13:08:52 +01:00
Mark McDowall 36b9c51163 Improved Command Queue
New: Limit concurrent tasks that require disk access
New: Prevent other tasks from running during application update
2018-12-29 13:08:51 +01:00
Mark McDowall 07be9cf47a New: Improved Plex Media Server authentication (Manually update settings) 2018-12-29 13:08:51 +01:00
Mark McDowall 897f3fea99 New: Bluray 1080p and 2160p remux qualities 2018-12-29 13:08:51 +01:00
Mark McDowall a3b90650be Remove unused references 2018-12-29 13:08:50 +01:00
Mark McDowall 673efe46fb MSBuild 15 2018-12-29 13:08:50 +01:00
Mark McDowall a17a60f914 Updated Debian package 2018-12-29 13:08:48 +01:00
Mark McDowall 07cbc71aa2 Secure URLs for Skyhook and Services 2018-12-29 13:08:48 +01:00
Mark McDowall 81d6c0d210 New: Backup options and restoration 2018-12-29 13:08:48 +01:00
Mark McDowall fdbe45c0ab New: Ability to rename using long paths when available 2018-12-29 13:08:47 +01:00
Mark McDowall 5666369cc4 Fixed: Default Windows Service Account 2018-12-29 13:08:47 +01:00
Mark McDowall e3b3e65409 ESLint and Stylelint during build 2018-12-29 13:08:47 +01:00
Mark McDowall c3166e08b4 Migrate AppData folder 2018-12-29 13:08:47 +01:00
Mark McDowall adaf428aa7 v3 Binaries 2018-12-29 13:08:47 +01:00
Mark McDowall ef70df1db3 Update Logos 2018-12-29 13:08:46 +01:00
Keivan Beigi c69ca290cf Removed anycpu from sln file 2018-12-29 13:08:46 +01:00
Keivan Beigi 50d4d7a19d removed legacy UI components 2018-12-29 13:08:46 +01:00
Keivan Beigi ab12c51e0f removed idea folder 2018-12-29 13:08:45 +01:00
Keivan Beigi e343f8e35e Removed old UI 2018-12-29 13:08:45 +01:00
Mark McDowall 5894b4fd95 v3 UI 2018-12-29 13:08:43 +01:00
Mark McDowall 99feff549d Upgrade Owin to 3.1.0 2018-12-29 13:08:42 +01:00
Mark McDowall 37518349b9 Upgrade Nancy to 1.4.4 2018-12-29 13:08:42 +01:00
Mark McDowall 5b4e194f25 Upgrade signalR to 2.2.2 2018-12-29 13:08:42 +01:00
Mark McDowall 296881b358 Target .net 4.6.1 2018-12-29 13:08:41 +01:00
Mark McDowall efb9c0791f New: Testing Custom Script executes the script and verifies the exit code 2018-12-29 13:08:41 +01:00
Mark McDowall acc901455b New: Separate automatic and interactive searches
Closes #253
2018-12-29 13:08:40 +01:00
Mark McDowall cfb7494992 Removed Drone Factory 2018-12-29 13:08:40 +01:00
Mark McDowall f31ac39e37 Quality groups 2018-12-29 13:08:39 +01:00
vawen 068ea1e934 New: Language Profiles
Closes #274
2018-12-29 13:08:38 +01:00
Mark McDowall 7297c1b8e4 v3 API 2018-12-29 13:08:37 +01:00
Mark McDowall 211f3769e1 Sonarr.sln 2018-12-29 13:08:23 +01:00
Mark McDowall 2686c78f06 Simplified logo 2018-12-29 13:08:22 +01:00
Mark McDowall 5b385d9900 Build fixes and cleanup 2018-12-29 13:08:22 +01:00
Taloth Saldono 31b266659e Fixed bad test due to skyhook now doing it's own fuzzy search. 2018-12-29 13:05:03 +01:00
Taloth Saldono e071b0c2e0 DataMapper LazyLoaded needlessly keeping the parent mapper alive. 2018-12-29 12:45:07 +01:00
Taloth Saldono 270f04d2d2 Fixed: Excessive memory usage due to sqlite cache configuration.
ref #2296
2018-12-29 12:43:35 +01:00
Mark McDowall 9af57c6786 New: Store last search time for EpisodeSearch
Closes #420
2018-12-06 20:59:09 -08:00
Mark McDowall ff4a550cbb New: Include OriginalFilePath with Episode Files
Closes #2336
2018-12-06 20:59:09 -08:00
Kevin Richter 537e4d7c39 Fix Quality Detection with DDP5.1 2018-11-24 11:24:24 +01:00
Taloth Saldono 9f16d9b2fc Fixed: File names and release titles lacking a series title and starting with the Air date.
fixes #2825
2018-11-21 22:02:51 +01:00
Taloth Saldono ae6d920e2a Updated error message if skyhook and other services respond with html content.
closes #2817
2018-11-14 21:48:56 +01:00
Mark McDowall 0d22f9ec29 Improve logging when rejecting release with unmonitored episodes 2018-11-11 21:11:14 -08:00
Mark McDowall 699076a405 New: Added warning for Download Station that 2FA is not supported
Closes #2451
2018-11-10 16:23:33 -08:00
Jeffrey Neer df593f486f New: Added priority levels to Join Notifications 2018-11-10 14:51:14 -08:00
Mark McDowall 0d95873a05 New: Parsing french anime releases with single absolute episode number
Closes #2798
2018-11-03 18:42:06 -07:00
Mark McDowall b20acc9063 Fixed: Sort The A-Team properly in series list 2018-11-03 11:58:00 -07:00
Mark McDowall 70d6d25178 New: Parse names with 1080i as 1080p if they are not RAW HD
Closes #2793
2018-11-03 11:52:02 -07:00
Mark McDowall 196d165432 New: Parse names with FHD as 1080p
Closes #2793
2018-11-03 11:45:34 -07:00
Mark McDowall bb3ca998fc Restrict 4k parsing to avoid false positives 2018-11-03 11:30:41 -07:00
Mark McDowall da73221cef Fixed: Handling of poorly formed items when parsing results from indexer 2018-10-24 20:43:52 -07:00
Mark McDowall 36f66eed21 New: Parse names with 4k as 2160p
Closes #2788
2018-10-24 20:13:57 -07:00
Mark McDowall 8e916d60f5 Fixed: Parsing of specials with only season and episode numbers in the file name 2018-10-24 18:32:22 -07:00
Mark McDowall 44048207f2 Remove file quality matches release import spec
New: Don't reject imports when quality doesn't match release quality
New: Reject grab when release was grabbed and imported already
Closes #2783
2018-10-22 20:37:32 -07:00
Mark McDowall b73b99df8d Fixed: Don't clean Kodi library if Always Update is disabled and video is playing
Fixes #2773
2018-10-22 14:20:22 -07:00
Mark McDowall ad69ecc5eb Fixed: Use season number from episode instead of parsed from release for custom scripts
Closes #2748
2018-10-07 19:03:32 -07:00
Mark McDowall 1304bc8fb9 Fixed: Exclude /snap/* locations from disk space
Closes #2743
2018-10-07 19:03:32 -07:00
Mark McDowall a4f63e728c Fixed: Don't use media info for non-video files
Fixes #2745
2018-10-07 19:03:32 -07:00
Jeff Byrnes 307b3536b7 New: Compatibility with Hombrew-installed mono 2018-09-15 10:49:22 -07:00
Mark McDowall 24c6d3f4b3 Don't read response stream if it equals Stream.Null 2018-09-14 17:50:13 -07:00
Mark McDowall 4a052708c8 New: Updated pushover app clone URL 2018-09-04 00:19:09 -07:00
Mark McDowall 39a8d4f0d8 Fixed: Parsing of new hashed release filenames (######_##.ext) 2018-09-03 11:24:48 -07:00
Mark McDowall ca22a25842 New: Add stopped option for rTorrent 2018-08-28 18:14:55 -07:00
Mark McDowall ff9a9a5e4d More restrictions when using download client title or folder name for parsing
Fixes #2663
2018-08-27 21:35:03 -07:00
Mark McDowall 3d7c59bc3b New: Add unique IDs to Kodi metadata
Closes #2711
2018-08-27 20:42:32 -07:00
Mark McDowall 63ea1f1afd Fixed: Skip sample check when rescanning series folder 2018-08-19 09:23:34 -07:00
Mark McDowall baf8f6cca6 Fixed: Parsing multi-episode in square bracket
Fixes #2669
2018-08-18 11:22:51 -07:00
Mark McDowall c67c7e1b5a More flexible matching some anime releases 2018-08-18 10:56:18 -07:00
Mark McDowall 46d8e5830a Fixed: Concurrent manual imports silently failing 2018-08-18 10:56:18 -07:00
Taloth Saldono 37054673b7 Fixed: Too big eta in qbit api still occurring on official builds. 2018-08-07 19:08:38 +02:00
Mark McDowall 86bc5c5547 Fixed: Parsing of some anime releases with season number in title
Fixes #2684
2018-07-30 19:20:40 -07:00
Taloth Saldono fc44607c73 Added missing UrlBase validation for SabnzbdSettings. 2018-07-18 07:39:58 +02:00
Taloth Saldono 2a1421f488 Fixed: Skip torrents in Deluge api that don't have hashes.
closes #2566
closes #2567
closes #2664
2018-07-16 19:16:26 +02:00
Nicholas Landriault d7a054f637 Deluge torrents that don't have a hash are skipped
In some cases torrents in Deluge may not have a hash (ex: https://torguard.net/checkmytorrentipaddress.php). This causes Sonarr to fail when loading the torrents from Deluge with error message: 'Unable to communicate with deluge. Object reference not set to an instance of an object'. This commit simply causes Sonarr to skip over the torrent with the missing hash and continue loading torrents that do have hashes.
2018-07-16 19:11:10 +02:00
Taloth Saldono 9c9ad9aec3 New: Added optional UrlBase to Nzbget and Sabnzbd settings.
ref #1651
2018-07-15 12:24:27 +02:00
Mark McDowall 1467c52e03 Fixed: Multi-file torrents in Vuze with different folder and file names
Fixes #2571
2018-07-11 19:01:41 -07:00
Mark McDowall e407145d10 Fixed: .vtt files treated as subtitles 2018-07-08 19:22:42 -07:00
Taloth Saldono 476110b1de Fixed: Store BitRate_Nominal (VBR) mediainfo in database instead of only BitRate.
ref Radarr/Radarr#2860
2018-07-08 18:36:36 +02:00
Taloth Saldono 45f9f45f50 Fixed: Quality parser for the rare HD-DVD. 2018-07-07 20:04:45 +02:00
Taloth Saldono d581d997c2 Fixed: Ignore /etc in System disk overview. 2018-07-07 11:09:04 +02:00
Taloth Saldono 633344e5bb Disabled httpbin.org tests for now due to the site being flaky. 2018-07-06 21:53:03 +02:00
Taloth Saldono 0cce6b74f9 Added logging of json snippets on json deserialization errors. 2018-07-06 21:44:15 +02:00
Taloth Saldono 8b8bfb9bf0 Added third httpbin site. 2018-07-06 19:50:29 +02:00
Taloth Saldono 7241ca4ae9 Run http tests more gracefully. 2018-07-06 19:09:59 +02:00
Taloth Saldono e9b11e55e9 Fixed: Regression with importing nested obfuscated directories.
Closes #2640
2018-07-04 21:55:08 +02:00
Mark McDowall 48126f55ed Fixed: Parsing titles with question marks
Fixes #2637
2018-06-27 19:54:25 -07:00
Mark McDowall cb549507ee Fixed: Parsing dates using underscores for separation 2018-06-27 18:32:28 -07:00
Mark McDowall a0b6cdb08e Fixed: Forced seeding in QBittorrent status treated as complete 2018-06-27 18:31:44 -07:00
Taloth Saldono 9b9597093c Fixed: Regression causing Manual Import to ignore user provided information. 2018-06-21 07:23:20 +02:00
3185 changed files with 114292 additions and 126622 deletions
+2 -2
View File
@@ -2,14 +2,14 @@
# editorconfig.org # editorconfig.org
root = true root = true
[*.{cs,html,js,hbs}] [*.{cs}]
charset = utf-8 charset = utf-8
trim_trailing_whitespace = true trim_trailing_whitespace = true
insert_final_newline = true insert_final_newline = true
indent_style = space indent_style = space
indent_size = 4 indent_size = 4
[*.less] [*.{js,html,js,hbs,less}]
charset = utf-8 charset = utf-8
trim_trailing_whitespace = true trim_trailing_whitespace = true
insert_final_newline = true insert_final_newline = true
+9
View File
@@ -0,0 +1,9 @@
{
"paths": [
"frontend/src/**/*.js"
],
"ignored": [
"**/node_modules/**/*"
],
"port": 5004
}
+1 -1
View File
@@ -120,7 +120,7 @@ _tests/
setup/Output/ setup/Output/
*.~is *.~is
UI.Phantom/ UI/
#VS outout folders #VS outout folders
bin bin
Generated
-1
View File
@@ -1 +0,0 @@
Sonarr
-25
View File
@@ -1,25 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.idea" />
<excludeFolder url="file://$MODULE_DIR$/Logo" />
<excludeFolder url="file://$MODULE_DIR$/_output" />
<excludeFolder url="file://$MODULE_DIR$/_output_mono" />
<excludeFolder url="file://$MODULE_DIR$/_output_osx" />
<excludeFolder url="file://$MODULE_DIR$/_output_osx_app" />
<excludeFolder url="file://$MODULE_DIR$/_start" />
<excludeFolder url="file://$MODULE_DIR$/_tests" />
<excludeFolder url="file://$MODULE_DIR$/debian" />
<excludeFolder url="file://$MODULE_DIR$/node_modules" />
<excludeFolder url="file://$MODULE_DIR$/osx" />
<excludeFolder url="file://$MODULE_DIR$/schemas" />
<excludeFolder url="file://$MODULE_DIR$/setup" />
<excludeFolder url="file://$MODULE_DIR$/src" />
<excludeFolder url="file://$MODULE_DIR$/tools" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Sonarr node_modules" level="project" />
</component>
</module>
-59
View File
@@ -1,59 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectCodeStyleSettingsManager">
<option name="PER_PROJECT_SETTINGS">
<value>
<option name="LINE_SEPARATOR" value="&#13;&#10;" />
<option name="RIGHT_MARGIN" value="190" />
<option name="HTML_ATTRIBUTE_WRAP" value="0" />
<option name="HTML_KEEP_LINE_BREAKS" value="false" />
<option name="HTML_KEEP_BLANK_LINES" value="1" />
<option name="HTML_ALIGN_ATTRIBUTES" value="false" />
<option name="HTML_INLINE_ELEMENTS" value="" />
<option name="HTML_DONT_ADD_BREAKS_IF_INLINE_CONTENT" value="" />
<CssCodeStyleSettings>
<option name="HEX_COLOR_LOWER_CASE" value="true" />
<option name="HEX_COLOR_LONG_FORMAT" value="true" />
<option name="VALUE_ALIGNMENT" value="1" />
</CssCodeStyleSettings>
<JSCodeStyleSettings>
<option name="SPACE_BEFORE_PROPERTY_COLON" value="true" />
<option name="ALIGN_OBJECT_PROPERTIES" value="2" />
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
<option name="OBJECT_LITERAL_WRAP" value="2" />
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
</JSCodeStyleSettings>
<XML>
<option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
</XML>
<codeStyleSettings language="CSS">
<indentOptions>
<option name="SMART_TABS" value="true" />
</indentOptions>
</codeStyleSettings>
<codeStyleSettings language="JavaScript">
<option name="LINE_COMMENT_AT_FIRST_COLUMN" value="true" />
<option name="KEEP_LINE_BREAKS" value="false" />
<option name="KEEP_FIRST_COLUMN_COMMENT" value="false" />
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
<option name="CATCH_ON_NEW_LINE" value="true" />
<option name="FINALLY_ON_NEW_LINE" value="true" />
<option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
<option name="ALIGN_MULTILINE_BINARY_OPERATION" value="true" />
<option name="SPACE_BEFORE_METHOD_PARENTHESES" value="true" />
<option name="CALL_PARAMETERS_WRAP" value="1" />
<option name="BINARY_OPERATION_WRAP" value="1" />
<option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
<option name="ARRAY_INITIALIZER_WRAP" value="2" />
<option name="ARRAY_INITIALIZER_LBRACE_ON_NEXT_LINE" value="true" />
<option name="ARRAY_INITIALIZER_RBRACE_ON_NEXT_LINE" value="true" />
<option name="IF_BRACE_FORCE" value="3" />
<option name="DOWHILE_BRACE_FORCE" value="3" />
<option name="WHILE_BRACE_FORCE" value="3" />
<option name="FOR_BRACE_FORCE" value="3" />
</codeStyleSettings>
</value>
</option>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</component>
</project>
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="PROJECT" charset="UTF-8" />
</component>
</project>
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<includedPredefinedLibrary name="ECMAScript 6" />
</component>
</project>
-14
View File
@@ -1,14 +0,0 @@
<component name="libraryTable">
<library name="Sonarr node_modules" type="javaScript">
<properties>
<option name="frameworkName" value="node_modules" />
<sourceFilesUrls>
<item url="file://$PROJECT_DIR$/node_modules" />
</sourceFilesUrls>
</properties>
<CLASSES>
<root url="file://$PROJECT_DIR$/node_modules" />
</CLASSES>
<SOURCES />
</library>
</component>
-14
View File
@@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectLevelVcsManager" settingsEditedManually="false">
<OptionsSetting value="true" id="Add" />
<OptionsSetting value="true" id="Remove" />
<OptionsSetting value="true" id="Checkout" />
<OptionsSetting value="true" id="Update" />
<OptionsSetting value="true" id="Status" />
<OptionsSetting value="true" id="Edit" />
<ConfirmationsSetting value="0" id="Add" />
<ConfirmationsSetting value="0" id="Remove" />
</component>
<component name="ProjectRootManager" version="2" />
</project>
-8
View File
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/Sonarr.iml" filepath="$PROJECT_DIR$/.idea/Sonarr.iml" />
</modules>
</component>
</project>
Generated
-6
View File
@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
+1
View File
@@ -0,0 +1 @@
save-prefix=""
+1
View File
@@ -0,0 +1 @@
save-prefix ""
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 16 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 490 B

After

Width:  |  Height:  |  Size: 423 B

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 5.8 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 620 B

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 824 B

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 11 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 929 B

+9 -240
View File
@@ -1,240 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <svg height="216.9" viewBox="0 0 216.7 216.9" width="216.7" xmlns="http://www.w3.org/2000/svg">
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> <path clip-rule="evenodd" d="M216.7 108.45c0 29.833-10.533 55.4-31.6 76.7-.7.833-1.483 1.6-2.35 2.3-3.466 3.4-7.133 6.484-11 9.25-18.267 13.467-39.367 20.2-63.3 20.2-23.967 0-45.033-6.733-63.2-20.2-4.8-3.4-9.3-7.25-13.5-11.55-16.367-16.266-26.417-35.167-30.15-56.7-.733-4.2-1.217-8.467-1.45-12.8-.1-2.4-.15-4.8-.15-7.2 0-2.533.05-4.95.15-7.25 0-.233.066-.467.2-.7 1.567-26.6 12.033-49.583 31.4-68.95C53.05 10.517 78.617 0 108.45 0c29.933 0 55.484 10.517 76.65 31.55 21.067 21.433 31.6 47.067 31.6 76.9z" fill="#EEE" fill-rule="evenodd"/>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <path clip-rule="evenodd" d="M194.65 42.5l-22.4 22.4C159.152 77.998 158 89.4 158 109.5c0 17.934 2.852 34.352 16.2 47.7 9.746 9.746 19 18.95 19 18.95-2.5 3.067-5.2 6.067-8.1 9-.7.833-1.483 1.6-2.35 2.3-2.533 2.5-5.167 4.817-7.9 6.95l-17.55-17.55c-15.598-15.6-27.996-17.1-48.6-17.1-19.77 0-33.223 1.822-47.7 16.3-8.647 8.647-18.55 18.6-18.55 18.6-3.767-2.867-7.333-6.034-10.7-9.5-2.8-2.8-5.417-5.667-7.85-8.6 0 0 9.798-9.848 19.15-19.2 13.852-13.853 16.1-29.916 16.1-47.85 0-17.5-2.874-33.823-15.6-46.55-8.835-8.836-21.05-21-21.05-21 2.833-3.6 5.917-7.067 9.25-10.4 2.934-2.867 5.934-5.55 9-8.05L61.1 43.85C74.102 56.852 90.767 60.2 108.7 60.2c18.467 0 35.077-3.577 48.6-17.1 8.32-8.32 19.3-19.25 19.3-19.25 2.9 2.367 5.733 4.933 8.5 7.7 3.467 3.533 6.65 7.183 9.55 10.95z" fill="#3A3F51" fill-rule="evenodd"/>
<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" <g clip-rule="evenodd">
height="218px" viewBox="0 0 218 218" enable-background="new 0 0 218 218" xml:space="preserve"> <path d="M78.7 114c-.2-1.167-.332-2.35-.4-3.55-.032-.667-.05-1.333-.05-2 0-.7.018-1.367.05-2 0-.067.018-.133.05-.2.435-7.367 3.334-13.733 8.7-19.1 5.9-5.833 12.984-8.75 21.25-8.75 8.3 0 15.384 2.917 21.25 8.75 5.834 5.934 8.75 13.033 8.75 21.3 0 8.267-2.916 15.35-8.75 21.25-.2.233-.416.45-.65.65-.966.933-1.982 1.783-3.05 2.55-5.065 3.733-10.916 5.6-17.55 5.6s-12.466-1.866-17.5-5.6c-1.332-.934-2.582-2-3.75-3.2-4.532-4.5-7.316-9.734-8.35-15.7z" fill="#0CF" fill-rule="evenodd"/>
<symbol id="hex_grid" viewBox="-114.25 -98.617 228.55 197.233"> <path d="M157.8 59.75l-15 14.65M30.785 32.526L71.65 73.25m84.6 84.25l27.808 28.78m1.855-153.894L157.8 59.75m-125.45 126l27.35-27.4" fill="none" stroke="#0CF" stroke-miterlimit="1" stroke-width="2"/>
<path d="M157.8 59.75l-16.95 17.2M58.97 60.604l17.2 17.15M59.623 158.43l16.75-17.4m61.928-1.396l18.028 17.945" fill="none" stroke="#0CF" stroke-miterlimit="1" stroke-width="7"/>
<path fill-rule="evenodd" clip-rule="evenodd" fill="none" stroke="#989898" stroke-width="0.5" stroke-linecap="square" stroke-miterlimit="1" d=" </g>
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 </svg>
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>

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

+9
View File
@@ -0,0 +1,9 @@
<svg height="216.9" viewBox="0 0 216.7 216.9" width="216.7" xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd" d="M216.7 108.45c0 29.833-10.533 55.4-31.6 76.7-.7.833-1.483 1.6-2.35 2.3-3.466 3.4-7.133 6.484-11 9.25-18.267 13.467-39.367 20.2-63.3 20.2-23.967 0-45.033-6.733-63.2-20.2-4.8-3.4-9.3-7.25-13.5-11.55-16.367-16.266-26.417-35.167-30.15-56.7-.733-4.2-1.217-8.467-1.45-12.8-.1-2.4-.15-4.8-.15-7.2 0-2.533.05-4.95.15-7.25 0-.233.066-.467.2-.7 1.567-26.6 12.033-49.583 31.4-68.95C53.05 10.517 78.617 0 108.45 0c29.933 0 55.484 10.517 76.65 31.55 21.067 21.433 31.6 47.067 31.6 76.9z" fill="#EEE" fill-rule="evenodd"/>
<path clip-rule="evenodd" d="M194.65 42.5l-22.4 22.4C159.152 77.998 158 89.4 158 109.5c0 17.934 2.852 34.352 16.2 47.7 9.746 9.746 19 18.95 19 18.95-2.5 3.067-5.2 6.067-8.1 9-.7.833-1.483 1.6-2.35 2.3-2.533 2.5-5.167 4.817-7.9 6.95l-17.55-17.55c-15.598-15.6-27.996-17.1-48.6-17.1-19.77 0-33.223 1.822-47.7 16.3-8.647 8.647-18.55 18.6-18.55 18.6-3.767-2.867-7.333-6.034-10.7-9.5-2.8-2.8-5.417-5.667-7.85-8.6 0 0 9.798-9.848 19.15-19.2 13.852-13.853 16.1-29.916 16.1-47.85 0-17.5-2.874-33.823-15.6-46.55-8.835-8.836-21.05-21-21.05-21 2.833-3.6 5.917-7.067 9.25-10.4 2.934-2.867 5.934-5.55 9-8.05L61.1 43.85C74.102 56.852 90.767 60.2 108.7 60.2c18.467 0 35.077-3.577 48.6-17.1 8.32-8.32 19.3-19.25 19.3-19.25 2.9 2.367 5.733 4.933 8.5 7.7 3.467 3.533 6.65 7.183 9.55 10.95z" fill="#3A3F51" fill-rule="evenodd"/>
<g clip-rule="evenodd">
<path d="M78.7 114c-.2-1.167-.332-2.35-.4-3.55-.032-.667-.05-1.333-.05-2 0-.7.018-1.367.05-2 0-.067.018-.133.05-.2.435-7.367 3.334-13.733 8.7-19.1 5.9-5.833 12.984-8.75 21.25-8.75 8.3 0 15.384 2.917 21.25 8.75 5.834 5.934 8.75 13.033 8.75 21.3 0 8.267-2.916 15.35-8.75 21.25-.2.233-.416.45-.65.65-.966.933-1.982 1.783-3.05 2.55-5.065 3.733-10.916 5.6-17.55 5.6s-12.466-1.866-17.5-5.6c-1.332-.934-2.582-2-3.75-3.2-4.532-4.5-7.316-9.734-8.35-15.7z" fill="#0CF" fill-rule="evenodd"/>
<path d="M157.8 59.75l-15 14.65M30.785 32.526L71.65 73.25m84.6 84.25l27.808 28.78m1.855-153.894L157.8 59.75m-125.45 126l27.35-27.4" fill="none" stroke="#0CF" stroke-miterlimit="1" stroke-width="2"/>
<path d="M157.8 59.75l-16.95 17.2M58.97 60.604l17.2 17.15M59.623 158.43l16.75-17.4m61.928-1.396l18.028 17.945" fill="none" stroke="#0CF" stroke-miterlimit="1" stroke-width="7"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

-1
View File
@@ -1 +0,0 @@
Write-Warning "DEPRECATED -- Please use build.sh instead."
+175 -81
View File
@@ -1,17 +1,21 @@
#! /bin/bash #! /bin/bash
msBuild='/c/Program Files (x86)/MSBuild/14.0/Bin' msBuildVersion='15.0'
outputFolder='./_output' outputFolder='./_output'
outputFolderMono='./_output_mono' outputFolderLinux='./_output_linux'
outputFolderOsx='./_output_osx' outputFolderMacOS='./_output_macos'
outputFolderOsxApp='./_output_osx_app' outputFolderMacOSApp='./_output_macos_app'
testPackageFolder='./_tests/' testPackageFolder='./_tests/'
testSearchPattern='*.Test/bin/x86/Release' testSearchPattern='*.Test/bin/x86/Release'
sourceFolder='./src' sourceFolder='./src'
slnFile=$sourceFolder/NzbDrone.sln slnFile=$sourceFolder/Sonarr.sln
updateFolder=$outputFolder/NzbDrone.Update updateFolder=$outputFolder/Sonarr.Update
updateFolderMono=$outputFolderMono/NzbDrone.Update updateFolderMono=$outputFolderLinux/Sonarr.Update
nuget='tools/nuget/nuget.exe'; nuget='tools/nuget/nuget.exe';
vswhere='tools/vswhere/vswhere.exe';
. ./version.sh
CheckExitCode() CheckExitCode()
{ {
"$@" "$@"
@@ -23,12 +27,43 @@ CheckExitCode()
return $status return $status
} }
ProgressStart()
{
echo "##teamcity[blockOpened name='$1']"
echo "##teamcity[progressStart '$1']"
}
ProgressEnd()
{
echo "##teamcity[progressFinish '$1']"
echo "##teamcity[blockClosed name='$1']"
}
UpdateVersionNumber()
{
if [ "$BUILD_NUMBER" != "" ]; then
echo "Updating Version Info"
verMajorMinorRevision=`echo "$buildVersion" | cut -d. -f1,2,3`
verBuild=`echo "${BUILD_NUMBER}" | cut -d. -f4`
BUILD_NUMBER=$verMajorMinorRevision.$verBuild
echo "##teamcity[buildNumber '$BUILD_NUMBER']"
sed -i "s/^[[]assembly: Assembly\(File\|Informational\)\?Version[(]\"[0-9.*]\+\"[)]/[assembly: Assembly\1Version(\"$BUILD_NUMBER\")/g" ./src/NzbDrone*/Properties/AssemblyInfo.cs ./src/Sonarr*/Properties/AssemblyInfo.cs ./src/ServiceHelpers/*/Properties/AssemblyInfo.cs ./src/Common/CommonVersionInfo.cs
fi
}
CreateReleaseInfo()
{
if [ "$BUILD_NUMBER" != "" ]; then
echo "Create Release Info"
echo -e "# Do Not Edit\nReleaseVersion=$BUILD_NUMBER\nBranch=${BRANCH:-dev}" > $outputFolder/release_info
fi
}
CleanFolder() CleanFolder()
{ {
local path=$1 local path=$1
local keepConfigFiles=$2 local keepConfigFiles=$2
find $path -name "*.transform" -exec rm "{}" \; find $path -name "*.transform" -exec rm "{}" \;
if [ $keepConfigFiles != true ] ; then if [ $keepConfigFiles != true ] ; then
@@ -39,9 +74,6 @@ CleanFolder()
find $path -name "FluentValidation.resources.dll" -exec rm "{}" \; find $path -name "FluentValidation.resources.dll" -exec rm "{}" \;
find $path -name "App.config" -exec rm "{}" \; find $path -name "App.config" -exec rm "{}" \;
echo "Removing .less files"
find $path -name "*.less" -exec rm "{}" \;
echo "Removing vshost files" echo "Removing vshost files"
find $path -name "*.vshost.exe" -exec rm "{}" \; find $path -name "*.vshost.exe" -exec rm "{}" \;
@@ -52,19 +84,16 @@ CleanFolder()
find $path -depth -empty -type d -exec rm -r "{}" \; find $path -depth -empty -type d -exec rm -r "{}" \;
} }
AddJsonNet()
{
rm $outputFolder/Newtonsoft.Json.*
cp $sourceFolder/packages/Newtonsoft.Json.*/lib/net35/*.dll $outputFolder
cp $sourceFolder/packages/Newtonsoft.Json.*/lib/net35/*.dll $outputFolder/NzbDrone.Update
}
BuildWithMSBuild() BuildWithMSBuild()
{ {
installationPath=`$vswhere -latest -products \* -requires Microsoft.Component.MSBuild -property installationPath`
installationPath=${installationPath/C:\\/\/c\/}
installationPath=${installationPath//\\/\/}
msBuild="$installationPath/MSBuild/$msBuildVersion/Bin"
echo $msBuild
export PATH=$msBuild:$PATH export PATH=$msBuild:$PATH
CheckExitCode MSBuild.exe $slnFile //t:Clean //m CheckExitCode MSBuild.exe $slnFile //p:Configuration=Release //p:Platform=x86 //t:Clean //m
$nuget restore $slnFile $nuget restore $slnFile
CheckExitCode MSBuild.exe $slnFile //p:Configuration=Release //p:Platform=x86 //t:Build //m //p:AllowedReferenceRelatedFileExtensions=.pdb CheckExitCode MSBuild.exe $slnFile //p:Configuration=Release //p:Platform=x86 //t:Build //m //p:AllowedReferenceRelatedFileExtensions=.pdb
} }
@@ -77,9 +106,20 @@ BuildWithXbuild()
CheckExitCode xbuild /p:Configuration=Release /p:Platform=x86 /t:Build /p:AllowedReferenceRelatedFileExtensions=.pdb $slnFile CheckExitCode xbuild /p:Configuration=Release /p:Platform=x86 /t:Build /p:AllowedReferenceRelatedFileExtensions=.pdb $slnFile
} }
LintUI()
{
ProgressStart 'ESLint'
CheckExitCode yarn eslint
ProgressEnd 'ESLint'
ProgressStart 'Stylelint'
CheckExitCode yarn stylelint
ProgressEnd 'Stylelint'
}
Build() Build()
{ {
echo "##teamcity[progressStart 'Build']" ProgressStart 'Build'
rm -rf $outputFolder rm -rf $outputFolder
@@ -91,23 +131,23 @@ Build()
CleanFolder $outputFolder false CleanFolder $outputFolder false
AddJsonNet
echo "Removing Mono.Posix.dll" echo "Removing Mono.Posix.dll"
rm $outputFolder/Mono.Posix.dll rm $outputFolder/Mono.Posix.dll
echo "##teamcity[progressFinish 'Build']" ProgressEnd 'Build'
} }
RunGulp() RunGulp()
{ {
echo "##teamcity[progressStart 'npm install']" ProgressStart 'yarn install'
npm-cache install npm || CheckExitCode npm install yarn install
echo "##teamcity[progressFinish 'npm install']" ProgressEnd 'yarn install'
echo "##teamcity[progressStart 'Running gulp']" LintUI
CheckExitCode npm run build
echo "##teamcity[progressFinish 'Running gulp']" ProgressStart 'Running gulp'
CheckExitCode yarn run build --production
ProgressEnd 'Running gulp'
} }
CreateMdbs() CreateMdbs()
@@ -129,79 +169,107 @@ CreateMdbs()
PackageMono() PackageMono()
{ {
echo "##teamcity[progressStart 'Creating Mono Package']" ProgressStart 'Creating Mono Package'
rm -rf $outputFolderMono
cp -r $outputFolder $outputFolderMono rm -rf $outputFolderLinux
echo "Copying Binaries"
cp -r $outputFolder $outputFolderLinux
echo "Creating MDBs" echo "Creating MDBs"
CreateMdbs $outputFolderMono CreateMdbs $outputFolderLinux
echo "Removing PDBs" echo "Removing PDBs"
find $outputFolderMono -name "*.pdb" -exec rm "{}" \; find $outputFolderLinux -name "*.pdb" -exec rm "{}" \;
echo "Removing Service helpers" echo "Removing Service helpers"
rm -f $outputFolderMono/ServiceUninstall.* rm -f $outputFolderLinux/ServiceUninstall.*
rm -f $outputFolderMono/ServiceInstall.* rm -f $outputFolderLinux/ServiceInstall.*
echo "Removing native windows binaries Sqlite, MediaInfo" echo "Removing native windows binaries Sqlite, MediaInfo"
rm -f $outputFolderMono/sqlite3.* rm -f $outputFolderLinux/sqlite3.*
rm -f $outputFolderMono/MediaInfo.* rm -f $outputFolderLinux/MediaInfo.*
echo "Adding NzbDrone.Core.dll.config (for dllmap)" echo "Adding Sonarr.Core.dll.config (for dllmap)"
cp $sourceFolder/NzbDrone.Core/NzbDrone.Core.dll.config $outputFolderMono cp $sourceFolder/NzbDrone.Core/Sonarr.Core.dll.config $outputFolderLinux
echo "Adding CurlSharp.dll.config (for dllmap)" echo "Adding CurlSharp.dll.config (for dllmap)"
cp $sourceFolder/NzbDrone.Common/CurlSharp.dll.config $outputFolderMono cp $sourceFolder/NzbDrone.Common/CurlSharp.dll.config $outputFolderLinux
echo "Renaming NzbDrone.Console.exe to NzbDrone.exe" echo "Adding unix System.Runtime.InteropServices.RuntimeInformation.dll (for SharpRaven)"
rm $outputFolderMono/NzbDrone.exe* cp $sourceFolder/packages/System.Runtime.InteropServices.RuntimeInformation.4.3.0/runtimes/unix/lib/netstandard1.1/System.Runtime.InteropServices.RuntimeInformation.dll $outputFolderLinux
for file in $outputFolderMono/NzbDrone.Console.exe*; do cp $sourceFolder/packages/System.Runtime.InteropServices.RuntimeInformation.4.3.0/runtimes/unix/lib/netstandard1.1/System.Runtime.InteropServices.RuntimeInformation.dll $outputFolderLinux/Sonarr.Update
echo "Renaming Sonarr.Console.exe to Sonarr.exe"
rm $outputFolderLinux/Sonarr.exe*
for file in $outputFolderLinux/Sonarr.Console.exe*; do
mv "$file" "${file//.Console/}" mv "$file" "${file//.Console/}"
done done
echo "Removing NzbDrone.Windows" echo "Removing Sonarr.Windows"
rm $outputFolderMono/NzbDrone.Windows.* rm $outputFolderLinux/Sonarr.Windows.*
echo "Adding NzbDrone.Mono to UpdatePackage" echo "Adding Sonarr.Mono to UpdatePackage"
cp $outputFolderMono/NzbDrone.Mono.* $updateFolderMono cp $outputFolderLinux/Sonarr.Mono.* $updateFolderMono
echo "##teamcity[progressFinish 'Creating Mono Package']" ProgressEnd 'Creating Mono Package'
} }
PackageOsx() PackageMacOS()
{ {
echo "##teamcity[progressStart 'Creating OS X Package']" ProgressStart 'Creating MacOS Package'
rm -rf $outputFolderOsx
cp -r $outputFolderMono $outputFolderOsx
echo "Adding sqlite dylibs" rm -rf $outputFolderMacOS
cp $sourceFolder/Libraries/Sqlite/*.dylib $outputFolderOsx mkdir $outputFolderMacOS
echo "Adding MediaInfo dylib"
cp $sourceFolder/Libraries/MediaInfo/*.dylib $outputFolderOsx
echo "Adding Startup script" echo "Adding Startup script"
cp ./osx/Sonarr $outputFolderOsx cp ./macOS/Sonarr $outputFolderMacOS
dos2unix $outputFolderMacOS/Sonarr
echo "##teamcity[progressFinish 'Creating OS X Package']" echo "Copying Binaries"
cp -r $outputFolderLinux/* $outputFolderMacOS
echo "Adding sqlite dylibs"
cp $sourceFolder/Libraries/Sqlite/*.dylib $outputFolderMacOS
echo "Adding MediaInfo dylib"
cp $sourceFolder/Libraries/MediaInfo/*.dylib $outputFolderMacOS
ProgressEnd 'Creating MacOS Package'
} }
PackageOsxApp() PackageMacOSApp()
{ {
echo "##teamcity[progressStart 'Creating OS X App Package']" ProgressStart 'Creating macOS App Package'
rm -rf $outputFolderOsxApp
mkdir $outputFolderOsxApp
cp -r ./osx/Sonarr.app $outputFolderOsxApp rm -rf $outputFolderMacOSApp
cp -r $outputFolderOsx $outputFolderOsxApp/Sonarr.app/Contents/MacOS mkdir $outputFolderMacOSApp
cp -r ./macOS/Sonarr.app $outputFolderMacOSApp
mkdir -p $outputFolderMacOSApp/Sonarr.app/Contents/MacOS
echo "##teamcity[progressFinish 'Creating OS X App Package']" echo "Adding Startup script"
cp ./macOS/Sonarr $outputFolderMacOSApp/Sonarr.app/Contents/MacOS
dos2unix $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/Sonarr
echo "Copying Binaries"
cp -r $outputFolderLinux/* $outputFolderMacOSApp/Sonarr.app/Contents/MacOS
echo "Adding sqlite dylibs"
cp $sourceFolder/Libraries/Sqlite/*.dylib $outputFolderMacOSApp/Sonarr.app/Contents/MacOS
echo "Adding MediaInfo dylib"
cp $sourceFolder/Libraries/MediaInfo/*.dylib $outputFolderMacOSApp/Sonarr.app/Contents/MacOS
echo "Removing Update Folder"
rm -r $outputFolderMacOSApp/Sonarr.app/Contents/MacOS/Sonarr.Update
ProgressEnd 'Creating macOS App Package'
} }
PackageTests() PackageTests()
{ {
echo "Packaging Tests" ProgressStart 'Creating Test Package'
echo "##teamcity[progressStart 'Creating Test Package']"
rm -rf $testPackageFolder rm -rf $testPackageFolder
mkdir $testPackageFolder mkdir $testPackageFolder
@@ -214,7 +282,7 @@ PackageTests()
fi fi
cp $outputFolder/*.dll $testPackageFolder cp $outputFolder/*.dll $testPackageFolder
cp ./*.sh $testPackageFolder cp ./test.sh $testPackageFolder
echo "Creating MDBs for tests" echo "Creating MDBs for tests"
CreateMdbs $testPackageFolder CreateMdbs $testPackageFolder
@@ -223,8 +291,8 @@ PackageTests()
CleanFolder $testPackageFolder true CleanFolder $testPackageFolder true
echo "Adding NzbDrone.Core.dll.config (for dllmap)" echo "Adding Sonarr.Core.dll.config (for dllmap)"
cp $sourceFolder/NzbDrone.Core/NzbDrone.Core.dll.config $testPackageFolder cp $sourceFolder/NzbDrone.Core/Sonarr.Core.dll.config $testPackageFolder
echo "Adding CurlSharp.dll.config (for dllmap)" echo "Adding CurlSharp.dll.config (for dllmap)"
cp $sourceFolder/NzbDrone.Common/CurlSharp.dll.config $testPackageFolder cp $sourceFolder/NzbDrone.Common/CurlSharp.dll.config $testPackageFolder
@@ -232,16 +300,39 @@ PackageTests()
echo "Copying CurlSharp libraries" echo "Copying CurlSharp libraries"
cp $sourceFolder/ExternalModules/CurlSharp/libs/i386/* $testPackageFolder cp $sourceFolder/ExternalModules/CurlSharp/libs/i386/* $testPackageFolder
echo "##teamcity[progressFinish 'Creating Test Package']" ProgressEnd 'Creating Test Package'
} }
CleanupWindowsPackage() CleanupWindowsPackage()
{ {
echo "Removing NzbDrone.Mono" ProgressStart 'Cleaning Windows Package'
rm -f $outputFolder/NzbDrone.Mono.*
echo "Adding NzbDrone.Windows to UpdatePackage" echo "Removing Sonarr.Mono"
cp $outputFolder/NzbDrone.Windows.* $updateFolder rm -f $outputFolder/Sonarr.Mono.*
echo "Adding Sonarr.Windows to UpdatePackage"
cp $outputFolder/Sonarr.Windows.* $updateFolder
ProgressEnd 'Cleaning Windows Package'
}
PublishArtifacts()
{
ProgressStart 'Publishing Artifacts'
# Tests
echo "##teamcity[publishArtifacts '_tests/** => tests.zip']"
# Releases
echo "##teamcity[publishArtifacts '$outputFolder/** => Sonarr.$BRANCH.$BUILD_NUMBER.windows.zip!Sonarr']"
echo "##teamcity[publishArtifacts '$outputFolderLinux/** => Sonarr.$BRANCH.$BUILD_NUMBER.linux.tar.gz!Sonarr']"
echo "##teamcity[publishArtifacts '$outputFolderMacOS/** => Sonarr.$BRANCH.$BUILD_NUMBER.macos.tar.gz!Sonarr']"
echo "##teamcity[publishArtifacts '$outputFolderMacOSApp/** => Sonarr.$BRANCH.$BUILD_NUMBER.macos.zip']"
# Debian Package
echo "##teamcity[publishArtifacts 'distribution/** => distribution.zip']"
ProgressEnd 'Publishing Artifacts'
} }
# Use mono or .net depending on OS # Use mono or .net depending on OS
@@ -256,10 +347,13 @@ case "$(uname -s)" in
;; ;;
esac esac
UpdateVersionNumber
Build Build
CreateReleaseInfo
RunGulp RunGulp
PackageMono PackageMono
PackageOsx PackageMacOS
PackageOsxApp PackageMacOSApp
PackageTests PackageTests
CleanupWindowsPackage CleanupWindowsPackage
PublishArtifacts
-5
View File
@@ -1,5 +0,0 @@
nzbdrone {version} {branch}; urgency=low
* Automatic Release.
-- NzbDrone <contact@nzbdrone.com> Mon, 26 Aug 2013 00:00:00 -0700
-12
View File
@@ -1,12 +0,0 @@
Section: web
Priority: optional
Maintainer: Sonarr <contact@nzbdrone.com>
Source: nzbdrone
Homepage: https://sonarr.tv
Vcs-Git: git@github.com:Sonarr/Sonarr.git
Vcs-Browser: https://github.com/Sonarr/Sonarr
Package: nzbdrone
Architecture: all
Depends: libmono-cil-dev (>= 3.2), sqlite3 (>= 3.7), mediainfo (>= 0.7.52)
Description: Sonarr is an internet PVR
-1
View File
@@ -1 +0,0 @@
nzbdrone_bin/* opt/NzbDrone
-13
View File
@@ -1,13 +0,0 @@
#!/usr/bin/make -f
# -*- makefile -*-
# Sample debian/rules that uses debhelper.
# This file was originally written by Joey Hess and Craig Small.
# As a special exception, when this file is copied by dh-make into a
# dh-make output file, you may use that output file without restriction.
# This special exception was added by Craig Small in version 0.37 of dh-make.
# Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1
%:
dh $@
+47
View File
@@ -0,0 +1,47 @@
fromdos ./debian/*
cp -r ./debian ./debian_backup
BuildVersion=${dependent_build_number:-3.10.0.999}
BuildBranch=${dependent_build_branch:-master}
BootstrapVersion=`echo "$BuildVersion" | cut -d. -f1,2,3`
BootstrapUpdater="BuiltIn"
PackageUpdater="apt"
echo Version: "$BuildVersion" Branch: "$BuildBranch"
rm -r ./sonarr_bin/Sonarr.Update
chmod -R ugo-x,ugo+rwX,go-w ./sonarr_bin/*
echo Updating changelog for $BuildVersion
sed -i "s:{version}:$BuildVersion:g; s:{branch}:$BuildBranch:g;" debian/changelog
sed -i "s:{version}:$BuildVersion:g; s:{updater}:$PackageUpdater:g" debian/preinst debian/postinst debian/postrm
sed -i '/#BEGIN BUILTIN UPDATER/,/#END BUILTIN UPDATER/d' debian/preinst debian/postinst debian/postrm
echo "# Do Not Edit\nPackageVersion=$BuildVersion\nReleaseVersion=$BuildVersion\nUpdateMethod=$PackageUpdater\nBranch=$BuildBranch" > package_info
echo Running debuild for $BuildVersion
debuild -b
# Restore debian directory to the original files
rm -rf ./debian
mv ./debian_backup ./debian
echo Updating changelog for $BootstrapVersion
sed -i "s:{version}:$BootstrapVersion:g; s:{branch}:$BuildBranch:g;" debian/changelog
sed -i "s:{version}:$BuildVersion:g; s:{updater}:$BootstrapUpdater:g" debian/preinst debian/postinst debian/postrm
sed -i '/#BEGIN BUILTIN UPDATER/d; /#END BUILTIN UPDATER/d' debian/preinst debian/postinst debian/postrm
echo "# Do Not Edit\nPackageVersion=$BootstrapVersion\nReleaseVersion=$BuildVersion\nUpdateMethod=$BootstrapUpdater\nBranch=$BuildBranch" > package_info
echo Running debuild for $BootstrapVersion
debuild -b
echo Moving stuff around
mv ../sonarr_*.deb ./
mv ../sonarr_*.changes ./
rm ../sonarr_*.build
echo Signing Package
dpkg-sig -k 884589CE --sign builder "sonarr_${BuildVersion}_all.deb"
dpkg-sig -k 884589CE --sign builder "sonarr_${BootstrapVersion}_all.deb"
echo running alien
alien -r -v ./*.deb
+6
View File
@@ -0,0 +1,6 @@
[*]
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 2
+5
View File
@@ -0,0 +1,5 @@
sonarr ({version}) {branch}; urgency=low
* Automatic Release.
-- Sonarr <hello@sonarr.tv> Sun, 28 Jan 2018 00:00:00 -0700
+14
View File
@@ -0,0 +1,14 @@
#!/bin/sh -e
. /usr/share/debconf/confmodule
db_beginblock
db_input high sonarr/owning_user || true
db_input high sonarr/owning_group || true
db_endblock
db_go
db_input low sonarr/config_directory || true
db_go
exit 0
+21
View File
@@ -0,0 +1,21 @@
Section: web
Priority: optional
Maintainer: Sonarr <hello@sonarr.tv>
Source: sonarr
Homepage: https://sonarr.tv
Vcs-Git: git@github.com:Sonarr/Sonarr.git
Vcs-Browser: https://github.com/Sonarr/Sonarr
Build-Depends: debhelper (>= 9),
dh-systemd (>= 1.5),
mono-devel (>= 4.6),
libmono-cil-dev (>= 4.6),
cli-common-dev (>= 0.5.7)
Package: sonarr
Architecture: all
Provides: nzbdrone
Conflicts: nzbdrone
Replaces: nzbdrone
Depends: adduser, libsqlite3-0 (>= 3.7), libmediainfo0v5 (>= 0.7.52), mono-runtime (>= 5.4), ${cli:Depends}, ${misc:Depends}
Recommends: sqlite3 (>= 3.7), mediainfo (>= 0.7.52), ${cli:Recommends}
Description: Internet PVR
+24 -24
View File
@@ -1,24 +1,24 @@
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: nzbdrone Upstream-Name: sonarr
Source: https://github.com/Sonarr/Sonarr Source: https://github.com/Sonarr/Sonarr
Files: * Files: *
Copyright: 2010-2016 Sonarr <hello@sonarr.tv> Copyright: 2010-2016 Sonarr <hello@sonarr.tv>
License: GPL-3.0+ License: GPL-3.0+
This program is free software: you can redistribute it and/or modify This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or the Free Software Foundation, either version 3 of the License, or
(at your option) any later version. (at your option) any later version.
. .
This package is distributed in the hope that it will be useful, This package is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details. GNU General Public License for more details.
. .
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
. .
On Debian systems, the complete text of the GNU General On Debian systems, the complete text of the GNU General
Public License version 3 can be found in "/usr/share/common-licenses/GPL-3". Public License version 3 can be found in "/usr/share/common-licenses/GPL-3".
+1
View File
@@ -0,0 +1 @@
sonarr_3.0.0.0_all.deb web optional
+2
View File
@@ -0,0 +1,2 @@
sonarr_bin/* /usr/lib/sonarr/bin
package_info /usr/lib/sonarr
+107
View File
@@ -0,0 +1,107 @@
#!/bin/sh
set -e
BUILD_VERSION={version}
UPDATER={updater}
. /usr/share/debconf/confmodule
db_get sonarr/owning_user
USER="$RET"
db_get sonarr/owning_group
GROUP="$RET"
db_get sonarr/config_directory
CONFDIR="$RET"
# Add User and Group
if ! getent group "$GROUP" >/dev/null; then
groupadd "$GROUP"
fi
if ! getent passwd "$USER" >/dev/null; then
adduser --system --no-create-home --ingroup "$GROUP" "$USER"
fi
if [ $1 = "configure" ]; then
# Migrate old Sonarr v3 alpha data dir from /var/opt/sonarr or user home
if [ -d "/var/opt/sonarr" ] && [ "$CONFDIR" != "/var/opt/sonarr" ] && [ ! -d "$CONFDIR" ]; then
varoptRoot="/var/opt/sonarr"
varoptAppData="$varoptRoot/.config/Sonarr"
sonarrUserHome=`getent passwd $USER | cut -d ':' -f 6`
sonarrAppData="$sonarrUserHome/.config/Sonarr"
if [ -f "$varoptRoot/sonarr.db" ]; then
# Handle /var/opt/sonarr/sonarr.db
mv "$varoptRoot" "$CONFDIR"
elif [ -f "$varoptAppData/sonarr.db" ]; then
# Handle /var/opt/sonarr/.config/Sonarr/sonarr.db
mv "$varoptAppData" "$CONFDIR"
rm -rf "$varoptRoot"
elif [ -f "$sonarrAppData/sonarr.db" ]; then
# Handle ~/.config/Sonarr/sonarr.db
mv "$sonarrAppData" "$CONFDIR"
rm -rf "$sonarrAppData"
else
mv "$varoptRoot" "$CONFDIR"
fi
chown -R $USER:$GROUP "$CONFDIR"
chmod -R 775 "$CONFDIR"
fi
# Migrate old NzbDrone data dir
if [ -d "/usr/lib/sonarr/nzbdrone-appdata" ] && [ ! -d "$CONFDIR" ]; then
NZBDRONE_DATA=`readlink /usr/lib/sonarr/nzbdrone-appdata`
if [ -f "$NZBDRONE_DATA/config.xml" ] && [ -f "$NZBDRONE_DATA/nzbdrone.db" ]; then
echo "Found NzbDrone database in $NZBDRONE_DATA, copying to $CONFDIR."
mkdir -p "$CONFDIR"
cp $NZBDRONE_DATA/config.xml $NZBDRONE_DATA/nzbdrone.db* "$CONFDIR/"
chown -R $USER:$GROUP "$CONFDIR"
chmod -R 775 "$CONFDIR"
else
echo "Missing NzbDrone database in $NZBDRONE_DATA, skipping migration."
fi
rm /usr/lib/sonarr/nzbdrone-appdata
fi
fi
# Create data directory
if [ ! -d "$CONFDIR" ]; then
mkdir -p "$CONFDIR"
chown -R $USER:$GROUP "$CONFDIR"
fi
#BEGIN BUILTIN UPDATER
# Apply patch if present
if [ "$UPDATER" = "BuiltIn" ] && [ -f /usr/lib/sonarr/bin_patch/release_info ]; then
# It shouldn't be possible to get a wrong bin_patch, but let's check anyway and throw it away if it's wrong
currentVersion=`cat /usr/lib/sonarr/bin_patch/release_info | grep 'ReleaseVersion=' | cut -d= -f 2`
currentRelease=`echo "$currentVersion" | cut -d. -f1,2,3`
currentBuild=`echo "$currentVersion" | cut -d. -f4`
targetVersion=$BUILD_VERSION
targetRelease=`echo "$targetVersion" | cut -d. -f1,2,3`
targetBuild=`echo "$targetVersion" | cut -d. -f4`
if [ "$currentRelease" = "$targetRelease" ] && [ "$currentBuild" -gt "$targetBuild" ]; then
echo "Applying $currentVersion from BuiltIn updater instead of downgrading to $targetVersion"
rm -rf /usr/lib/sonarr/bin
mv /usr/lib/sonarr/bin_patch /usr/lib/sonarr/bin
else
rm -rf /usr/lib/sonarr/bin_patch
fi
fi
#END BUILTIN UPDATER
# Set permissions on /usr/lib/sonarr
chown -R $USER:$GROUP /usr/lib/sonarr
# Update sonarr.service file
sed -i "s:User=sonarr:User=$USER:g; s:Group=sonarr:Group=$GROUP:g; s:-data=/var/lib/sonarr:-data=$CONFDIR:g" /lib/systemd/system/sonarr.service
#BEGIN BUILTIN UPDATER
if [ $1 = "upgrade" ] && [ "$UPDATER" = "BuiltIn" ]; then
# If we upgraded, signal Sonarr to do an update check on startup instead of scheduled.
touch $CONFDIR/update_required
chown $USER:$GROUP $CONFDIR/update_required
fi
#END BUILTIN UPDATER
#DEBHELPER#
exit 0
+36
View File
@@ -0,0 +1,36 @@
#!/bin/sh
set -e
BUILD_VERSION={version}
UPDATER={updater}
if [ $1 = "abort-install" ]; then
# preinst was aborted, possibly due to NzbDrone still running.
# Nothing to do here
:
fi
# The bin directory is expected to be empty, unless the BuiltIn updater added files.
if [ $1 = "remove" ] && [ -d /usr/lib/sonarr/bin ]; then
rm -rf /usr/lib/sonarr/bin
fi
#BEGIN BUILTIN UPDATER
# Remove any existing patch if still present
if [ $1 = "remove" ] && [ -d /usr/lib/sonarr/bin_patch ]; then
rm -rf /usr/lib/sonarr/bin_patch
fi
#END BUILTIN UPDATER
# Purge the entire sonarr configuration directory.
# TODO: Maybe move a minimal backup to tmp?
if [ $1 = "purge" ] && [ -e /usr/share/debconf/confmodule ]; then
. /usr/share/debconf/confmodule
db_get sonarr/config_directory
CONFDIR="$RET"
if [ -d "$CONFDIR" ]; then
rm -rf "$CONFDIR"
fi
fi
#DEBHELPER#
+76
View File
@@ -0,0 +1,76 @@
#!/bin/sh
set -e
BUILD_VERSION={version}
UPDATER={updater}
# Deal with existing nzbdrone installs
#
# Existing nzbdrone packages do not have startup scripts and the process might still be running.
# If the user manually installed nzbdrone then the process might still be running too.
if [ $1 = "install" ]; then
psNzbDrone=`ps ax -o'user,pid,ppid,unit,args' | grep mono.*NzbDrone\\\\.exe || true`
if [ ! -z "$psNzbDrone" ]; then
# Get the user and optional systemd unit
psNzbDroneUser=`echo "$psNzbDrone" | tr -s ' ' | cut -d ' ' -f 1`
psNzbDroneUnit=`echo "$psNzbDrone" | tr -s ' ' | cut -d ' ' -f 4`
# Get the appdata from the cmdline or get it from the user dir
droneAppData=`echo "$psNzbDrone" | tr ' ' '\n' | grep -- "-data=" | cut -d= -f 2`
if [ "$droneAppData" = "" ]; then
droneUserHome=`getent passwd $psNzbDroneUser | cut -d ':' -f 6`
droneAppData="$droneUserHome/.config/NzbDrone"
fi
if [ "$psNzbDroneUnit" != "-" ] && [ -d /run/systemd/system ]; then
# The user used a systemd auto-startup for NzbDrone, we can deal with that.
echo "NzbDrone systemd startup detected at $psNzbDroneUnit, stopping and disabling..."
deb-systemd-invoke stop $psNzbDroneUnit >/dev/null
deb-systemd-invoke mask $psNzbDroneUnit >/dev/null
else
# We don't support auto migration for other startup methods, so bail.
# This leaves the sonarr package in an incomplete state.
echo "ps: $psNzbDrone"
echo "Error: An existing Sonarr v2 (NzbDrone) process is running. Remove the NzbDrone auto-startup prior to installing sonarr."
exit 1
fi
# We don't have the debconf configuration yet so we can't migrate the data.
# Instead we symlink so postinst knows where it's at.
if [ -f "/usr/lib/sonarr/nzbdrone-appdata" ]; then
rm "/usr/lib/sonarr/nzbdrone-appdata"
else
mkdir -p "/usr/lib/sonarr"
fi
ln -s $droneAppData /usr/lib/sonarr/nzbdrone-appdata
fi
fi
#BEGIN BUILTIN UPDATER
# Check for supported upgrade paths
if [ $1 = "upgrade" ] && [ "$UPDATER" = "BuiltIn" ] && [ -f /usr/lib/sonarr/bin/release_info ]; then
# If we allow the Built-In updater to upgrade from 3.0.1.123 to 3.0.2.500 and now apt is catching up to 3.0.2.425
# then we need to deal with that 500->425 'downgrade'.
# We do that by preserving the binaries and using those instead for postinst.
currentVersion=`cat /usr/lib/sonarr/bin/release_info | grep 'ReleaseVersion=' | cut -d= -f 2`
currentRelease=`echo "$currentVersion" | cut -d. -f1,2,3`
currentBuild=`echo "$currentVersion" | cut -d. -f4`
targetVersion=$BUILD_VERSION
targetRelease=`echo "$targetVersion" | cut -d. -f1,2,3`
targetBuild=`echo "$targetVersion" | cut -d. -f4`
if [ -d /usr/lib/sonarr/bin_patch ]; then
rm -rf /usr/lib/sonarr/bin_patch
fi
# Check if the existing version is already an upgrade for the included build
if [ "$currentRelease" = "$targetRelease" ] && [ "$currentBuild" -gt "$targetBuild" ]; then
echo "Preserving $currentVersion from BuiltIn updater instead of downgrading to $targetVersion"
cp -r /usr/lib/sonarr/bin /usr/lib/sonarr/bin_patch
fi
fi
#END BUILTIN UPDATER
#DEBHELPER#
exit 0
+29
View File
@@ -0,0 +1,29 @@
#!/usr/bin/make -f
# -*- makefile -*-
# Sample debian/rules that uses debhelper.
# This file was originally written by Joey Hess and Craig Small.
# As a special exception, when this file is copied by dh-make into a
# dh-make output file, you may use that output file without restriction.
# This special exception was added by Craig Small in version 0.37 of dh-make.
# Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1
# Note: System.Native is a dependency of System.Runtime.InteropServices.RuntimeInformation used by SharpRaven,
# but SharpRaven doesn't use any functions that need System.Native
EXCLUDE_MODULEREFS = crypt32 httpapi System.Native
%:
dh $@ --with=systemd --with=cli
# No init script, only systemd
override_dh_installinit:
true
# Sonarr like debug symbols for logging
override_dh_clistrip:
override_dh_makeclilibs:
override_dh_clideps:
dh_clideps -d -r $(patsubst %,--exclude-moduleref=%,$(EXCLUDE_MODULEREFS))
@@ -0,0 +1,2 @@
recommends libcurl3
ignores msbuild
+17
View File
@@ -0,0 +1,17 @@
[Unit]
Description=Sonarr Daemon
After=network.target
[Service]
User=sonarr
Group=sonarr
UMask=002
Type=simple
ExecStart=/usr/bin/mono --debug /usr/lib/sonarr/bin/Sonarr.exe -nobrowser -data=/var/lib/sonarr
TimeoutStopSec=20
KillMode=process
Restart=on-failure
[Install]
WantedBy=multi-user.target
+21
View File
@@ -0,0 +1,21 @@
Template: sonarr/owning_user
Type: string
Default: sonarr
Description: Sonarr user:
Specify the user that is used to run Sonarr. The user will be created if it does not already exist.
The default 'sonarr' should work fine for most users. You can specify the user group next.
Template: sonarr/owning_group
Type: string
Default: sonarr
Description: Sonarr group:
Specify the group that is used to run Sonarr. The group will be created if it does not already exist.
If the user doesn't already exist then this group will be used as the user's primary group.
Any media files created by Sonarr will be writeable by this group.
It's advisable to keep the group the same between download client, Sonarr and media centers.
Template: sonarr/config_directory
Type: string
Default: /var/lib/sonarr
Description: Config directory:
Specify the directory where Sonarr stores the internal database and metadata. Media content will be stored elsewhere.
+25
View File
@@ -0,0 +1,25 @@
{
"remove-empty-rulesets": true,
"always-semicolon": true,
"color-case": "lower",
"block-indent": " ",
"color-shorthand": false,
"element-case": "lower",
"eof-newline": true,
"leading-zero": true,
"quotes": "double",
"sort-order-fallback": "abc",
"space-before-colon": "",
"space-after-colon": " ",
"space-before-combinator": " ",
"space-after-combinator": " ",
"space-between-declarations": "\n",
"space-before-opening-brace": " ",
"space-after-opening-brace": "\n",
"space-after-selector-delimiter": " ",
"space-before-selector-delimiter": "",
"space-before-closing-brace": "\n",
"strip-spaces": true,
"tab-size": true,
"unitless-zero": false
}
+335
View File
@@ -0,0 +1,335 @@
{
"indent": {
"value": " ",
"FunctionExpression": 1,
"ArrayExpression": 1,
"ObjectExpression": 1
},
"lineBreak": {
"value": "\n",
"before": {
"ArrayPatternClosing": 0,
"ArrayPatternComma": 0,
"ArrayPatternOpening": 0,
"ArrowFunctionExpressionArrow": 0,
"ArrowFunctionExpressionClosingBrace": ">=1",
"ArrowFunctionExpressionOpeningBrace": 0,
"AssignmentExpression": ">=1",
"AssignmentOperator": 0,
"BlockStatement": 0,
"BreakKeyword": ">=1",
"CallExpression": -1,
"CallExpressionClosingParentheses": -1,
"CallExpressionOpeningParentheses": 0,
"CatchClosingBrace": ">=1",
"CatchKeyword": 0,
"CatchOpeningBrace": 0,
"ClassDeclaration": ">=1",
"ClassDeclarationClosingBrace": ">=1",
"ClassDeclarationOpeningBrace": 0,
"ConditionalExpression": ">=1",
"DeleteOperator": ">=1",
"DoWhileStatement": ">=1",
"DoWhileStatementClosingBrace": ">=1",
"DoWhileStatementOpeningBrace": 0,
"ElseIfStatement": 0,
"ElseIfStatementClosingBrace": ">=1",
"ElseIfStatementOpeningBrace": 0,
"ElseStatement": 0,
"ElseStatementClosingBrace": ">=1",
"ElseStatementOpeningBrace": 0,
"EmptyStatement": -1,
"EndOfFile": -1,
"FinallyClosingBrace": ">=1",
"FinallyKeyword": -1,
"FinallyOpeningBrace": 0,
"ForInStatement": ">=1",
"ForInStatementClosingBrace": ">=1",
"ForInStatementExpressionClosing": 0,
"ForInStatementExpressionOpening": 0,
"ForInStatementOpeningBrace": 0,
"ForStatement": ">=1",
"ForStatementClosingBrace": ">=1",
"ForStatementExpressionClosing": "<2",
"ForStatementExpressionOpening": 0,
"ForStatementOpeningBrace": 0,
"FunctionDeclaration": ">=1",
"FunctionDeclarationClosingBrace": ">=1",
"FunctionDeclarationOpeningBrace": 0,
"FunctionExpression": 0,
"FunctionExpressionClosingBrace": 1,
"FunctionExpressionOpeningBrace":0,
"IIFEClosingParentheses": 0,
"IfStatement": ">=1",
"IfStatementClosingBrace": ">=1",
"IfStatementOpeningBrace": 0,
"LogicalExpression": -1,
"MemberExpressionClosing": 0,
"MemberExpressionOpening": 0,
"MemberExpressionPeriod": -1,
"MethodDefinition": ">=1",
"ObjectExpressionClosingBrace": "<=1",
"ObjectPatternClosingBrace": 0,
"ObjectPatternComma": 0,
"ObjectPatternOpeningBrace": 0,
"ParameterDefault": 0,
"Property": "<=2",
"PropertyValue": 0,
"ReturnStatement": -1,
"SwitchClosingBrace": ">=1",
"SwitchOpeningBrace": 0,
"ThisExpression": -1,
"ThrowStatement": ">=1",
"TryClosingBrace": ">=1",
"TryKeyword": -1,
"TryOpeningBrace": 0,
"VariableDeclaration": ">=1",
"VariableDeclarationSemiColon": 0,
"VariableDeclarationWithoutInit": ">=1",
"VariableName": ">=1",
"VariableValue": 0,
"WhileStatement": ">=1",
"WhileStatementClosingBrace": ">=1",
"WhileStatementOpeningBrace": 0
},
"after": {
"ArrayPatternClosing": 0,
"ArrayPatternComma": 0,
"ArrayPatternOpening": 0,
"ArrowFunctionExpressionArrow": 0,
"ArrowFunctionExpressionClosingBrace": -1,
"ArrowFunctionExpressionOpeningBrace": ">=1",
"AssignmentExpression": ">=1",
"AssignmentOperator": 0,
"BlockStatement": 0,
"BreakKeyword": -1,
"CallExpression": -1,
"CallExpressionClosingParentheses": -1,
"CallExpressionOpeningParentheses": -1,
"CatchClosingBrace": ">=0",
"CatchKeyword": 0,
"CatchOpeningBrace": ">=1",
"ClassDeclaration": ">=1",
"ClassDeclarationClosingBrace": ">=1",
"ClassDeclarationOpeningBrace": ">=1",
"ConditionalExpression": ">=1",
"DeleteOperator": ">=1",
"DoWhileStatement": ">=1",
"DoWhileStatementClosingBrace": 0,
"DoWhileStatementOpeningBrace": ">=1",
"ElseIfStatement": ">=1",
"ElseIfStatementClosingBrace": ">=1",
"ElseIfStatementOpeningBrace": ">=1",
"ElseStatement": ">=1",
"ElseStatementClosingBrace": ">=1",
"ElseStatementOpeningBrace": ">=1",
"EmptyStatement": -1,
"FinallyClosingBrace": ">=1",
"FinallyKeyword": -1,
"FinallyOpeningBrace": ">=1",
"ForInStatement": ">=1",
"ForInStatementClosingBrace": ">=1",
"ForInStatementExpressionClosing": -1,
"ForInStatementExpressionOpening": "<2",
"ForInStatementOpeningBrace": ">=1",
"ForStatement": ">=1",
"ForStatementClosingBrace": ">=1",
"ForStatementExpressionClosing": -1,
"ForStatementExpressionOpening": "<2",
"ForStatementOpeningBrace": ">=1",
"FunctionDeclaration": ">=1",
"FunctionDeclarationClosingBrace": ">=1",
"FunctionDeclarationOpeningBrace": ">=1",
"FunctionExpression": 0,
"FunctionExpressionClosingBrace": -1,
"FunctionExpressionOpeningBrace": 1,
"IIFEOpeningParentheses": 0,
"IfStatement": ">=1",
"IfStatementClosingBrace": ">=1",
"IfStatementOpeningBrace": ">=1",
"LogicalExpression": -1,
"MemberExpressionClosing": 0,
"MemberExpressionOpening": 0,
"MemberExpressionPeriod": 0,
"MethodDefinition": ">=1",
"ObjectExpressionOpeningBrace": "<=1",
"ObjectPatternClosingBrace": 0,
"ObjectPatternComma": 0,
"ObjectPatternOpeningBrace": 0,
"ParameterDefault": 0,
"Property": -1,
"PropertyName": 0,
"ReturnStatement": -1,
"SwitchCaseColon": ">=1",
"SwitchClosingBrace": ">=1",
"SwitchOpeningBrace": ">=1",
"ThisExpression": 0,
"ThrowStatement": ">=1",
"TryClosingBrace": 0,
"TryKeyword": -1,
"TryOpeningBrace": ">=1",
"VariableDeclaration": ">=1",
"VariableDeclarationSemiColon": ">=1",
"VariableValue": -1,
"WhileStatement": ">=1",
"WhileStatementClosingBrace": ">=1",
"WhileStatementOpeningBrace": ">=1"
}
},
"whiteSpace": {
"value": " ",
"removeTrailing": 1,
"before": {
"ArgumentComma": 0,
"ArgumentList": 0,
"ArgumentListArrayExpression": 0,
"ArgumentListFunctionExpression": 1,
"ArgumentListObjectExpression": 0,
"ArrayExpressionClosing": 0,
"ArrayExpressionComma": 0,
"ArrayExpressionOpening": 1,
"AssignmentOperator": 1,
"BinaryExpression": 0,
"BinaryExpressionOperator": 1,
"BlockComment": 1,
"CallExpression": 1,
"CatchClosingBrace": 1,
"CatchKeyword": 1,
"CatchOpeningBrace": 1,
"CatchParameterList": 0,
"CommaOperator": 0,
"ConditionalExpressionAlternate": 1,
"ConditionalExpressionConsequent": 1,
"DoWhileStatementClosingBrace": 1,
"DoWhileStatementConditional": 1,
"DoWhileStatementOpeningBrace": 1,
"ElseIfStatementClosingBrace": 1,
"ElseIfStatementOpeningBrace": 1,
"ElseStatementClosingBrace": 1,
"ElseStatementOpeningBrace": 1,
"EmptyStatement": 0,
"ExpressionClosingParentheses": 0,
"FinallyClosingBrace": 1,
"FinallyKeyword": -1,
"FinallyOpeningBrace": 1,
"ForInStatement": 1,
"ForInStatementClosingBrace": 1,
"ForInStatementExpressionClosing": 0,
"ForInStatementExpressionOpening": 1,
"ForInStatementOpeningBrace": 1,
"ForStatement": 1,
"ForStatementClosingBrace": 1,
"ForStatementExpressionClosing": 0,
"ForStatementExpressionOpening": 1,
"ForStatementOpeningBrace": 1,
"ForStatementSemicolon": 0,
"FunctionDeclarationClosingBrace": 1,
"FunctionDeclarationOpeningBrace": 1,
"FunctionExpressionClosingBrace": 1,
"FunctionExpressionOpeningBrace": 1,
"IfStatementClosingBrace": 1,
"IfStatementConditionalClosing": 0,
"IfStatementConditionalOpening": 1,
"IfStatementOpeningBrace": 1,
"LineComment": 1,
"LogicalExpressionOperator": 1,
"MemberExpressionClosing": 0,
"ObjectExpressionClosingBrace": 1,
"ParameterComma": 0,
"ParameterList": 0,
"Property": 1,
"PropertyName": 1,
"PropertyValue": 1,
"SwitchDiscriminantClosing": 0,
"SwitchDiscriminantOpening": 1,
"ThrowKeyword": 1,
"TryClosingBrace": 1,
"TryKeyword": -1,
"TryOpeningBrace": 1,
"UnaryExpressionOperator": 0,
"VariableName": 1,
"VariableValue": 1,
"WhileStatementClosingBrace": 1,
"WhileStatementConditionalClosing": 0,
"WhileStatementConditionalOpening": 1,
"WhileStatementOpeningBrace": 1
},
"after": {
"ArgumentComma": 1,
"ArgumentList": 0,
"ArgumentListArrayExpression": 1,
"ArgumentListFunctionExpression": 1,
"ArgumentListObjectExpression": 0,
"ArrayExpressionClosing": 0,
"ArrayExpressionComma": 1,
"ArrayExpressionOpening": 0,
"AssignmentOperator": 1,
"BinaryExpression": 0,
"BinaryExpressionOperator": 1,
"BlockComment": 1,
"CallExpression": 0,
"CatchClosingBrace": 1,
"CatchKeyword": 1,
"CatchOpeningBrace": 1,
"CatchParameterList": 0,
"CommaOperator": 1,
"ConditionalExpressionConsequent": 1,
"ConditionalExpressionTest": 1,
"DoWhileStatementBody": 1,
"DoWhileStatementClosingBrace": 1,
"DoWhileStatementOpeningBrace": 1,
"ElseIfStatementClosingBrace": 1,
"ElseIfStatementOpeningBrace": 1,
"ElseStatementClosingBrace": 1,
"ElseStatementOpeningBrace": 1,
"EmptyStatement": 0,
"ExpressionOpeningParentheses": 0,
"FinallyClosingBrace": 1,
"FinallyKeyword": -1,
"FinallyOpeningBrace": 1,
"ForInStatement": 1,
"ForInStatementClosingBrace": 1,
"ForInStatementExpressionClosing": 1,
"ForInStatementExpressionOpening": 0,
"ForInStatementOpeningBrace": 1,
"ForStatement": 1,
"ForStatementClosingBrace": 1,
"ForStatementExpressionClosing": 1,
"ForStatementExpressionOpening": 0,
"ForStatementOpeningBrace": 1,
"ForStatementSemicolon": 1,
"FunctionDeclarationClosingBrace": 0,
"FunctionDeclarationOpeningBrace": 0,
"FunctionExpressionClosingBrace": 0,
"FunctionExpressionOpeningBrace": 0,
"FunctionName": 0,
"FunctionReservedWord": 0,
"IfStatementClosingBrace": 1,
"IfStatementConditionalClosing": 0,
"IfStatementConditionalOpening": 0,
"IfStatementOpeningBrace": 1,
"LogicalExpressionOperator": 1,
"MemberExpressionOpening": 0,
"ObjectExpressionClosingBrace": 0,
"ObjectExpressionOpeningBrace": 1,
"ParameterComma": 1,
"ParameterList": 0,
"PropertyName": 0,
"PropertyValue": 0,
"SwitchDiscriminantClosing": 1,
"SwitchDiscriminantOpening": 0,
"ThrowKeyword": 1,
"TryClosingBrace": 1,
"TryKeyword": -1,
"TryOpeningBrace": 1,
"UnaryExpressionOperator": 0,
"VariableName": 1,
"WhileStatementClosingBrace": 1,
"WhileStatementConditionalClosing": 1,
"WhileStatementConditionalOpening": 0,
"WhileStatementOpeningBrace": 1
}
}
}
+1
View File
@@ -0,0 +1 @@
**/JsLibraries/**
+288
View File
@@ -0,0 +1,288 @@
{
"parser": "babel-eslint",
"env": {
"browser": true,
"commonjs": true,
"node": true,
"es6": true
},
"globals": {
"expect": false,
"chai": false,
"sinon": false
},
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module",
"ecmaFeatures": {
"modules": true,
"impliedStrict": true
}
},
"plugins": [
"filenames",
"react"
],
"rules": {
"filenames/match-exported": ["error"],
# ECMAScript 6
"arrow-body-style": [0],
"arrow-parens": ["error", "always"],
"arrow-spacing": ["error", { "before": true, "after": true }],
"constructor-super": "error",
"generator-star-spacing": "off",
"no-class-assign": "error",
"no-confusing-arrow": "error",
"no-const-assign": "error",
"no-dupe-class-members": "error",
"no-duplicate-imports": "error",
"no-new-symbol": "error",
"no-this-before-super": "error",
"no-useless-escape": "error",
"no-useless-computed-key": "error",
"no-useless-constructor": "error",
"no-var": "warn",
"object-shorthand": ["error", "properties"],
"prefer-arrow-callback": "error",
"prefer-const": "warn",
"prefer-reflect": "off",
"prefer-rest-params": "off",
"prefer-spread": "warn",
"prefer-template": "error",
"require-yield": "off",
"template-curly-spacing": ["error", "never"],
"yield-star-spacing": "off",
# Possible Errors
"comma-dangle": "error",
"no-cond-assign": "error",
"no-console": "off",
"no-constant-condition": "warn",
"no-control-regex": "error",
"no-debugger": "off",
"no-dupe-args": "error",
"no-dupe-keys": "error",
"no-duplicate-case": "error",
"no-empty": "warn",
"no-empty-character-class": "error",
"no-ex-assign": "error",
"no-extra-boolean-cast": "error",
"no-extra-parens": ["error", "functions"],
"no-extra-semi": "error",
"no-func-assign": "error",
"no-inner-declarations": "error",
"no-invalid-regexp": "error",
"no-irregular-whitespace": "error",
"no-negated-in-lhs": "error",
"no-obj-calls": "error",
"no-regex-spaces": "error",
"no-sparse-arrays": "error",
"no-unexpected-multiline": "error",
"no-unreachable": "warn",
"no-unsafe-finally": "error",
"use-isnan": "error",
"valid-jsdoc": "off",
"valid-typeof": "error",
# Best Practices
"accessor-pairs": "off",
"array-callback-return": "warn",
"block-scoped-var": "warn",
"consistent-return": "off",
"curly": "error",
"default-case": "error",
"dot-location": ["error", "property"],
"dot-notation": "error",
"eqeqeq": ["error", "smart"],
"guard-for-in": "error",
"no-alert": "warn",
"no-caller": "error",
"no-case-declarations": "error",
"no-div-regex": "error",
"no-else-return": "error",
"no-empty-function": ["error", {"allow": ["arrowFunctions"]}],
"no-empty-pattern": "error",
"no-eval": "error",
"no-extend-native": "error",
"no-extra-bind": "error",
"no-fallthrough": "error",
"no-floating-decimal": "error",
"no-implicit-coercion": ["error", {
"boolean": false,
"number": true,
"string": true,
"allow": [/* "!!", "~", "*", "+" */]
}],
"no-implicit-globals": "error",
"no-implied-eval": "error",
"no-invalid-this": "off",
"no-iterator": "error",
"no-labels": "error",
"no-lone-blocks": "error",
"no-loop-func": "error",
"no-magic-numbers": ["off", {"ignoreArrayIndexes": true, "ignore": [0, 1] }],
"no-multi-spaces": "error",
"no-multi-str": "error",
"no-native-reassign": ["error", {"exceptions": ["console"]}],
"no-new": "off",
"no-new-func": "error",
"no-new-wrappers": "error",
"no-octal": "error",
"no-octal-escape": "error",
"no-param-reassign": "off",
"no-process-env": "off",
"no-proto": "error",
"no-redeclare": "error",
"no-return-assign": "warn",
"no-script-url": "error",
"no-self-assign": "error",
"no-self-compare": "error",
"no-sequences": "error",
"no-throw-literal": "error",
"no-unmodified-loop-condition": "error",
"no-unused-expressions": "error",
"no-unused-labels": "error",
"no-useless-call": "error",
"no-useless-concat": "error",
"no-void": "error",
"no-warning-comments": "off",
"no-with": "error",
"radix": ["error", "as-needed"],
"vars-on-top": "off",
"wrap-iife": ["error", "inside"],
"yoda": "error",
# Strict Mode
"strict": ["error", "never"],
# Variables
"init-declarations": ["error", "always"],
"no-catch-shadow": "error",
"no-delete-var": "error",
"no-label-var": "error",
"no-restricted-globals": "off",
"no-shadow": "error",
"no-shadow-restricted-names": "error",
"no-undef": "error",
"no-undef-init": "off",
"no-undefined": "off",
"no-unused-vars": ["error", { "args": "none", "ignoreRestSiblings": true }],
"no-use-before-define": "error",
# Node.js and CommonJS
"callback-return": "warn",
"global-require": "error",
"handle-callback-err": "warn",
"no-mixed-requires": "error",
"no-new-require": "error",
"no-path-concat": "error",
"no-process-exit": "error",
# Stylistic Issues
"array-bracket-spacing": ["error", "never"],
"block-spacing": ["error", "always"],
"brace-style": ["error", "1tbs", { "allowSingleLine": false }],
"camelcase": "off",
"comma-spacing": ["error", {"before": false, "after": true}],
"comma-style": ["error", "last"],
"computed-property-spacing": ["error", "never"],
"consistent-this": ["error", "self"],
"eol-last": "error",
"func-names": "off",
"func-style": ["error", "declaration"],
"indent": ["error", 2, {"SwitchCase": 1}],
"key-spacing": ["error", {"beforeColon": false, "afterColon": true}],
"keyword-spacing": ["error", { "before": true, "after": true}],
"lines-around-comment": ["error", { "beforeBlockComment": true, "afterBlockComment": false }],
"max-depth": ["error", {"maximum": 5}],
"max-nested-callbacks": ["error", 4],
"max-params": ["error", 6],
"max-statements": "off",
"max-statements-per-line": ["error", { "max": 1 }],
"new-cap": ["error", {"capIsNewExceptions": ["$.Deferred", "DragDropContext", "DragLayer", "DragSource", "DropTarget"]}],
"new-parens": "error",
"newline-after-var": "off",
"newline-before-return": "off",
"newline-per-chained-call": "off",
"no-array-constructor": "error",
"no-bitwise": "error",
"no-continue": "error",
"no-inline-comments": "off",
"no-lonely-if": "warn",
"no-mixed-spaces-and-tabs": "error",
"no-multiple-empty-lines": ["error", { "max": 1 }],
"no-negated-condition": "warn",
"no-nested-ternary": "error",
"no-new-object": "error",
"no-plusplus": "off",
"no-restricted-syntax": "off",
"no-spaced-func": "error",
"no-ternary": "off",
"no-trailing-spaces": "error",
"no-underscore-dangle": ["error", { "allowAfterThis": true }],
"no-unneeded-ternary": "error",
"no-whitespace-before-property": "error",
"object-curly-spacing": ["error", "always"],
"one-var": ["error", "never"],
"one-var-declaration-per-line": ["error", "always"],
"operator-assignment": ["off", "never"],
"operator-linebreak": ["error", "after"],
"quote-props": ["error", "as-needed"],
"quotes": ["error", "single"],
"require-jsdoc": "off",
"semi": "error",
"semi-spacing": ["error", { "before": false, "after": true }],
"sort-vars": "off",
"space-before-blocks": ["error", "always"],
"space-before-function-paren": ["error", "never"],
"space-in-parens": "off",
"space-infix-ops": "off",
"space-unary-ops": "off",
"spaced-comment": "error",
"wrap-regex": "error",
# React
"react/jsx-boolean-value": [2, "always"],
"react/jsx-uses-vars": 2,
"react/jsx-closing-bracket-location": 2,
"react/jsx-tag-spacing": ["error"],
"react/jsx-curly-spacing": [2, "never"],
"react/jsx-equals-spacing": [2, "never"],
"react/jsx-indent-props": [2, 2],
"react/jsx-indent": [2, 2],
"react/jsx-key": 2,
"react/jsx-no-bind": [2, { "allowArrowFunctions": true }],
"react/jsx-no-duplicate-props": [2, { "ignoreCase": true }],
"react/jsx-max-props-per-line": [2, { "maximum": 2 }],
"react/jsx-handler-names": [2, { "eventHandlerPrefix": "(on|dispatch)", "eventHandlerPropPrefix": "on" }],
"react/jsx-no-undef": 2,
"react/jsx-pascal-case": 2,
"react/jsx-uses-react": 2,
// Explicitly disabled in case we want to enable them again
"react/no-did-mount-set-state": 0,
"react/no-did-update-set-state": 0,
"react/no-direct-mutation-state": 2,
"react/no-multi-comp": [2, { "ignoreStateless": true }],
"react/no-unknown-property": 2,
"react/prefer-es6-class": 2,
"react/prop-types": 2,
"react/react-in-jsx-scope": 2,
"react/self-closing-comp": 2,
"react/sort-comp": 2,
"react/jsx-wrap-multilines": 2
}
}
+12
View File
@@ -0,0 +1,12 @@
{
"js": {
"indent_size": 2,
"indent_char": " ",
"indent_level": 2,
"indent_with_tabs": false,
"preserve_newlines": true,
"brace_style": "collapse",
"max_preserve_newlines": 2,
"jslint_happy": true
}
}
+396
View File
@@ -0,0 +1,396 @@
{
"plugins": [
"stylelint-order"
],
"ignoreFiles": [
"frontend/src/Styles/scaffolding.css",
"**/*.js"
],
"rules": {
"at-rule-empty-line-before": [
"always",
{
"except": [
"inside-block"
]
}
],
"at-rule-name-case": "lower",
"at-rule-name-newline-after": "always-multi-line",
"at-rule-name-space-after": "always",
"at-rule-no-unknown": [
true,
{
"ignoreAtRules": [
"/^add\\-mixin$/",
"/^define\\-mixin$/"
]
}
],
"at-rule-no-vendor-prefix": true,
"at-rule-semicolon-newline-after": "always",
"at-rule-semicolon-space-before": "never",
"block-closing-brace-empty-line-before": "never",
"block-closing-brace-newline-after": "always",
"block-closing-brace-newline-before": "always",
"block-closing-brace-space-after": "always-single-line",
"block-closing-brace-space-before": "always-single-line",
"block-no-empty": true,
"block-opening-brace-newline-after": "always",
"block-opening-brace-newline-before": "never-single-line",
"block-opening-brace-space-after": "always-single-line",
"block-opening-brace-space-before": "always",
"color-hex-case": "lower",
"color-hex-length": "short",
"color-named": "never",
"color-no-invalid-hex": true,
"comment-whitespace-inside": "always",
"declaration-bang-space-after": "never",
"declaration-bang-space-before": "always",
"declaration-block-no-duplicate-properties": [
true,
{
"ignoreProperties": [
"composes"
]
}
],
"declaration-block-no-redundant-longhand-properties": true,
"declaration-block-no-shorthand-property-overrides": true,
"declaration-block-semicolon-newline-after": "always",
"declaration-block-semicolon-newline-before": "never-multi-line",
"declaration-block-semicolon-space-before": "never",
"declaration-block-single-line-max-declarations": 1,
"declaration-block-trailing-semicolon": "always",
"declaration-colon-space-after": "always",
"declaration-colon-space-before": "never",
"font-family-name-quotes": "always-unless-keyword",
"function-calc-no-unspaced-operator": true,
"function-comma-newline-after": "never-multi-line",
"function-comma-newline-before": "never-multi-line",
"function-comma-space-after": "always",
"function-comma-space-before": "never",
"function-linear-gradient-no-nonstandard-direction": true,
"function-name-case": "lower",
"function-parentheses-newline-inside": "never-multi-line",
"function-parentheses-space-inside": "never",
"function-url-quotes": "always",
"function-url-scheme-blacklist": [
"data"
],
"function-whitespace-after": "always",
"indentation": 2,
"keyframe-declaration-no-important": true,
"length-zero-no-unit": true,
"max-empty-lines": 1,
"max-line-length": [
100,
{
"ignore": [
"non-comments"
]
}
],
"max-nesting-depth": 2,
"media-feature-colon-space-after": "always",
"media-feature-colon-space-before": "never",
"media-feature-name-case": "lower",
"media-feature-name-no-vendor-prefix": true,
"media-feature-range-operator-space-after": "always",
"media-feature-range-operator-space-before": "always",
"no-empty-source": true,
"no-eol-whitespace": true,
"no-extra-semicolons": true,
"no-invalid-double-slash-comments": true,
"no-missing-end-of-source-newline": true,
"number-leading-zero": "always",
"number-no-trailing-zeros": true,
"order/order": [
"custom-properties",
"dollar-variables",
{
"hasBlock": false,
"name": "add-mixin",
"type": "at-rule"
},
"declarations",
"rules",
"at-rules"
],
"order/properties-order": [
{
"emptyLineBefore": "always",
"properties": [
"composes"
]
},
{
"emptyLineBefore": "always",
"properties": [
"position",
"top",
"right",
"bottom",
"left",
"z-index",
"display",
"visibility",
"align-content",
"align-items",
"align-self",
"justify-content",
"flex",
"flex-direction",
"flex-order",
"flex-pack",
"flex-align",
"flex-grow",
"flex-shrink",
"flex-basis",
"flex-wrap",
"flex-flow",
"float",
"clear",
"overflow",
"overflow-x",
"overflow-y",
"-webkit-overflow-scrolling",
"clip",
"box-sizing",
"margin",
"margin-top",
"margin-right",
"margin-bottom",
"margin-left",
"padding",
"padding-top",
"padding-right",
"padding-bottom",
"padding-left",
"min-width",
"min-height",
"max-width",
"max-height",
"width",
"height",
"outline",
"outline-width",
"outline-style",
"outline-color",
"outline-offset",
"border",
"border-spacing",
"border-collapse",
"border-width",
"border-style",
"border-color",
"border-top",
"border-top-width",
"border-top-style",
"border-top-color",
"border-right",
"border-right-width",
"border-right-style",
"border-right-color",
"border-bottom",
"border-bottom-width",
"border-bottom-style",
"border-bottom-color",
"border-left",
"border-left-width",
"border-left-style",
"border-left-color",
"border-radius",
"border-top-left-radius",
"border-top-right-radius",
"border-bottom-right-radius",
"border-bottom-left-radius",
"border-image",
"border-image-source",
"border-image-slice",
"border-image-width",
"border-image-outset",
"border-image-repeat",
"border-top-image",
"border-right-image",
"border-bottom-image",
"border-left-image",
"border-corner-image",
"border-top-left-image",
"border-top-right-image",
"border-bottom-right-image",
"border-bottom-left-image",
"background",
"background-color",
"background-image",
"background-attachment",
"background-position",
"background-position-x",
"background-position-y",
"background-clip",
"background-origin",
"background-size",
"background-repeat",
"box-decoration-break",
"box-shadow",
"color",
"table-layout",
"caption-side",
"empty-cells",
"list-style",
"list-style-position",
"list-style-type",
"list-style-image",
"quotes",
"content",
"counter-increment",
"counter-reset",
"-ms-writing-mode",
"vertical-align",
"text-align",
"text-align-last",
"text-decoration",
"text-emphasis",
"text-emphasis-position",
"text-emphasis-style",
"text-emphasis-color",
"text-indent",
"text-justify",
"text-outline",
"text-transform",
"text-wrap",
"text-overflow",
"text-overflow-ellipsis",
"text-overflow-mode",
"text-shadow",
"white-space",
"word-spacing",
"word-wrap",
"word-break",
"tab-size",
"hyphens",
"letter-spacing",
"font",
"font-weight",
"font-style",
"font-variant",
"font-size-adjust",
"font-stretch",
"font-size",
"font-family",
"font-smoothing",
"-moz-osx-font-smoothing",
"-webkit-font-smoothing",
"src",
"line-height",
"opacity",
"filter",
"resize",
"cursor",
"appearance",
"nav-index",
"nav-up",
"nav-right",
"nav-down",
"nav-left",
"transition",
"transition-delay",
"transition-timing-function",
"transition-duration",
"transition-property",
"transform",
"transform-origin",
"transform-style",
"backface-visibility",
"animation",
"animation-name",
"animation-duration",
"animation-play-state",
"animation-timing-function",
"animation-delay",
"animation-iteration-count",
"animation-direction",
"animation-fill-mode",
"pointer-events",
"user-select",
"touch-action",
"-webkit-tap-highlight-color",
"unicode-bidi",
"direction",
"columns",
"column-span",
"column-width",
"column-count",
"column-fill",
"column-gap",
"column-rule",
"column-rule-width",
"column-rule-style",
"column-rule-color",
"break-before",
"break-inside",
"break-after",
"page-break-before",
"page-break-inside",
"page-break-after",
"orphans",
"widows",
"zoom",
"max-zoom",
"min-zoom",
"user-zoom",
"orientation"
]
}
],
"property-case": "lower",
"property-no-vendor-prefix": true,
"rule-empty-line-before": [
"always",
{
"except": [
"first-nested"
],
"ignore": [
"after-comment"
]
}
],
"selector-attribute-brackets-space-inside": "never",
"selector-attribute-operator-space-after": "never",
"selector-attribute-operator-space-before": "never",
"selector-attribute-quotes": "never",
"selector-class-pattern": "^[A-Za-z0-9]+$",
"selector-combinator-space-after": "always",
"selector-combinator-space-before": "always",
"selector-descendant-combinator-no-non-space": true,
"selector-list-comma-newline-after": "always",
"selector-list-comma-newline-before": "never-multi-line",
"selector-list-comma-space-before": "never",
"selector-max-attribute": 0,
"selector-max-class": 3,
"selector-max-compound-selectors": 3,
"selector-max-empty-lines": 0,
"selector-max-id": 0,
"selector-max-universal": 0,
"selector-pseudo-class-case": "lower",
"selector-pseudo-class-parentheses-space-inside": "never",
"selector-pseudo-element-case": "lower",
"selector-pseudo-element-colon-notation": "double",
"selector-pseudo-element-no-unknown": true,
"selector-type-case": "lower",
"selector-type-no-unknown": true,
"shorthand-property-no-redundant-values": true,
"string-no-newline": true,
"string-quotes": "single",
"time-min-milliseconds": 100,
"unit-case": "lower",
"unit-no-unknown": true,
"value-list-comma-newline-after": "never-multi-line",
"value-list-comma-newline-before": "never-multi-line",
"value-list-comma-space-after": "always",
"value-list-comma-space-before": "never",
"value-list-max-empty-lines": 0,
"value-no-vendor-prefix": true
}
}
+7
View File
@@ -0,0 +1,7 @@
{
"ecmaVersion": 6,
"libs": [
"browser",
"jquery"
]
}
+15
View File
@@ -0,0 +1,15 @@
const gulp = require('gulp');
const runSequence = require('run-sequence');
require('./clean');
require('./copy');
gulp.task('build', () => {
return runSequence('clean', [
'webpack',
'copyHtml',
'copyFonts',
'copyImages',
'copyJs'
]);
});
+8
View File
@@ -0,0 +1,8 @@
const gulp = require('gulp');
const del = require('del');
const paths = require('./helpers/paths');
gulp.task('clean', () => {
return del([paths.dest.root]);
});
+45
View File
@@ -0,0 +1,45 @@
var path = require('path');
var gulp = require('gulp');
var print = require('gulp-print').default;
var cache = require('gulp-cached');
var livereload = require('gulp-livereload');
var paths = require('./helpers/paths.js');
gulp.task('copyJs', () => {
return gulp.src(
[
path.join(paths.src.root, 'polyfills.js')
])
.pipe(cache('copyJs'))
.pipe(print())
.pipe(gulp.dest(paths.dest.root))
.pipe(livereload());
});
gulp.task('copyHtml', () => {
return gulp.src(paths.src.html)
.pipe(cache('copyHtml'))
.pipe(print())
.pipe(gulp.dest(paths.dest.root))
.pipe(livereload());
});
gulp.task('copyFonts', () => {
return gulp.src(
path.join(paths.src.fonts, '**', '*.*')
)
.pipe(cache('copyFonts'))
.pipe(print())
.pipe(gulp.dest(paths.dest.fonts))
.pipe(livereload());
});
gulp.task('copyImages', () => {
return gulp.src(
path.join(paths.src.images, '**', '*.*')
)
.pipe(cache('copyImages'))
.pipe(print())
.pipe(gulp.dest(paths.dest.images))
.pipe(livereload());
});
+8
View File
@@ -0,0 +1,8 @@
require('./build.js');
require('./clean.js');
require('./copy.js');
require('./imageMin.js');
require('./start.js');
require('./stripBom.js');
require('./watch.js');
require('./webpack.js');
+6
View File
@@ -0,0 +1,6 @@
const gulpUtil = require('gulp-util');
module.exports = function errorHandler(error) {
gulpUtil.log(gulpUtil.colors.red(`Error (${error.plugin}): ${error.message}`));
this.emit('end');
};
@@ -0,0 +1,15 @@
const path = require('path');
const rootPath = path.resolve(__dirname + '/../../src/');
module.exports = function(source) {
if (this.cacheable) {
this.cacheable();
}
const resourcePath = this.resourcePath.replace(rootPath, '');
const wrappedSource =`
<!-- begin ${resourcePath} -->
${source}
<!-- end ${resourcePath} -->`;
return wrappedSource;
};
+23
View File
@@ -0,0 +1,23 @@
const root = './frontend/src/';
const paths = {
src: {
root,
html: root + '*.html',
scripts: root + '**/*.js',
content: root + 'Content/',
fonts: root + 'Content/Fonts/',
images: root + 'Content/Images/',
exclude: {
libs: `!${root}JsLibraries/**`
}
},
dest: {
root: './_output/UI/',
content: './_output/UI/Content/',
fonts: './_output/UI/Content/Fonts/',
images: './_output/UI/Content/Images/'
}
};
module.exports = paths;
+15
View File
@@ -0,0 +1,15 @@
var gulp = require('gulp');
var print = require('gulp-print').default;
var paths = require('./helpers/paths.js');
gulp.task('imageMin', () => {
var imagemin = require('gulp-imagemin');
return gulp.src(paths.src.images)
.pipe(imagemin({
progressive: false,
optimizationLevel: 4,
svgoPlugins: [{ removeViewBox: false }]
}))
.pipe(print())
.pipe(gulp.dest(paths.src.content + 'Images/'));
});
+104
View File
@@ -0,0 +1,104 @@
// will download and run sonarr (server) in a non-windows enviroment
// you can use this if you don't care about the server code and just want to work
// with the web code.
var http = require('http');
var gulp = require('gulp');
var fs = require('fs');
var targz = require('tar.gz');
var del = require('del');
var spawn = require('child_process').spawn;
function download(url, dest, cb) {
console.log('Downloading ' + url + ' to ' + dest);
var file = fs.createWriteStream(dest);
http.get(url, function(response) {
response.pipe(file);
file.on('finish', function() {
console.log('Download completed');
file.close(cb);
});
});
}
function getLatest(cb) {
var branch = 'develop';
process.argv.forEach(function(val) {
var branchMatch = /branch=([\S]*)/.exec(val);
if (branchMatch && branchMatch.length > 1) {
branch = branchMatch[1];
}
});
var url = 'http://services.sonarr.tv/v1/update/' + branch + '?os=osx';
console.log('Checking for latest version:', url);
http.get(url, function(res) {
var data = '';
res.on('data', function(chunk) {
data += chunk;
});
res.on('end', function() {
var updatePackage = JSON.parse(data).updatePackage;
console.log('Latest version available: ' + updatePackage.version + ' Release Date: ' + updatePackage.releaseDate);
cb(updatePackage);
});
}).on('error', function(e) {
console.log('problem with request: ' + e.message);
});
}
function extract(source, dest, cb) {
console.log('extracting download page to ' + dest);
new targz().extract(source, dest, function(err) {
if (err) {
console.log(err);
}
console.log('Update package extracted.');
cb();
});
}
gulp.task('getSonarr', function() {
try {
fs.mkdirSync('./_start/');
} catch (e) {
if (e.code !== 'EEXIST') {
throw e;
}
}
getLatest(function(updatePackage) {
var packagePath = './_start/' + updatePackage.filename;
var dirName = './_start/' + updatePackage.version;
download(updatePackage.url, packagePath, function() {
extract(packagePath, dirName, function() {
// clean old binaries
console.log('Cleaning old binaries');
del.sync(['./_output/*', '!./_output/UI/']);
console.log('copying binaries to target');
gulp.src(dirName + '/NzbDrone/*.*')
.pipe(gulp.dest('./_output/'));
});
});
});
});
gulp.task('startSonarr', function() {
var ls = spawn('mono', ['--debug', './_output/NzbDrone.exe']);
ls.stdout.on('data', function(data) {
process.stdout.write(data);
});
ls.stderr.on('data', function(data) {
process.stdout.write(data);
});
ls.on('close', function(code) {
console.log('child process exited with code ' + code);
});
});
+13
View File
@@ -0,0 +1,13 @@
const gulp = require('gulp');
const paths = require('./helpers/paths.js');
const stripbom = require('gulp-stripbom');
function stripBom(dest) {
gulp.src([paths.src.scripts, paths.src.exclude.libs])
.pipe(stripbom({ showLog: false }))
.pipe(gulp.dest(dest));
}
gulp.task('stripBom', () => {
stripBom(paths.src.root);
});
+27
View File
@@ -0,0 +1,27 @@
const gulp = require('gulp');
const livereload = require('gulp-livereload');
const watch = require('gulp-watch');
const paths = require('./helpers/paths.js');
require('./copy.js');
require('./webpack.js');
function watchTask(glob, task) {
const options = {
name: `watch: ${task}`,
verbose: true
};
return watch(glob, options, () => {
gulp.start(task);
});
}
gulp.task('watch', ['copyHtml', 'copyFonts', 'copyImages', 'copyJs'], () => {
livereload.listen({ start: true });
gulp.start('webpackWatch');
watchTask(paths.src.html, 'copyHtml');
watchTask(`${paths.src.fonts}**/*.*`, 'copyFonts');
watchTask(`${paths.src.images}**/*.*`, 'copyImages');
});
+212
View File
@@ -0,0 +1,212 @@
const gulp = require('gulp');
const webpackStream = require('webpack-stream');
const livereload = require('gulp-livereload');
const path = require('path');
const webpack = require('webpack');
const errorHandler = require('./helpers/errorHandler');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const uiFolder = 'UI';
const root = path.join(__dirname, '..', 'src');
const isProduction = process.argv.indexOf('--production') > -1;
console.log('ROOT:', root);
console.log('isProduction:', isProduction);
const cssVarsFiles = [
'../src/Styles/Variables/colors',
'../src/Styles/Variables/dimensions',
'../src/Styles/Variables/fonts',
'../src/Styles/Variables/animations'
].map(require.resolve);
const extractCSSPlugin = new ExtractTextPlugin({
filename: path.join('_output', uiFolder, 'Content', 'styles.css'),
allChunks: true,
disable: false,
ignoreOrder: true
});
const plugins = [
extractCSSPlugin,
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor'
}),
new webpack.DefinePlugin({
__DEV__: !isProduction,
'process.env.NODE_ENV': isProduction ? JSON.stringify('production') : JSON.stringify('development')
})
];
if (isProduction) {
plugins.push(new UglifyJSPlugin({
sourceMap: true,
uglifyOptions: {
mangle: false,
output: {
comments: false,
beautify: true
}
}
}));
}
const config = {
devtool: '#source-map',
stats: {
children: false
},
watchOptions: {
ignored: /node_modules/
},
entry: {
preload: 'preload.js',
vendor: 'vendor.js',
index: 'index.js'
},
resolve: {
modules: [
root,
path.join(root, 'Shims'),
'node_modules'
],
alias: {
jquery: 'jquery/src/jquery'
}
},
output: {
filename: path.join('_output', uiFolder, '[name].js'),
sourceMapFilename: '[file].map'
},
plugins,
resolveLoader: {
modules: [
'node_modules',
'frontend/gulp/webpack/'
]
},
module: {
rules: [
{
test: /\.js?$/,
exclude: /(node_modules|JsLibraries)/,
loader: 'babel-loader',
query: {
plugins: ['transform-class-properties'],
presets: ['es2015', 'decorators-legacy', 'react', 'stage-2'],
env: {
development: {
plugins: ['transform-react-jsx-source']
}
}
}
},
// CSS Modules
{
test: /\.css$/,
exclude: /(node_modules|globals.css)/,
use: extractCSSPlugin.extract({
fallback: 'style-loader',
use: [
{
loader: 'css-variables-loader',
options: {
cssVarsFiles
}
},
{
loader: 'css-loader',
options: {
modules: true,
importLoaders: 1,
localIdentName: '[name]-[local]-[hash:base64:5]',
sourceMap: true
}
},
{
loader: 'postcss-loader',
options: {
config: {
ctx: {
cssVarsFiles
},
path: 'frontend/postcss.config.js'
}
}
}
]
})
},
// Global styles
{
test: /\.css$/,
include: /(node_modules|globals.css)/,
use: [
'style-loader',
{
loader: 'css-loader'
}
]
},
// Fonts
{
test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
use: [
{
loader: 'url-loader',
options: {
limit: 10240,
mimetype: 'application/font-woff',
emitFile: false,
name: 'Content/Fonts/[name].[ext]'
}
}
]
},
{
test: /\.(ttf|eot|eot?#iefix|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
use: [
{
loader: 'file-loader',
options: {
emitFile: false,
name: 'Content/Fonts/[name].[ext]'
}
}
]
}
]
}
};
gulp.task('webpack', () => {
return gulp.src('index.js')
.pipe(webpackStream(config))
.pipe(gulp.dest(''));
});
gulp.task('webpackWatch', () => {
config.watch = true;
return gulp.src('')
.pipe(webpackStream(config))
.on('error', errorHandler)
.pipe(gulp.dest(''))
.on('error', errorHandler)
.pipe(livereload())
.on('error', errorHandler);
});
@@ -0,0 +1,11 @@
const loaderUtils = require('loader-utils');
module.exports = function cssVariablesLoader(source) {
const options = loaderUtils.getOptions(this);
options.cssVarsFiles.forEach((cssVarsFile) => {
this.addDependency(cssVarsFile);
});
return source;
};
+33
View File
@@ -0,0 +1,33 @@
const reload = require('require-nocache')(module);
module.exports = (ctx, configPath, options) => {
const config = {
plugins: {
'postcss-mixins': {
mixinsDir: [
'frontend/src/Styles/Mixins'
]
},
'postcss-simple-vars': {
variables: () =>
ctx.options.cssVarsFiles.reduce((acc, vars) => {
return Object.assign(acc, reload(vars));
}, {})
},
'postcss-nested': {},
autoprefixer: {
browsers: [
'Chrome >= 30',
'Firefox >= 30',
'Safari >= 6',
'Edge >= 12',
'Explorer >= 11',
'iOS >= 7',
'Android >= 4.4'
]
}
}
};
return config;
};
+4
View File
@@ -0,0 +1,4 @@
// Place your settings in this file to overwrite default and user settings.
{
"files.insertFinalNewline": true
}
@@ -0,0 +1,110 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { icons } from 'Helpers/Props';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import TablePager from 'Components/Table/TablePager';
import PageContent from 'Components/Page/PageContent';
import PageContentBodyConnector from 'Components/Page/PageContentBodyConnector';
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
import BlacklistRowConnector from './BlacklistRowConnector';
class Blacklist extends Component {
//
// Render
render() {
const {
isFetching,
isPopulated,
error,
items,
columns,
totalRecords,
isClearingBlacklistExecuting,
onClearBlacklistPress,
...otherProps
} = this.props;
return (
<PageContent title="Blacklist">
<PageToolbar>
<PageToolbarSection>
<PageToolbarButton
label="Clear"
iconName={icons.CLEAR}
isSpinning={isClearingBlacklistExecuting}
onPress={onClearBlacklistPress}
/>
</PageToolbarSection>
</PageToolbar>
<PageContentBodyConnector>
{
isFetching && !isPopulated &&
<LoadingIndicator />
}
{
!isFetching && !!error &&
<div>Unable to load blacklist</div>
}
{
isPopulated && !error && !items.length &&
<div>
No history blacklist
</div>
}
{
isPopulated && !error && !!items.length &&
<div>
<Table
columns={columns}
{...otherProps}
>
<TableBody>
{
items.map((item) => {
return (
<BlacklistRowConnector
key={item.id}
columns={columns}
{...item}
/>
);
})
}
</TableBody>
</Table>
<TablePager
totalRecords={totalRecords}
isFetching={isFetching}
{...otherProps}
/>
</div>
}
</PageContentBodyConnector>
</PageContent>
);
}
}
Blacklist.propTypes = {
isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
error: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
totalRecords: PropTypes.number,
isClearingBlacklistExecuting: PropTypes.bool.isRequired,
onClearBlacklistPress: PropTypes.func.isRequired
};
export default Blacklist;
@@ -0,0 +1,145 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
import withCurrentPage from 'Components/withCurrentPage';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
import * as blacklistActions from 'Store/Actions/blacklistActions';
import { executeCommand } from 'Store/Actions/commandActions';
import * as commandNames from 'Commands/commandNames';
import Blacklist from './Blacklist';
function createMapStateToProps() {
return createSelector(
(state) => state.blacklist,
createCommandExecutingSelector(commandNames.CLEAR_BLACKLIST),
(blacklist, isClearingBlacklistExecuting) => {
return {
isClearingBlacklistExecuting,
...blacklist
};
}
);
}
const mapDispatchToProps = {
...blacklistActions,
executeCommand
};
class BlacklistConnector extends Component {
//
// Lifecycle
componentDidMount() {
const {
useCurrentPage,
fetchBlacklist,
gotoBlacklistFirstPage
} = this.props;
registerPagePopulator(this.repopulate);
if (useCurrentPage) {
fetchBlacklist();
} else {
gotoBlacklistFirstPage();
}
}
componentDidUpdate(prevProps) {
if (prevProps.isClearingBlacklistExecuting && !this.props.isClearingBlacklistExecuting) {
this.props.gotoBlacklistFirstPage();
}
}
componentWillUnmount() {
this.props.clearBlacklist();
unregisterPagePopulator(this.repopulate);
}
//
// Control
repopulate = () => {
this.props.fetchBlacklist();
}
//
// Listeners
onFirstPagePress = () => {
this.props.gotoBlacklistFirstPage();
}
onPreviousPagePress = () => {
this.props.gotoBlacklistPreviousPage();
}
onNextPagePress = () => {
this.props.gotoBlacklistNextPage();
}
onLastPagePress = () => {
this.props.gotoBlacklistLastPage();
}
onPageSelect = (page) => {
this.props.gotoBlacklistPage({ page });
}
onSortPress = (sortKey) => {
this.props.setBlacklistSort({ sortKey });
}
onTableOptionChange = (payload) => {
this.props.setBlacklistTableOption(payload);
if (payload.pageSize) {
this.props.gotoBlacklistFirstPage();
}
}
onClearBlacklistPress = () => {
this.props.executeCommand({ name: commandNames.CLEAR_BLACKLIST });
}
//
// Render
render() {
return (
<Blacklist
onFirstPagePress={this.onFirstPagePress}
onPreviousPagePress={this.onPreviousPagePress}
onNextPagePress={this.onNextPagePress}
onLastPagePress={this.onLastPagePress}
onPageSelect={this.onPageSelect}
onSortPress={this.onSortPress}
onTableOptionChange={this.onTableOptionChange}
onClearBlacklistPress={this.onClearBlacklistPress}
{...this.props}
/>
);
}
}
BlacklistConnector.propTypes = {
isClearingBlacklistExecuting: PropTypes.bool.isRequired,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
fetchBlacklist: PropTypes.func.isRequired,
gotoBlacklistFirstPage: PropTypes.func.isRequired,
gotoBlacklistPreviousPage: PropTypes.func.isRequired,
gotoBlacklistNextPage: PropTypes.func.isRequired,
gotoBlacklistLastPage: PropTypes.func.isRequired,
gotoBlacklistPage: PropTypes.func.isRequired,
setBlacklistSort: PropTypes.func.isRequired,
setBlacklistTableOption: PropTypes.func.isRequired,
clearBlacklist: PropTypes.func.isRequired,
executeCommand: PropTypes.func.isRequired
};
export default withCurrentPage(
connect(createMapStateToProps, mapDispatchToProps)(BlacklistConnector)
);
@@ -0,0 +1,89 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import Button from 'Components/Link/Button';
import DescriptionList from 'Components/DescriptionList/DescriptionList';
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
import Modal from 'Components/Modal/Modal';
import ModalContent from 'Components/Modal/ModalContent';
import ModalHeader from 'Components/Modal/ModalHeader';
import ModalBody from 'Components/Modal/ModalBody';
import ModalFooter from 'Components/Modal/ModalFooter';
class BlacklistDetailsModal extends Component {
//
// Render
render() {
const {
isOpen,
sourceTitle,
protocol,
indexer,
message,
onModalClose
} = this.props;
return (
<Modal
isOpen={isOpen}
onModalClose={onModalClose}
>
<ModalContent
onModalClose={onModalClose}
>
<ModalHeader>
Details
</ModalHeader>
<ModalBody>
<DescriptionList>
<DescriptionListItem
title="Name"
data={sourceTitle}
/>
<DescriptionListItem
title="Protocol"
data={protocol}
/>
{
!!message &&
<DescriptionListItem
title="Indexer"
data={indexer}
/>
}
{
!!message &&
<DescriptionListItem
title="Message"
data={message}
/>
}
</DescriptionList>
</ModalBody>
<ModalFooter>
<Button onPress={onModalClose}>
Close
</Button>
</ModalFooter>
</ModalContent>
</Modal>
);
}
}
BlacklistDetailsModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
sourceTitle: PropTypes.string.isRequired,
protocol: PropTypes.string.isRequired,
indexer: PropTypes.string,
message: PropTypes.string,
onModalClose: PropTypes.func.isRequired
};
export default BlacklistDetailsModal;
@@ -0,0 +1,18 @@
.language,
.quality {
composes: cell from 'Components/Table/Cells/TableRowCell.css';
width: 100px;
}
.indexer {
composes: cell from 'Components/Table/Cells/TableRowCell.css';
width: 80px;
}
.actions {
composes: cell from 'Components/Table/Cells/TableRowCell.css';
width: 70px;
}
@@ -0,0 +1,186 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { icons, kinds } from 'Helpers/Props';
import IconButton from 'Components/Link/IconButton';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import TableRow from 'Components/Table/TableRow';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import EpisodeLanguage from 'Episode/EpisodeLanguage';
import EpisodeQuality from 'Episode/EpisodeQuality';
import SeriesTitleLink from 'Series/SeriesTitleLink';
import BlacklistDetailsModal from './BlacklistDetailsModal';
import styles from './BlacklistRow.css';
class BlacklistRow extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
isDetailsModalOpen: false
};
}
//
// Listeners
onDetailsPress = () => {
this.setState({ isDetailsModalOpen: true });
}
onDetailsModalClose = () => {
this.setState({ isDetailsModalOpen: false });
}
//
// Render
render() {
const {
series,
sourceTitle,
language,
quality,
date,
protocol,
indexer,
message,
columns,
onRemovePress
} = this.props;
return (
<TableRow>
{
columns.map((column) => {
const {
name,
isVisible
} = column;
if (!isVisible) {
return null;
}
if (name === 'series.sortTitle') {
return (
<TableRowCell key={name}>
<SeriesTitleLink
titleSlug={series.titleSlug}
title={series.title}
/>
</TableRowCell>
);
}
if (name === 'sourceTitle') {
return (
<TableRowCell key={name}>
{sourceTitle}
</TableRowCell>
);
}
if (name === 'language') {
return (
<TableRowCell
key={name}
className={styles.language}
>
<EpisodeLanguage
language={language}
/>
</TableRowCell>
);
}
if (name === 'quality') {
return (
<TableRowCell
key={name}
className={styles.quality}
>
<EpisodeQuality
quality={quality}
/>
</TableRowCell>
);
}
if (name === 'date') {
return (
<RelativeDateCellConnector
key={name}
date={date}
/>
);
}
if (name === 'indexer') {
return (
<TableRowCell
key={name}
className={styles.indexer}
>
{indexer}
</TableRowCell>
);
}
if (name === 'actions') {
return (
<TableRowCell
key={name}
className={styles.actions}
>
<IconButton
name={icons.INFO}
onPress={this.onDetailsPress}
/>
<IconButton
title="Remove from blacklist"
name={icons.REMOVE}
kind={kinds.DANGER}
onPress={onRemovePress}
/>
</TableRowCell>
);
}
return null;
})
}
<BlacklistDetailsModal
isOpen={this.state.isDetailsModalOpen}
sourceTitle={sourceTitle}
protocol={protocol}
indexer={indexer}
message={message}
onModalClose={this.onDetailsModalClose}
/>
</TableRow>
);
}
}
BlacklistRow.propTypes = {
id: PropTypes.number.isRequired,
series: PropTypes.object.isRequired,
sourceTitle: PropTypes.string.isRequired,
language: PropTypes.object.isRequired,
quality: PropTypes.object.isRequired,
date: PropTypes.string.isRequired,
protocol: PropTypes.string.isRequired,
indexer: PropTypes.string,
message: PropTypes.string,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
onRemovePress: PropTypes.func.isRequired
};
export default BlacklistRow;
@@ -0,0 +1,26 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { removeFromBlacklist } from 'Store/Actions/blacklistActions';
import createSeriesSelector from 'Store/Selectors/createSeriesSelector';
import BlacklistRow from './BlacklistRow';
function createMapStateToProps() {
return createSelector(
createSeriesSelector(),
(series) => {
return {
series
};
}
);
}
function createMapDispatchToProps(dispatch, props) {
return {
onRemovePress() {
dispatch(removeFromBlacklist({ id: props.id }));
}
};
}
export default connect(createMapStateToProps, createMapDispatchToProps)(BlacklistRow);
@@ -0,0 +1,5 @@
.description {
composes: title from 'Components/DescriptionList/DescriptionListItemDescription.css';
overflow-wrap: break-word;
}
@@ -0,0 +1,244 @@
import PropTypes from 'prop-types';
import React from 'react';
import formatDateTime from 'Utilities/Date/formatDateTime';
import formatAge from 'Utilities/Number/formatAge';
import Link from 'Components/Link/Link';
import DescriptionList from 'Components/DescriptionList/DescriptionList';
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
import DescriptionListItemTitle from 'Components/DescriptionList/DescriptionListItemTitle';
import DescriptionListItemDescription from 'Components/DescriptionList/DescriptionListItemDescription';
import styles from './HistoryDetails.css';
function HistoryDetails(props) {
const {
eventType,
sourceTitle,
data,
shortDateFormat,
timeFormat
} = props;
if (eventType === 'grabbed') {
const {
indexer,
releaseGroup,
nzbInfoUrl,
downloadClient,
downloadId,
age,
ageHours,
ageMinutes,
publishedDate
} = data;
return (
<DescriptionList>
<DescriptionListItem
descriptionClassName={styles.description}
title="Name"
data={sourceTitle}
/>
{
!!indexer &&
<DescriptionListItem
title="Indexer"
data={indexer}
/>
}
{
!!releaseGroup &&
<DescriptionListItem
descriptionClassName={styles.description}
title="Release Group"
data={releaseGroup}
/>
}
{
!!nzbInfoUrl &&
<span>
<DescriptionListItemTitle>
Info URL
</DescriptionListItemTitle>
<DescriptionListItemDescription>
<Link to={nzbInfoUrl}>{nzbInfoUrl}</Link>
</DescriptionListItemDescription>
</span>
}
{
!!downloadClient &&
<DescriptionListItem
title="Download Client"
data={downloadClient}
/>
}
{
!!downloadId &&
<DescriptionListItem
title="Grab ID"
data={downloadId}
/>
}
{
!!indexer &&
<DescriptionListItem
title="Age (when grabbed)"
data={formatAge(age, ageHours, ageMinutes)}
/>
}
{
!!publishedDate &&
<DescriptionListItem
title="Published Date"
data={formatDateTime(publishedDate, shortDateFormat, timeFormat, { includeSeconds: true })}
/>
}
</DescriptionList>
);
}
if (eventType === 'downloadFailed') {
const {
message
} = data;
return (
<DescriptionList>
<DescriptionListItem
descriptionClassName={styles.description}
title="Name"
data={sourceTitle}
/>
{
!!message &&
<DescriptionListItem
title="Message"
data={message}
/>
}
</DescriptionList>
);
}
if (eventType === 'downloadFolderImported') {
const {
droppedPath,
importedPath
} = data;
return (
<DescriptionList>
<DescriptionListItem
descriptionClassName={styles.description}
title="Name"
data={sourceTitle}
/>
{
!!droppedPath &&
<DescriptionListItem
descriptionClassName={styles.description}
title="Source"
data={droppedPath}
/>
}
{
!!importedPath &&
<DescriptionListItem
descriptionClassName={styles.description}
title="Imported To"
data={importedPath}
/>
}
</DescriptionList>
);
}
if (eventType === 'episodeFileDeleted') {
const {
reason
} = data;
let reasonMessage = '';
switch (reason) {
case 'Manual':
reasonMessage = 'File was deleted by via UI';
break;
case 'MissingFromDisk':
reasonMessage = 'Sonarr was unable to find the file on disk so it was removed';
break;
case 'Upgrade':
reasonMessage = 'File was deleted to import an upgrade';
break;
default:
reasonMessage = '';
}
return (
<DescriptionList>
<DescriptionListItem
title="Name"
data={sourceTitle}
/>
<DescriptionListItem
title="Reason"
data={reasonMessage}
/>
</DescriptionList>
);
}
if (eventType === 'episodeFileRenamed') {
const {
sourcePath,
sourceRelativePath,
path,
relativePath
} = data;
return (
<DescriptionList>
<DescriptionListItem
title="Source Path"
data={sourcePath}
/>
<DescriptionListItem
title="Source Relative Path"
data={sourceRelativePath}
/>
<DescriptionListItem
title="Destination Path"
data={path}
/>
<DescriptionListItem
title="Destination Relative Path"
data={relativePath}
/>
</DescriptionList>
);
}
}
HistoryDetails.propTypes = {
eventType: PropTypes.string.isRequired,
sourceTitle: PropTypes.string.isRequired,
data: PropTypes.object.isRequired,
shortDateFormat: PropTypes.string.isRequired,
timeFormat: PropTypes.string.isRequired
};
export default HistoryDetails;
@@ -0,0 +1,19 @@
import _ from 'lodash';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import HistoryDetails from './HistoryDetails';
function createMapStateToProps() {
return createSelector(
createUISettingsSelector(),
(uiSettings) => {
return _.pick(uiSettings, [
'shortDateFormat',
'timeFormat'
]);
}
);
}
export default connect(createMapStateToProps)(HistoryDetails);
@@ -0,0 +1,5 @@
.markAsFailedButton {
composes: button from 'Components/Link/Button.css';
margin-right: auto;
}
@@ -0,0 +1,104 @@
import PropTypes from 'prop-types';
import React from 'react';
import { kinds } from 'Helpers/Props';
import Button from 'Components/Link/Button';
import SpinnerButton from 'Components/Link/SpinnerButton';
import Modal from 'Components/Modal/Modal';
import ModalContent from 'Components/Modal/ModalContent';
import ModalHeader from 'Components/Modal/ModalHeader';
import ModalBody from 'Components/Modal/ModalBody';
import ModalFooter from 'Components/Modal/ModalFooter';
import HistoryDetails from './HistoryDetails';
import styles from './HistoryDetailsModal.css';
function getHeaderTitle(eventType) {
switch (eventType) {
case 'grabbed':
return 'Grabbed';
case 'downloadFailed':
return 'Download Failed';
case 'downloadFolderImported':
return 'Episode Imported';
case 'episodeFileDeleted':
return 'Episode File Deleted';
case 'episodeFileRenamed':
return 'Episode File Renamed';
default:
return 'Unknown';
}
}
function HistoryDetailsModal(props) {
const {
isOpen,
eventType,
sourceTitle,
data,
isMarkingAsFailed,
shortDateFormat,
timeFormat,
onMarkAsFailedPress,
onModalClose
} = props;
return (
<Modal
isOpen={isOpen}
onModalClose={onModalClose}
>
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
{getHeaderTitle(eventType)}
</ModalHeader>
<ModalBody>
<HistoryDetails
eventType={eventType}
sourceTitle={sourceTitle}
data={data}
shortDateFormat={shortDateFormat}
timeFormat={timeFormat}
/>
</ModalBody>
<ModalFooter>
{
eventType === 'grabbed' &&
<SpinnerButton
className={styles.markAsFailedButton}
kind={kinds.DANGER}
isSpinning={isMarkingAsFailed}
onPress={onMarkAsFailedPress}
>
Mark as Failed
</SpinnerButton>
}
<Button
onPress={onModalClose}
>
Close
</Button>
</ModalFooter>
</ModalContent>
</Modal>
);
}
HistoryDetailsModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
eventType: PropTypes.string.isRequired,
sourceTitle: PropTypes.string.isRequired,
data: PropTypes.object.isRequired,
isMarkingAsFailed: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired,
timeFormat: PropTypes.string.isRequired,
onMarkAsFailedPress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
HistoryDetailsModal.defaultProps = {
isMarkingAsFailed: false
};
export default HistoryDetailsModal;
+161
View File
@@ -0,0 +1,161 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { align, icons } from 'Helpers/Props';
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import TablePager from 'Components/Table/TablePager';
import PageContent from 'Components/Page/PageContent';
import PageContentBodyConnector from 'Components/Page/PageContentBodyConnector';
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
import FilterMenu from 'Components/Menu/FilterMenu';
import HistoryRowConnector from './HistoryRowConnector';
class History extends Component {
//
// Lifecycle
shouldComponentUpdate(nextProps) {
// Don't update when fetching has completed if items have changed,
// before episodes start fetching or when episodes start fetching.
if (
(
this.props.isFetching &&
nextProps.isPopulated &&
hasDifferentItems(this.props.items, nextProps.items)
) ||
(!this.props.isEpisodesFetching && nextProps.isEpisodesFetching)
) {
return false;
}
return true;
}
//
// Render
render() {
const {
isFetching,
isPopulated,
error,
items,
columns,
selectedFilterKey,
filters,
totalRecords,
isEpisodesFetching,
isEpisodesPopulated,
episodesError,
onFilterSelect,
onFirstPagePress,
...otherProps
} = this.props;
const isFetchingAny = isFetching || isEpisodesFetching;
const isAllPopulated = isPopulated && (isEpisodesPopulated || !items.length);
const hasError = error || episodesError;
return (
<PageContent title="History">
<PageToolbar>
<PageToolbarSection>
<PageToolbarButton
label="Refresh"
iconName={icons.REFRESH}
isSpinning={isFetching}
onPress={onFirstPagePress}
/>
</PageToolbarSection>
<PageToolbarSection alignContent={align.RIGHT}>
<FilterMenu
alignMenu={align.RIGHT}
selectedFilterKey={selectedFilterKey}
filters={filters}
customFilters={[]}
onFilterSelect={onFilterSelect}
/>
</PageToolbarSection>
</PageToolbar>
<PageContentBodyConnector>
{
isFetchingAny && !isAllPopulated &&
<LoadingIndicator />
}
{
!isFetchingAny && hasError &&
<div>Unable to load history</div>
}
{
// If history isPopulated and it's empty show no history found and don't
// wait for the episodes to populate because they are never coming.
isPopulated && !hasError && !items.length &&
<div>
No history found
</div>
}
{
isAllPopulated && !hasError && !!items.length &&
<div>
<Table
columns={columns}
{...otherProps}
>
<TableBody>
{
items.map((item) => {
return (
<HistoryRowConnector
key={item.id}
columns={columns}
{...item}
/>
);
})
}
</TableBody>
</Table>
<TablePager
totalRecords={totalRecords}
isFetching={isFetchingAny}
onFirstPagePress={onFirstPagePress}
{...otherProps}
/>
</div>
}
</PageContentBodyConnector>
</PageContent>
);
}
}
History.propTypes = {
isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
error: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
selectedFilterKey: PropTypes.string.isRequired,
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
totalRecords: PropTypes.number,
isEpisodesFetching: PropTypes.bool.isRequired,
isEpisodesPopulated: PropTypes.bool.isRequired,
episodesError: PropTypes.object,
onFilterSelect: PropTypes.func.isRequired,
onFirstPagePress: PropTypes.func.isRequired
};
export default History;
@@ -0,0 +1,157 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
import selectUniqueIds from 'Utilities/Object/selectUniqueIds';
import withCurrentPage from 'Components/withCurrentPage';
import * as historyActions from 'Store/Actions/historyActions';
import { fetchEpisodes, clearEpisodes } from 'Store/Actions/episodeActions';
import History from './History';
function createMapStateToProps() {
return createSelector(
(state) => state.history,
(state) => state.episodes,
(history, episodes) => {
return {
isEpisodesFetching: episodes.isFetching,
isEpisodesPopulated: episodes.isPopulated,
episodesError: episodes.error,
...history
};
}
);
}
const mapDispatchToProps = {
...historyActions,
fetchEpisodes,
clearEpisodes
};
class HistoryConnector extends Component {
//
// Lifecycle
componentDidMount() {
const {
useCurrentPage,
fetchHistory,
gotoHistoryFirstPage
} = this.props;
registerPagePopulator(this.repopulate);
if (useCurrentPage) {
fetchHistory();
} else {
gotoHistoryFirstPage();
}
}
componentDidUpdate(prevProps) {
if (hasDifferentItems(prevProps.items, this.props.items)) {
const episodeIds = selectUniqueIds(this.props.items, 'episodeId');
if (episodeIds.length) {
this.props.fetchEpisodes({ episodeIds });
} else {
this.props.clearEpisodes();
}
}
}
componentWillUnmount() {
unregisterPagePopulator(this.repopulate);
this.props.clearHistory();
this.props.clearEpisodes();
}
//
// Control
repopulate = () => {
this.props.fetchHistory();
}
//
// Listeners
onFirstPagePress = () => {
this.props.gotoHistoryFirstPage();
}
onPreviousPagePress = () => {
this.props.gotoHistoryPreviousPage();
}
onNextPagePress = () => {
this.props.gotoHistoryNextPage();
}
onLastPagePress = () => {
this.props.gotoHistoryLastPage();
}
onPageSelect = (page) => {
this.props.gotoHistoryPage({ page });
}
onSortPress = (sortKey) => {
this.props.setHistorySort({ sortKey });
}
onFilterSelect = (selectedFilterKey) => {
this.props.setHistoryFilter({ selectedFilterKey });
}
onTableOptionChange = (payload) => {
this.props.setHistoryTableOption(payload);
if (payload.pageSize) {
this.props.gotoHistoryFirstPage();
}
}
//
// Render
render() {
return (
<History
onFirstPagePress={this.onFirstPagePress}
onPreviousPagePress={this.onPreviousPagePress}
onNextPagePress={this.onNextPagePress}
onLastPagePress={this.onLastPagePress}
onPageSelect={this.onPageSelect}
onSortPress={this.onSortPress}
onFilterSelect={this.onFilterSelect}
onTableOptionChange={this.onTableOptionChange}
{...this.props}
/>
);
}
}
HistoryConnector.propTypes = {
items: PropTypes.arrayOf(PropTypes.object).isRequired,
fetchHistory: PropTypes.func.isRequired,
gotoHistoryFirstPage: PropTypes.func.isRequired,
gotoHistoryPreviousPage: PropTypes.func.isRequired,
gotoHistoryNextPage: PropTypes.func.isRequired,
gotoHistoryLastPage: PropTypes.func.isRequired,
gotoHistoryPage: PropTypes.func.isRequired,
setHistorySort: PropTypes.func.isRequired,
setHistoryFilter: PropTypes.func.isRequired,
setHistoryTableOption: PropTypes.func.isRequired,
clearHistory: PropTypes.func.isRequired,
fetchEpisodes: PropTypes.func.isRequired,
clearEpisodes: PropTypes.func.isRequired
};
export default withCurrentPage(
connect(createMapStateToProps, mapDispatchToProps)(HistoryConnector)
);
@@ -0,0 +1,6 @@
.cell {
composes: cell from 'Components/Table/Cells/TableRowCell.css';
width: 35px;
text-align: center;
}
@@ -0,0 +1,82 @@
import PropTypes from 'prop-types';
import React from 'react';
import { icons, kinds } from 'Helpers/Props';
import Icon from 'Components/Icon';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import styles from './HistoryEventTypeCell.css';
function getIconName(eventType) {
switch (eventType) {
case 'grabbed':
return icons.DOWNLOADING;
case 'seriesFolderImported':
return icons.DRIVE;
case 'downloadFolderImported':
return icons.DOWNLOADED;
case 'downloadFailed':
return icons.DOWNLOADING;
case 'episodeFileDeleted':
return icons.DELETE;
case 'episodeFileRenamed':
return icons.ORGANIZE;
default:
return icons.UNKNOWN;
}
}
function getIconKind(eventType) {
switch (eventType) {
case 'downloadFailed':
return kinds.DANGER;
default:
return kinds.DEFAULT;
}
}
function getTooltip(eventType, data) {
switch (eventType) {
case 'grabbed':
return `Episode grabbed from ${data.indexer} and sent to ${data.downloadClient}`;
case 'seriesFolderImported':
return 'Episode imported from series folder';
case 'downloadFolderImported':
return 'Episode downloaded successfully and picked up from download client';
case 'downloadFailed':
return 'Episode download failed';
case 'episodeFileDeleted':
return 'Episode file deleted';
case 'episodeFileRenamed':
return 'Episode file renamed';
default:
return 'Unknown event';
}
}
function HistoryEventTypeCell({ eventType, data }) {
const iconName = getIconName(eventType);
const iconKind = getIconKind(eventType);
const tooltip = getTooltip(eventType, data);
return (
<TableRowCell
className={styles.cell}
title={tooltip}
>
<Icon
name={iconName}
kind={iconKind}
/>
</TableRowCell>
);
}
HistoryEventTypeCell.propTypes = {
eventType: PropTypes.string.isRequired,
data: PropTypes.object
};
HistoryEventTypeCell.defaultProps = {
data: {}
};
export default HistoryEventTypeCell;
@@ -0,0 +1,23 @@
.downloadClient {
composes: cell from 'Components/Table/Cells/TableRowCell.css';
width: 120px;
}
.indexer {
composes: cell from 'Components/Table/Cells/TableRowCell.css';
width: 80px;
}
.releaseGroup {
composes: cell from 'Components/Table/Cells/TableRowCell.css';
width: 110px;
}
.details {
composes: cell from 'Components/Table/Cells/TableRowCell.css';
width: 30px;
}
+263
View File
@@ -0,0 +1,263 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { icons } from 'Helpers/Props';
import IconButton from 'Components/Link/IconButton';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import TableRow from 'Components/Table/TableRow';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import episodeEntities from 'Episode/episodeEntities';
import SeasonEpisodeNumber from 'Episode/SeasonEpisodeNumber';
import EpisodeTitleLink from 'Episode/EpisodeTitleLink';
import EpisodeLanguage from 'Episode/EpisodeLanguage';
import EpisodeQuality from 'Episode/EpisodeQuality';
import SeriesTitleLink from 'Series/SeriesTitleLink';
import HistoryEventTypeCell from './HistoryEventTypeCell';
import HistoryDetailsModal from './Details/HistoryDetailsModal';
import styles from './HistoryRow.css';
class HistoryRow extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
isDetailsModalOpen: false
};
}
componentDidUpdate(prevProps) {
if (
prevProps.isMarkingAsFailed &&
!this.props.isMarkingAsFailed &&
!this.props.markAsFailedError
) {
this.setState({ isDetailsModalOpen: false });
}
}
//
// Listeners
onDetailsPress = () => {
this.setState({ isDetailsModalOpen: true });
}
onDetailsModalClose = () => {
this.setState({ isDetailsModalOpen: false });
}
//
// Render
render() {
const {
episodeId,
series,
episode,
language,
languageCutoffNotMet,
quality,
qualityCutoffNotMet,
eventType,
sourceTitle,
date,
data,
isMarkingAsFailed,
columns,
shortDateFormat,
timeFormat,
onMarkAsFailedPress
} = this.props;
if (!episode) {
return null;
}
return (
<TableRow>
{
columns.map((column) => {
const {
name,
isVisible
} = column;
if (!isVisible) {
return null;
}
if (name === 'eventType') {
return (
<HistoryEventTypeCell
key={name}
eventType={eventType}
data={data}
/>
);
}
if (name === 'series.sortTitle') {
return (
<TableRowCell key={name}>
<SeriesTitleLink
titleSlug={series.titleSlug}
title={series.title}
/>
</TableRowCell>
);
}
if (name === 'episode') {
return (
<TableRowCell key={name}>
<SeasonEpisodeNumber
seasonNumber={episode.seasonNumber}
episodeNumber={episode.episodeNumber}
absoluteEpisodeNumber={episode.absoluteEpisodeNumber}
seriesType={series.seriesType}
alternateTitles={series.alternateTitles}
sceneSeasonNumber={episode.sceneSeasonNumber}
sceneEpisodeNumber={episode.sceneEpisodeNumber}
sceneAbsoluteEpisodeNumber={episode.sceneAbsoluteEpisodeNumber}
/>
</TableRowCell>
);
}
if (name === 'episodeTitle') {
return (
<TableRowCell key={name}>
<EpisodeTitleLink
episodeId={episodeId}
episodeEntity={episodeEntities.EPISODES}
seriesId={series.id}
episodeTitle={episode.title}
showOpenSeriesButton={true}
/>
</TableRowCell>
);
}
if (name === 'language') {
return (
<TableRowCell key={name}>
<EpisodeLanguage
language={language}
isCutoffMet={languageCutoffNotMet}
/>
</TableRowCell>
);
}
if (name === 'quality') {
return (
<TableRowCell key={name}>
<EpisodeQuality
quality={quality}
isCutoffMet={qualityCutoffNotMet}
/>
</TableRowCell>
);
}
if (name === 'date') {
return (
<RelativeDateCellConnector
key={name}
date={date}
/>
);
}
if (name === 'downloadClient') {
return (
<TableRowCell
key={name}
className={styles.downloadClient}
>
{data.downloadClient}
</TableRowCell>
);
}
if (name === 'indexer') {
return (
<TableRowCell
key={name}
className={styles.indexer}
>
{data.indexer}
</TableRowCell>
);
}
if (name === 'releaseGroup') {
return (
<TableRowCell
key={name}
className={styles.releaseGroup}
>
{data.releaseGroup}
</TableRowCell>
);
}
if (name === 'details') {
return (
<TableRowCell
key={name}
className={styles.details}
>
<IconButton
name={icons.INFO}
onPress={this.onDetailsPress}
/>
</TableRowCell>
);
}
return null;
})
}
<HistoryDetailsModal
isOpen={this.state.isDetailsModalOpen}
eventType={eventType}
sourceTitle={sourceTitle}
data={data}
isMarkingAsFailed={isMarkingAsFailed}
shortDateFormat={shortDateFormat}
timeFormat={timeFormat}
onMarkAsFailedPress={onMarkAsFailedPress}
onModalClose={this.onDetailsModalClose}
/>
</TableRow>
);
}
}
HistoryRow.propTypes = {
episodeId: PropTypes.number,
series: PropTypes.object.isRequired,
episode: PropTypes.object,
language: PropTypes.object.isRequired,
languageCutoffNotMet: PropTypes.bool.isRequired,
quality: PropTypes.object.isRequired,
qualityCutoffNotMet: PropTypes.bool.isRequired,
eventType: PropTypes.string.isRequired,
sourceTitle: PropTypes.string.isRequired,
date: PropTypes.string.isRequired,
data: PropTypes.object.isRequired,
isMarkingAsFailed: PropTypes.bool,
markAsFailedError: PropTypes.object,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
shortDateFormat: PropTypes.string.isRequired,
timeFormat: PropTypes.string.isRequired,
onMarkAsFailedPress: PropTypes.func.isRequired
};
export default HistoryRow;
@@ -0,0 +1,76 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchHistory, markAsFailed } from 'Store/Actions/historyActions';
import createSeriesSelector from 'Store/Selectors/createSeriesSelector';
import createEpisodeSelector from 'Store/Selectors/createEpisodeSelector';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import HistoryRow from './HistoryRow';
function createMapStateToProps() {
return createSelector(
createSeriesSelector(),
createEpisodeSelector(),
createUISettingsSelector(),
(series, episode, uiSettings) => {
return {
series,
episode,
shortDateFormat: uiSettings.shortDateFormat,
timeFormat: uiSettings.timeFormat
};
}
);
}
const mapDispatchToProps = {
fetchHistory,
markAsFailed
};
class HistoryRowConnector extends Component {
//
// Lifecycle
componentDidUpdate(prevProps) {
if (
prevProps.isMarkingAsFailed &&
!this.props.isMarkingAsFailed &&
!this.props.markAsFailedError
) {
this.props.fetchHistory();
}
}
//
// Listeners
onMarkAsFailedPress = () => {
this.props.markAsFailed({ id: this.props.id });
}
//
// Render
render() {
return (
<HistoryRow
{...this.props}
onMarkAsFailedPress={this.onMarkAsFailedPress}
/>
);
}
}
HistoryRowConnector.propTypes = {
id: PropTypes.number.isRequired,
isMarkingAsFailed: PropTypes.bool,
markAsFailedError: PropTypes.object,
fetchHistory: PropTypes.func.isRequired,
markAsFailed: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(HistoryRowConnector);
@@ -0,0 +1,13 @@
.torrent {
composes: label from 'Components/Label.css';
border-color: $torrentColor;
background-color: $torrentColor;
}
.usenet {
composes: label from 'Components/Label.css';
border-color: $usenetColor;
background-color: $usenetColor;
}
@@ -0,0 +1,20 @@
import PropTypes from 'prop-types';
import React from 'react';
import Label from 'Components/Label';
import styles from './ProtocolLabel.css';
function ProtocolLabel({ protocol }) {
const protocolName = protocol === 'usenet' ? 'nzb' : protocol;
return (
<Label className={styles[protocol]}>
{protocolName}
</Label>
);
}
ProtocolLabel.propTypes = {
protocol: PropTypes.string.isRequired
};
export default ProtocolLabel;
+266
View File
@@ -0,0 +1,266 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
import getSelectedIds from 'Utilities/Table/getSelectedIds';
import selectAll from 'Utilities/Table/selectAll';
import toggleSelected from 'Utilities/Table/toggleSelected';
import { icons } from 'Helpers/Props';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import TablePager from 'Components/Table/TablePager';
import PageContent from 'Components/Page/PageContent';
import PageContentBodyConnector from 'Components/Page/PageContentBodyConnector';
import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
import RemoveQueueItemsModal from './RemoveQueueItemsModal';
import QueueOptionsConnector from './QueueOptionsConnector';
import QueueRowConnector from './QueueRowConnector';
class Queue extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
allSelected: false,
allUnselected: false,
lastToggled: null,
selectedState: {},
isPendingSelected: false,
isConfirmRemoveModalOpen: false
};
}
shouldComponentUpdate(nextProps) {
// Don't update when fetching has completed if items have changed,
// before episodes start fetching or when episodes start fetching.
if (
(
this.props.isFetching &&
nextProps.isPopulated &&
hasDifferentItems(this.props.items, nextProps.items)
) ||
(!this.props.isEpisodesFetching && nextProps.isEpisodesFetching)
) {
return false;
}
return true;
}
componentDidUpdate(prevProps) {
if (hasDifferentItems(prevProps.items, this.props.items)) {
this.setState({ selectedState: {} });
return;
}
const selectedIds = this.getSelectedIds();
const isPendingSelected = _.some(this.props.items, (item) => {
return selectedIds.indexOf(item.id) > -1 && item.status === 'Delay';
});
if (isPendingSelected !== this.state.isPendingSelected) {
this.setState({ isPendingSelected });
}
}
//
// Control
getSelectedIds = () => {
return getSelectedIds(this.state.selectedState);
}
//
// Listeners
onSelectAllChange = ({ value }) => {
this.setState(selectAll(this.state.selectedState, value));
}
onSelectedChange = ({ id, value, shiftKey = false }) => {
this.setState((state) => {
return toggleSelected(state, this.props.items, id, value, shiftKey);
});
}
onGrabSelectedPress = () => {
this.props.onGrabSelectedPress(this.getSelectedIds());
}
onRemoveSelectedPress = () => {
this.setState({ isConfirmRemoveModalOpen: true });
}
onRemoveSelectedConfirmed = (blacklist) => {
this.props.onRemoveSelectedPress(this.getSelectedIds(), blacklist);
this.setState({ isConfirmRemoveModalOpen: false });
}
onConfirmRemoveModalClose = () => {
this.setState({ isConfirmRemoveModalOpen: false });
}
//
// Render
render() {
const {
isFetching,
isPopulated,
error,
items,
isEpisodesFetching,
isEpisodesPopulated,
episodesError,
columns,
totalRecords,
isGrabbing,
isRemoving,
isCheckForFinishedDownloadExecuting,
onRefreshPress,
...otherProps
} = this.props;
const {
allSelected,
allUnselected,
selectedState,
isConfirmRemoveModalOpen,
isPendingSelected
} = this.state;
const isRefreshing = isFetching || isEpisodesFetching || isCheckForFinishedDownloadExecuting;
const isAllPopulated = isPopulated && (isEpisodesPopulated || !items.length);
const hasError = error || episodesError;
const selectedCount = this.getSelectedIds().length;
const disableSelectedActions = selectedCount === 0;
return (
<PageContent title="Queue">
<PageToolbar>
<PageToolbarSection>
<PageToolbarButton
label="Refresh"
iconName={icons.REFRESH}
isSpinning={isRefreshing}
onPress={onRefreshPress}
/>
<PageToolbarSeparator />
<PageToolbarButton
label="Grab Selected"
iconName={icons.DOWNLOAD}
isDisabled={disableSelectedActions || !isPendingSelected}
isSpinning={isGrabbing}
onPress={this.onGrabSelectedPress}
/>
<PageToolbarButton
label="Remove Selected"
iconName={icons.REMOVE}
isDisabled={disableSelectedActions}
isSpinning={isRemoving}
onPress={this.onRemoveSelectedPress}
/>
</PageToolbarSection>
</PageToolbar>
<PageContentBodyConnector>
{
isRefreshing && !isAllPopulated &&
<LoadingIndicator />
}
{
!isRefreshing && hasError &&
<div>
Failed to load Queue
</div>
}
{
isPopulated && !hasError && !items.length &&
<div>
Queue is empty
</div>
}
{
isAllPopulated && !hasError && !!items.length &&
<div>
<Table
columns={columns}
selectAll={true}
allSelected={allSelected}
allUnselected={allUnselected}
{...otherProps}
optionsComponent={QueueOptionsConnector}
onSelectAllChange={this.onSelectAllChange}
>
<TableBody>
{
items.map((item) => {
return (
<QueueRowConnector
key={item.id}
episodeId={item.episodeId}
isSelected={selectedState[item.id]}
columns={columns}
{...item}
onSelectedChange={this.onSelectedChange}
/>
);
})
}
</TableBody>
</Table>
<TablePager
totalRecords={totalRecords}
isFetching={isRefreshing}
{...otherProps}
/>
</div>
}
</PageContentBodyConnector>
<RemoveQueueItemsModal
isOpen={isConfirmRemoveModalOpen}
selectedCount={selectedCount}
onRemovePress={this.onRemoveSelectedConfirmed}
onModalClose={this.onConfirmRemoveModalClose}
/>
</PageContent>
);
}
}
Queue.propTypes = {
isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
error: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
isEpisodesFetching: PropTypes.bool.isRequired,
isEpisodesPopulated: PropTypes.bool.isRequired,
episodesError: PropTypes.object,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
totalRecords: PropTypes.number,
isGrabbing: PropTypes.bool.isRequired,
isRemoving: PropTypes.bool.isRequired,
isCheckForFinishedDownloadExecuting: PropTypes.bool.isRequired,
onRefreshPress: PropTypes.func.isRequired,
onGrabSelectedPress: PropTypes.func.isRequired,
onRemoveSelectedPress: PropTypes.func.isRequired
};
export default Queue;
@@ -0,0 +1,186 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
import selectUniqueIds from 'Utilities/Object/selectUniqueIds';
import withCurrentPage from 'Components/withCurrentPage';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
import { executeCommand } from 'Store/Actions/commandActions';
import * as queueActions from 'Store/Actions/queueActions';
import { fetchEpisodes, clearEpisodes } from 'Store/Actions/episodeActions';
import * as commandNames from 'Commands/commandNames';
import Queue from './Queue';
function createMapStateToProps() {
return createSelector(
(state) => state.episodes,
(state) => state.queue.options,
(state) => state.queue.paged,
createCommandExecutingSelector(commandNames.CHECK_FOR_FINISHED_DOWNLOAD),
(episodes, options, queue, isCheckForFinishedDownloadExecuting) => {
return {
isEpisodesFetching: episodes.isFetching,
isEpisodesPopulated: episodes.isPopulated,
episodesError: episodes.error,
isCheckForFinishedDownloadExecuting,
...options,
...queue
};
}
);
}
const mapDispatchToProps = {
...queueActions,
fetchEpisodes,
clearEpisodes,
executeCommand
};
class QueueConnector extends Component {
//
// Lifecycle
componentDidMount() {
const {
useCurrentPage,
fetchQueue,
gotoQueueFirstPage
} = this.props;
registerPagePopulator(this.repopulate);
if (useCurrentPage) {
fetchQueue();
} else {
gotoQueueFirstPage();
}
}
componentDidUpdate(prevProps) {
if (hasDifferentItems(prevProps.items, this.props.items)) {
const episodeIds = selectUniqueIds(this.props.items, 'episodeId');
if (episodeIds.length) {
this.props.fetchEpisodes({ episodeIds });
} else {
this.props.clearEpisodes();
}
}
if (
this.props.includeUnknownSeriesItems !==
prevProps.includeUnknownSeriesItems
) {
this.repopulate();
}
}
componentWillUnmount() {
unregisterPagePopulator(this.repopulate);
this.props.clearQueue();
this.props.clearEpisodes();
}
//
// Control
repopulate = () => {
this.props.fetchQueue();
}
//
// Listeners
onFirstPagePress = () => {
this.props.gotoQueueFirstPage();
}
onPreviousPagePress = () => {
this.props.gotoQueuePreviousPage();
}
onNextPagePress = () => {
this.props.gotoQueueNextPage();
}
onLastPagePress = () => {
this.props.gotoQueueLastPage();
}
onPageSelect = (page) => {
this.props.gotoQueuePage({ page });
}
onSortPress = (sortKey) => {
this.props.setQueueSort({ sortKey });
}
onTableOptionChange = (payload) => {
this.props.setQueueTableOption(payload);
if (payload.pageSize) {
this.props.gotoQueueFirstPage();
}
}
onRefreshPress = () => {
this.props.executeCommand({
name: commandNames.CHECK_FOR_FINISHED_DOWNLOAD
});
}
onGrabSelectedPress = (ids) => {
this.props.grabQueueItems({ ids });
}
onRemoveSelectedPress = (ids, blacklist) => {
this.props.removeQueueItems({ ids, blacklist });
}
//
// Render
render() {
return (
<Queue
onFirstPagePress={this.onFirstPagePress}
onPreviousPagePress={this.onPreviousPagePress}
onNextPagePress={this.onNextPagePress}
onLastPagePress={this.onLastPagePress}
onPageSelect={this.onPageSelect}
onSortPress={this.onSortPress}
onTableOptionChange={this.onTableOptionChange}
onRefreshPress={this.onRefreshPress}
onGrabSelectedPress={this.onGrabSelectedPress}
onRemoveSelectedPress={this.onRemoveSelectedPress}
{...this.props}
/>
);
}
}
QueueConnector.propTypes = {
items: PropTypes.arrayOf(PropTypes.object).isRequired,
fetchQueue: PropTypes.func.isRequired,
gotoQueueFirstPage: PropTypes.func.isRequired,
gotoQueuePreviousPage: PropTypes.func.isRequired,
gotoQueueNextPage: PropTypes.func.isRequired,
gotoQueueLastPage: PropTypes.func.isRequired,
gotoQueuePage: PropTypes.func.isRequired,
setQueueSort: PropTypes.func.isRequired,
setQueueTableOption: PropTypes.func.isRequired,
clearQueue: PropTypes.func.isRequired,
grabQueueItems: PropTypes.func.isRequired,
removeQueueItems: PropTypes.func.isRequired,
fetchEpisodes: PropTypes.func.isRequired,
clearEpisodes: PropTypes.func.isRequired,
executeCommand: PropTypes.func.isRequired
};
export default withCurrentPage(
connect(createMapStateToProps, mapDispatchToProps)(QueueConnector)
);
@@ -0,0 +1,97 @@
import moment from 'moment';
import PropTypes from 'prop-types';
import React from 'react';
import { icons, kinds } from 'Helpers/Props';
import Icon from 'Components/Icon';
function QueueDetails(props) {
const {
title,
size,
sizeleft,
estimatedCompletionTime,
status: queueStatus,
errorMessage,
progressBar
} = props;
const status = queueStatus.toLowerCase();
const progress = (100 - sizeleft / size * 100);
if (status === 'pending') {
return (
<Icon
name={icons.PENDING}
title={`Release will be processed ${moment(estimatedCompletionTime).fromNow()}`}
/>
);
}
if (status === 'completed') {
if (errorMessage) {
return (
<Icon
name={icons.DOWNLOAD}
kind={kinds.DANGER}
title={`Import failed: ${errorMessage}`}
/>
);
}
// TODO: show an icon when download is complete, but not imported yet?
}
if (errorMessage) {
return (
<Icon
name={icons.DOWNLOADING}
kind={kinds.DANGER}
title={`Download failed: ${errorMessage}`}
/>
);
}
if (status === 'failed') {
return (
<Icon
name={icons.DOWNLOADING}
kind={kinds.DANGER}
title="Download failed: check download client for more details"
/>
);
}
if (status === 'warning') {
return (
<Icon
name={icons.DOWNLOADING}
kind={kinds.WARNING}
title="Download warning: check download client for more details"
/>
);
}
if (progress < 5) {
return (
<Icon
name={icons.DOWNLOADING}
title={`Episode is downloading - ${progress.toFixed(1)}% ${title}`}
/>
);
}
return progressBar;
}
QueueDetails.propTypes = {
title: PropTypes.string.isRequired,
size: PropTypes.number.isRequired,
sizeleft: PropTypes.number.isRequired,
estimatedCompletionTime: PropTypes.string,
status: PropTypes.string.isRequired,
errorMessage: PropTypes.string,
progressBar: PropTypes.node.isRequired
};
export default QueueDetails;
@@ -0,0 +1,77 @@
import PropTypes from 'prop-types';
import React, { Component, Fragment } from 'react';
import { inputTypes } from 'Helpers/Props';
import FormGroup from 'Components/Form/FormGroup';
import FormLabel from 'Components/Form/FormLabel';
import FormInputGroup from 'Components/Form/FormInputGroup';
class QueueOptions extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
includeUnknownSeriesItems: props.includeUnknownSeriesItems
};
}
componentDidUpdate(prevProps) {
const {
includeUnknownSeriesItems
} = this.props;
if (includeUnknownSeriesItems !== prevProps.includeUnknownSeriesItems) {
this.setState({
includeUnknownSeriesItems
});
}
}
//
// Listeners
onOptionChange = ({ name, value }) => {
this.setState({
[name]: value
}, () => {
this.props.onOptionChange({
[name]: value
});
});
}
//
// Render
render() {
const {
includeUnknownSeriesItems
} = this.state;
return (
<Fragment>
<FormGroup>
<FormLabel>Show Unknown Series Items</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="includeUnknownSeriesItems"
value={includeUnknownSeriesItems}
helpText="Show items without a series in the queue, this could include removed series, movies or anything else in Sonarr's category"
onChange={this.onOptionChange}
/>
</FormGroup>
</Fragment>
);
}
}
QueueOptions.propTypes = {
includeUnknownSeriesItems: PropTypes.bool.isRequired,
onOptionChange: PropTypes.func.isRequired
};
export default QueueOptions;
@@ -0,0 +1,19 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { setQueueOption } from 'Store/Actions/queueActions';
import QueueOptions from './QueueOptions';
function createMapStateToProps() {
return createSelector(
(state) => state.queue.options,
(options) => {
return options;
}
);
}
const mapDispatchToProps = {
onOptionChange: setQueueOption
};
export default connect(createMapStateToProps, mapDispatchToProps)(QueueOptions);
+23
View File
@@ -0,0 +1,23 @@
.quality {
composes: cell from 'Components/Table/Cells/TableRowCell.css';
width: 150px;
}
.protocol {
composes: cell from 'Components/Table/Cells/TableRowCell.css';
width: 100px;
}
.progress {
composes: cell from 'Components/Table/Cells/TableRowCell.css';
width: 150px;
}
.actions {
composes: cell from 'Components/Table/Cells/TableRowCell.css';
width: 70px;
}
+369
View File
@@ -0,0 +1,369 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { icons, kinds } from 'Helpers/Props';
import IconButton from 'Components/Link/IconButton';
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
import ProgressBar from 'Components/ProgressBar';
import TableRow from 'Components/Table/TableRow';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
import ProtocolLabel from 'Activity/Queue/ProtocolLabel';
import EpisodeTitleLink from 'Episode/EpisodeTitleLink';
import EpisodeQuality from 'Episode/EpisodeQuality';
import SeasonEpisodeNumber from 'Episode/SeasonEpisodeNumber';
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
import SeriesTitleLink from 'Series/SeriesTitleLink';
import QueueStatusCell from './QueueStatusCell';
import TimeleftCell from './TimeleftCell';
import RemoveQueueItemModal from './RemoveQueueItemModal';
import styles from './QueueRow.css';
class QueueRow extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
isRemoveQueueItemModalOpen: false,
isInteractiveImportModalOpen: false
};
}
//
// Listeners
onRemoveQueueItemPress = () => {
this.setState({ isRemoveQueueItemModalOpen: true });
}
onRemoveQueueItemModalConfirmed = (blacklist) => {
this.props.onRemoveQueueItemPress(blacklist);
this.setState({ isRemoveQueueItemModalOpen: false });
}
onRemoveQueueItemModalClose = () => {
this.setState({ isRemoveQueueItemModalOpen: false });
}
onInteractiveImportPress = () => {
this.setState({ isInteractiveImportModalOpen: true });
}
onInteractiveImportModalClose = () => {
this.setState({ isInteractiveImportModalOpen: false });
}
//
// Render
render() {
const {
id,
downloadId,
title,
status,
trackedDownloadStatus,
statusMessages,
errorMessage,
series,
episode,
quality,
protocol,
indexer,
downloadClient,
estimatedCompletionTime,
timeleft,
size,
sizeleft,
showRelativeDates,
shortDateFormat,
timeFormat,
isGrabbing,
grabError,
isRemoving,
isSelected,
columns,
onSelectedChange,
onGrabPress
} = this.props;
const {
isRemoveQueueItemModalOpen,
isInteractiveImportModalOpen
} = this.state;
const progress = 100 - (sizeleft / size * 100);
const showInteractiveImport = status === 'Completed' && trackedDownloadStatus === 'Warning';
const isPending = status === 'Delay' || status === 'DownloadClientUnavailable';
return (
<TableRow>
<TableSelectCell
id={id}
isSelected={isSelected}
onSelectedChange={onSelectedChange}
/>
{
columns.map((column) => {
const {
name,
isVisible
} = column;
if (!isVisible) {
return null;
}
if (name === 'status') {
return (
<QueueStatusCell
key={name}
sourceTitle={title}
status={status}
trackedDownloadStatus={trackedDownloadStatus}
statusMessages={statusMessages}
errorMessage={errorMessage}
/>
);
}
if (name === 'series.sortTitle') {
return (
<TableRowCell key={name}>
{
series ?
<SeriesTitleLink
titleSlug={series.titleSlug}
title={series.title}
/> :
title
}
</TableRowCell>
);
}
if (name === 'episode') {
return (
<TableRowCell key={name}>
{
episode ?
<SeasonEpisodeNumber
seasonNumber={episode.seasonNumber}
episodeNumber={episode.episodeNumber}
absoluteEpisodeNumber={episode.absoluteEpisodeNumber}
seriesType={series.seriesType}
alternateTitles={series.alternateTitles}
sceneSeasonNumber={episode.sceneSeasonNumber}
sceneEpisodeNumber={episode.sceneEpisodeNumber}
sceneAbsoluteEpisodeNumber={episode.sceneAbsoluteEpisodeNumber}
unverifiedSceneNumbering={episode.unverifiedSceneNumbering}
/> :
'-'
}
</TableRowCell>
);
}
if (name === 'episode.title') {
return (
<TableRowCell key={name}>
{
episode ?
<EpisodeTitleLink
episodeId={episode.id}
seriesId={series.id}
episodeFileId={episode.episodeFileId}
episodeTitle={episode.title}
showOpenSeriesButton={true}
/> :
'-'
}
</TableRowCell>
);
}
if (name === 'episode.airDateUtc') {
if (episode) {
return (
<RelativeDateCellConnector
key={name}
date={episode.airDateUtc}
/>
);
}
return (
<TableRowCell key={name}>
-
</TableRowCell>
);
}
if (name === 'quality') {
return (
<TableRowCell key={name}>
<EpisodeQuality
quality={quality}
/>
</TableRowCell>
);
}
if (name === 'protocol') {
return (
<TableRowCell key={name}>
<ProtocolLabel
protocol={protocol}
/>
</TableRowCell>
);
}
if (name === 'indexer') {
return (
<TableRowCell key={name}>
{indexer}
</TableRowCell>
);
}
if (name === 'downloadClient') {
return (
<TableRowCell key={name}>
{downloadClient}
</TableRowCell>
);
}
if (name === 'estimatedCompletionTime') {
return (
<TimeleftCell
key={name}
status={status}
estimatedCompletionTime={estimatedCompletionTime}
timeleft={timeleft}
size={size}
sizeleft={sizeleft}
showRelativeDates={showRelativeDates}
shortDateFormat={shortDateFormat}
timeFormat={timeFormat}
/>
);
}
if (name === 'progress') {
return (
<TableRowCell
key={name}
className={styles.progress}
>
{
!!progress &&
<ProgressBar
progress={progress}
title={`${progress.toFixed(1)}%`}
/>
}
</TableRowCell>
);
}
if (name === 'actions') {
return (
<TableRowCell
key={name}
className={styles.actions}
>
{
showInteractiveImport &&
<IconButton
name={icons.INTERACTIVE}
onPress={this.onInteractiveImportPress}
/>
}
{
isPending &&
<SpinnerIconButton
name={icons.DOWNLOAD}
kind={grabError ? kinds.DANGER : kinds.DEFAULT}
isSpinning={isGrabbing}
onPress={onGrabPress}
/>
}
<SpinnerIconButton
title="Remove from queue"
name={icons.REMOVE}
isSpinning={isRemoving}
onPress={this.onRemoveQueueItemPress}
/>
</TableRowCell>
);
}
return null;
})
}
<InteractiveImportModal
isOpen={isInteractiveImportModalOpen}
downloadId={downloadId}
title={title}
onModalClose={this.onInteractiveImportModalClose}
/>
<RemoveQueueItemModal
isOpen={isRemoveQueueItemModalOpen}
sourceTitle={title}
onRemovePress={this.onRemoveQueueItemModalConfirmed}
onModalClose={this.onRemoveQueueItemModalClose}
/>
</TableRow>
);
}
}
QueueRow.propTypes = {
id: PropTypes.number.isRequired,
downloadId: PropTypes.string,
title: PropTypes.string.isRequired,
status: PropTypes.string.isRequired,
trackedDownloadStatus: PropTypes.string,
statusMessages: PropTypes.arrayOf(PropTypes.object),
errorMessage: PropTypes.string,
series: PropTypes.object,
episode: PropTypes.object,
quality: PropTypes.object.isRequired,
protocol: PropTypes.string.isRequired,
indexer: PropTypes.string,
downloadClient: PropTypes.string,
estimatedCompletionTime: PropTypes.string,
timeleft: PropTypes.string,
size: PropTypes.number,
sizeleft: PropTypes.number,
showRelativeDates: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired,
timeFormat: PropTypes.string.isRequired,
isGrabbing: PropTypes.bool.isRequired,
grabError: PropTypes.object,
isRemoving: PropTypes.bool.isRequired,
isSelected: PropTypes.bool,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
onSelectedChange: PropTypes.func.isRequired,
onGrabPress: PropTypes.func.isRequired,
onRemoveQueueItemPress: PropTypes.func.isRequired
};
QueueRow.defaultProps = {
isGrabbing: false,
isRemoving: false
};
export default QueueRow;
@@ -0,0 +1,71 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { grabQueueItem, removeQueueItem } from 'Store/Actions/queueActions';
import createSeriesSelector from 'Store/Selectors/createSeriesSelector';
import createEpisodeSelector from 'Store/Selectors/createEpisodeSelector';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import QueueRow from './QueueRow';
function createMapStateToProps() {
return createSelector(
createSeriesSelector(),
createEpisodeSelector(),
createUISettingsSelector(),
(series, episode, uiSettings) => {
const result = _.pick(uiSettings, [
'showRelativeDates',
'shortDateFormat',
'timeFormat'
]);
result.series = series;
result.episode = episode;
return result;
}
);
}
const mapDispatchToProps = {
grabQueueItem,
removeQueueItem
};
class QueueRowConnector extends Component {
//
// Listeners
onGrabPress = () => {
this.props.grabQueueItem({ id: this.props.id });
}
onRemoveQueueItemPress = (blacklist) => {
this.props.removeQueueItem({ id: this.props.id, blacklist });
}
//
// Render
render() {
return (
<QueueRow
{...this.props}
onGrabPress={this.onGrabPress}
onRemoveQueueItemPress={this.onRemoveQueueItemPress}
/>
);
}
}
QueueRowConnector.propTypes = {
id: PropTypes.number.isRequired,
episode: PropTypes.object,
grabQueueItem: PropTypes.func.isRequired,
removeQueueItem: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(QueueRowConnector);

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